diff --git a/ROADMAP.md b/ROADMAP.md
index 5505e66..c4358c9 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -25,8 +25,8 @@
* [ ] Ban[4]
* [ ] Unban[4]
* [ ] Room metadata changes
- * [x] Name[1]
- * [x] Topic[1]
+ * [x] Name
+ * [x] Topic
* [ ] Avatar
* [ ] Per-room user nick
* KakaoTalk → Matrix
diff --git a/matrix_appservice_kakaotalk/portal.py b/matrix_appservice_kakaotalk/portal.py
index e7be31f..e569152 100644
--- a/matrix_appservice_kakaotalk/portal.py
+++ b/matrix_appservice_kakaotalk/portal.py
@@ -1219,9 +1219,6 @@ class Portal(DBPortal, BasePortal):
# Misses should be guarded by supports_state_event, but handle this just in case
self.log.error(f"Skipping Matrix state event {evt.event_id} of unsupported type {evt.type}")
return
- if not self.is_open:
- self.log.info(f"Not bridging f{handler.action_name} change of portal for non-open channel")
- return
try:
effective_sender, _ = await self.get_relay_sender(sender, f"{handler.action_name} {evt.event_id}")
if effective_sender:
@@ -1338,6 +1335,9 @@ class Portal(DBPortal, BasePortal):
) -> None:
if content.topic == prev_content.topic:
return
+ if not self.is_open:
+ self.log.info(f"Not bridging topic change of portal for non-open channel")
+ return
if not (sender and sender.is_connected):
raise Exception(
"Only users connected to KakaoTalk can set the description of a KakaoTalk channel"
diff --git a/node/src/client.js b/node/src/client.js
index b2ff192..622c51a 100644
--- a/node/src/client.js
+++ b/node/src/client.js
@@ -366,20 +366,39 @@ class UserClient {
* @param {ChannelProps} channelProps
*/
async getChannel(channelProps) {
- let channel = this.#talkClient.channelList.get(channelProps.id)
- if (channel) {
- return channel
- } else {
- const channelList = getChannelListForType(
- this.#talkClient.channelList,
- channelProps.type
- )
- const res = await channelList.addChannel({ channelId: channelProps.id })
- if (!res.success) {
- throw new Error(`Unable to add ${channelProps.type} channel ${channelProps.id}`)
- }
- return res.result
+ const talkChannel = this.#talkClient.channelList.get(channelProps.id)
+ if (talkChannel) {
+ return talkChannel
}
+
+ const channelList = getChannelListForType(
+ this.#talkClient.channelList,
+ channelProps.type
+ )
+ const res = await channelList.addChannel({ channelId: channelProps.id })
+ if (!res.success) {
+ this.error(`Unable to add ${channelProps.type} channel ${channelProps.id}`)
+ throw res
+ }
+ return res.result
+ }
+
+ /**
+ * @param {Long} channelId
+ */
+ async getNormalChannel(channelId) {
+ const channelList = this.#talkClient.channelList.normal
+ const talkChannel = channelList.get(channelId)
+ if (talkChannel) {
+ return talkChannel
+ }
+
+ const res = await channelList.addChannel({ channelId: channelId })
+ if (!res.success) {
+ this.error(`Unable to add normal channel ${channelProps.id}`)
+ throw res
+ }
+ return res.result
}
/**
@@ -600,14 +619,32 @@ export default class PeerClient {
const userClient = this.#getUser(mxid)
const talkChannel = await userClient.getChannel(channelProps)
if (permNeeded) {
- const permActual = talkChannel.getUserInfo({ userId: userClient.userId }).perm
- if (permNeeded.indexOf(permActual) == -1) {
- throw new PermError(permNeeded, permActual, action)
- }
+ await this.#requireChannelPerm(talkChannel, permNeeded, action)
}
return talkChannel
}
+ /**
+ * @param {TalkOpenChannel} talkChannel
+ * @param {OpenChannelUserPerm[]} permNeeded Throw if the user's permission level matches none of the values in this list.
+ * @param {string} action The action requiring permission
+ * @throws {PermError} if the user does not have the specified permission level.
+ */
+ async #requireChannelPerm(talkChannel, permNeeded, action) {
+ const permActual = talkChannel.getUserInfo({ userId: talkChannel.clientUser.userId }).perm
+ if (permNeeded.indexOf(permActual) == -1) {
+ throw new PermError(permNeeded, permActual, action)
+ }
+ }
+
+ /**
+ * @param {string} mxid
+ * @param {Long} channelId
+ */
+ async #getUserNormalChannel(mxid, channelId) {
+ return await this.#getUser(mxid).getNormalChannel(channelId)
+ }
+
/**
* @param {object} req
* @param {string} req.mxid
@@ -1104,13 +1141,12 @@ export default class PeerClient {
* @param {string} req.name
*/
setChannelName = async (req) => {
- const talkChannel = await this.#getUserChannel(
- req.mxid,
- req.channel_props,
- [OpenChannelUserPerm.OWNER],
- "change channel name"
- )
- return await talkChannel.setTitleMeta(req.name)
+ if (!isChannelTypeOpen(req.channel_props.type)) {
+ const talkChannel = await this.#getUserNormalChannel(req.mxid, req.channel_props.id)
+ return await talkChannel.setTitleMeta(req.name)
+ } else {
+ return await this.#setOpenChannelProperty(req.mxid, req.channel_props, "linkName", req.name)
+ }
}
/**
@@ -1120,13 +1156,33 @@ export default class PeerClient {
* @param {string} req.description
*/
setChannelDescription = async (req) => {
- const talkChannel = await this.#getUserChannel(
- req.mxid,
- req.channel_props,
- [OpenChannelUserPerm.OWNER],
- "change channel description"
+ return await this.#setOpenChannelProperty(req.mxid, req.channel_props, "description", req.description)
+ }
+
+ /**
+ * @param {string} mxid
+ * @param {ChannelProps} channelProps
+ * @param {string} propertyName
+ * @param {any} propertyValue
+ */
+ async #setOpenChannelProperty(mxid, channelProps, propertyName, propertyValue) {
+ if (isChannelTypeOpen(channelProps)) {
+ throw ProtocolError(`Cannot set ${propertyName} of non-open channel ${channelProps.id} (type = ${channelProps.type})`)
+ }
+
+ const userClient = this.#getUser(mxid)
+ /** @type {TalkOpenChannel} */
+ const talkChannel = await userClient.getChannel(channelProps)
+ this.#requireChannelPerm(talkChannel, [OpenChannelUserPerm.OWNER], `change channel ${propertyName}`)
+
+ const linkRes = await talkChannel.getLatestOpenLink()
+ if (!linkRes.success) throw linkRes
+
+ const link = linkRes.result
+ link[propertyName] = propertyValue
+ return await userClient.talkClient.channelList.open.updateOpenLink(
+ { linkId: link.linkId }, link
)
- return await talkChannel.setNoticeMeta(req.description)
}
/*