Try outbound room title & description, and work on outbound room avatar

But they fail with -203 (invalid body)
This commit is contained in:
Andrew Ferrazzutti 2022-04-14 03:45:01 -04:00
parent abf3114203
commit 770b0e447b
4 changed files with 232 additions and 49 deletions

View File

@ -398,6 +398,39 @@ class Client:
perm=perm,
)
async def set_channel_name(
self,
channel_props: ChannelProps,
name: str,
) -> None:
return await self._api_user_request_void(
"set_channel_name",
channel_props=channel_props.serialize(),
name=name,
)
async def set_channel_description(
self,
channel_props: ChannelProps,
description: str,
) -> None:
return await self._api_user_request_void(
"set_channel_description",
channel_props=channel_props.serialize(),
description=description,
)
async def set_channel_photo(
self,
channel_props: ChannelProps,
photo_url: str,
) -> None:
return await self._api_user_request_void(
"set_channel_photo",
channel_props=channel_props.serialize(),
photo_url=photo_url,
)
# TODO Combine these into one

View File

@ -26,7 +26,6 @@ from mautrix.types import (
RedactionEvent,
RoomID,
SingleReceiptEventContent,
PowerLevelStateEventContent,
StateEvent,
UserID,
)
@ -162,25 +161,6 @@ class MatrixHandler(BaseMatrixHandler):
if message:
await user.client.mark_read(portal.channel_props, message.ktid)
@classmethod
async def handle_power_level(
cls,
room_id: RoomID,
user_id: UserID,
prev_content: PowerLevelStateEventContent,
content: PowerLevelStateEventContent,
event_id: EventID,
) -> None:
user = await u.User.get_by_mxid(user_id)
if not user:
return
portal = await po.Portal.get_by_mxid(room_id)
if not portal:
return
await portal.handle_matrix_power_level(user, prev_content, content, event_id)
async def handle_ephemeral_event(
self, evt: ReceiptEvent | Event
) -> None:
@ -198,5 +178,13 @@ class MatrixHandler(BaseMatrixHandler):
"""
async def handle_state_event(self, evt: StateEvent) -> None:
if evt.type == EventType.ROOM_POWER_LEVELS:
await self.handle_power_level(evt.room_id, evt.sender, evt.prev_content, evt.content, evt.event_id)
if po.Portal.supports_state_event(evt.type):
user = await u.User.get_by_mxid(evt.sender)
if not user:
return
portal = await po.Portal.get_by_mxid(evt.room_id)
if not portal:
return
await portal.handle_matrix_state_event(user, evt)

View File

@ -20,6 +20,8 @@ from typing import (
Any,
AsyncGenerator,
Awaitable,
Callable,
NamedTuple,
Pattern,
cast,
)
@ -49,8 +51,13 @@ from mautrix.types import (
MessageEventContent,
MessageType,
PowerLevelStateEventContent,
RoomAvatarStateEventContent,
RoomNameStateEventContent,
RoomTopicStateEventContent,
RelationType,
RoomID,
StateEvent,
StateEventContent,
TextMessageEventContent,
UserID,
VideoInfo,
@ -118,6 +125,14 @@ class FakeLock:
pass
class StateEventHandler(NamedTuple):
# TODO Can this use Generic to force the two StateEventContent parameters to be of the same type?
# Or, just have a single StateEvent parameter
apply: Callable[[u.User, StateEventContent, StateEventContent], Awaitable[None]]
revert: Callable[[StateEventContent], Awaitable[None]]
action_name: str
StateBridge = EventType.find("m.bridge", EventType.Class.STATE)
StateHalfShotBridge = EventType.find("uk.half-shot.bridge", EventType.Class.STATE)
@ -199,8 +214,7 @@ class Portal(DBPortal, BasePortal):
NotificationDisabler.puppet_cls = p.Puppet
NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"]
# TODO More
cls._chat_type_handler_map = {
cls._CHAT_TYPE_HANDLER_MAP = {
KnownChatType.FEED: cls._handle_kakaotalk_feed,
KnownChatType.TEXT: cls._handle_kakaotalk_text,
KnownChatType.REPLY: cls._handle_kakaotalk_reply,
@ -212,6 +226,33 @@ class Portal(DBPortal, BasePortal):
16385: cls._handle_kakaotalk_deleted,
}
cls._STATE_EVENT_HANDLER_MAP: dict[EventType, StateEventHandler] = {
EventType.ROOM_POWER_LEVELS: StateEventHandler(
cls._handle_matrix_power_levels,
cls._revert_matrix_power_levels,
"power level"
),
EventType.ROOM_NAME: StateEventHandler(
cls._handle_matrix_room_name,
cls._revert_matrix_room_name,
"room name"
),
EventType.ROOM_TOPIC: StateEventHandler(
cls._handle_matrix_room_topic,
cls._revert_matrix_room_topic,
"room topic"
),
EventType.ROOM_AVATAR: StateEventHandler(
cls._handle_matrix_room_avatar,
cls._revert_matrix_room_avatar,
"room avatar"
),
}
@classmethod
def supports_state_event(cls, evt_type: EventType) -> bool:
return evt_type in cls._STATE_EVENT_HANDLER_MAP
# region DB conversion
async def delete(self) -> None:
@ -1044,51 +1085,50 @@ class Portal(DBPortal, BasePortal):
pass
"""
async def handle_matrix_power_level(
self,
sender: u.User,
prev_content: PowerLevelStateEventContent,
content: PowerLevelStateEventContent,
event_id: EventID,
) -> None:
async def handle_matrix_state_event(self, sender: u.User, evt: StateEvent) -> None:
try:
await self._handle_matrix_power_level(sender, prev_content, content, event_id)
handler: StateEventHandler = self._STATE_EVENT_HANDLER_MAP[evt.type]
except KeyError:
# 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
try:
effective_sender, _ = await self.get_relay_sender(sender, f"{handler.action_name} {evt.event_id}")
await handler.apply(self, effective_sender, evt.prev_content, evt.content)
except Exception as e:
self.log.error(
f"Failed to handle Matrix power level {event_id}: {e}",
f"Failed to handle Matrix {handler.action_name} {evt.event_id}: {e}",
exc_info=not isinstance(e, NotImplementedError),
)
sender.send_remote_checkpoint(
self._status_from_exception(e),
event_id,
evt.event_id,
self.mxid,
EventType.ROOM_POWER_LEVELS,
evt.type,
error=e,
)
if not isinstance(e, NotImplementedError):
change = f"{handler.action_name} change"
await self._send_bridge_error(
f"{e}. Reverting the power level change...",
thing="power level change"
f"{e}. Reverting the {change}...",
thing=change
)
# NOTE Redacting instead doesn't work
await self.main_intent.set_power_levels(self.mxid, prev_content)
await handler.revert(self, evt.prev_content)
else:
await self._send_delivery_receipt(event_id)
await self._send_delivery_receipt(evt.event_id)
sender.send_remote_checkpoint(
MessageSendCheckpointStatus.SUCCESS,
event_id,
evt.event_id,
self.mxid,
EventType.ROOM_POWER_LEVELS,
evt.type,
)
async def _handle_matrix_power_level(
async def _handle_matrix_power_levels(
self,
sender: u.User,
prev_content: PowerLevelStateEventContent,
content: PowerLevelStateEventContent,
event_id: EventID,
) -> None:
sender, _ = await self.get_relay_sender(sender, f"power level {event_id}")
for target_mxid, power_level in content.users.items():
if power_level == prev_content.get_user_level(target_mxid):
continue
@ -1102,6 +1142,74 @@ class Portal(DBPortal, BasePortal):
"Only users connected to KakaoTalk can set power levels of KakaoTalk users"
)
async def _revert_matrix_power_levels(self, prev_content: PowerLevelStateEventContent) -> None:
await self.main_intent.set_power_levels(self.mxid, prev_content)
async def _handle_matrix_room_name(
self,
sender: u.User,
prev_content: RoomNameStateEventContent,
content: RoomNameStateEventContent,
) -> None:
if content.name == prev_content.name:
return
if not (sender and sender.is_connected):
raise Exception(
"Only users connected to KakaoTalk can set the name of a KakaoTalk channel"
)
await sender.client.set_channel_name(self.channel_props, content.name)
self.name = content.name
self.name_set = True
await self.save()
async def _revert_matrix_room_name(self, prev_content: RoomNameStateEventContent) -> None:
await self.main_intent.set_room_name(self.mxid, prev_content.name)
async def _handle_matrix_room_topic(
self,
sender: u.User,
prev_content: RoomTopicStateEventContent,
content: RoomTopicStateEventContent,
) -> None:
if content.topic == prev_content.topic:
return
if not (sender and sender.is_connected):
raise Exception(
"Only users connected to KakaoTalk can set the description of a KakaoTalk channel"
)
await sender.client.set_channel_description(self.channel_props, content.topic)
self.description = content.topic
self.topic_set = True
await self.save()
async def _revert_matrix_room_topic(self, prev_content: RoomTopicStateEventContent) -> None:
await self.main_intent.set_room_topic(self.mxid, prev_content.topic)
async def _handle_matrix_room_avatar(
self,
sender: u.User,
prev_content: RoomAvatarStateEventContent,
content: RoomAvatarStateEventContent,
) -> None:
if content.url == prev_content.url:
return
if not (sender and sender.is_connected):
raise Exception(
"Only users connected to KakaoTalk can set the photo of a KakaoTalk channel"
)
raise NotImplementedError("Changing the room avatar is not supported by the KakaoTalk bridge.")
""" TODO
photo_url = str(self.main_intent.api.get_download_url(content.url))
await sender.client.set_channel_photo(self.channel_props, photo_url)
self.photo_id = photo_url
self.avatar_url = content.url
self.avatar_set = True
await self.save()
"""
async def _revert_matrix_room_avatar(self, prev_content: RoomAvatarStateEventContent) -> None:
await self.main_intent.set_room_avatar(self.mxid, prev_content.url)
async def handle_matrix_leave(self, user: u.User) -> None:
if self.is_direct:
self.log.info(f"{user.mxid} left private chat portal with {self.ktid}")
@ -1204,7 +1312,7 @@ class Portal(DBPortal, BasePortal):
await intent.ensure_joined(self.mxid)
self._backfill_leave.add(intent)
handler = self._chat_type_handler_map.get(chat.type, Portal._handle_kakaotalk_unsupported)
handler = self._CHAT_TYPE_HANDLER_MAP.get(chat.type, Portal._handle_kakaotalk_unsupported)
event_ids = [
event_id for event_id in
await handler(

View File

@ -519,7 +519,7 @@ export default class PeerClient {
const talkChannel = await userClient.getChannel(channelProps)
if (permNeeded) {
const permActual = talkChannel.getUserInfo({ userId: userClient.userId }).perm
if (!(permActual in permNeeded)) {
if (permNeeded.indexOf(permActual) == -1) {
throw new PermError(permNeeded, permActual, action)
}
}
@ -807,6 +807,57 @@ export default class PeerClient {
return await talkChannel.setUserPerm({ userId: req.user_id }, req.perm)
}
/**
* @param {Object} req
* @param {string} req.mxid
* @param {ChannelProps} req.channel_props
* @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)
}
/**
* @param {Object} req
* @param {string} req.mxid
* @param {ChannelProps} req.channel_props
* @param {string} req.description
*/
setChannelDescription = async (req) => {
const talkChannel = await this.#getUserChannel(
req.mxid,
req.channel_props,
[OpenChannelUserPerm.OWNER],
"change channel description"
)
return await talkChannel.setNoticeMeta(req.description)
}
/**
* @param {Object} req
* @param {string} req.mxid
* @param {ChannelProps} req.channel_props
* @param {string} req.photo_url
*/
setChannelPhoto = async (req) => {
const talkChannel = await this.#getUserChannel(
req.mxid,
req.channel_props,
[OpenChannelUserPerm.OWNER],
"change channel photo"
)
return await talkChannel.setProfileMeta({
imageUrl: req.photo_url,
fullImageUrl: req.photo_url,
})
}
handleUnknownCommand = () => {
throw new Error("Unknown command")
}
@ -886,6 +937,9 @@ export default class PeerClient {
delete_chat: this.deleteChat,
mark_read: this.markRead,
send_perm: this.sendPerm,
set_channel_name: this.setChannelName,
set_channel_description: this.setChannelDescription,
set_channel_photo: this.setChannelPhoto,
}[req.command] || this.handleUnknownCommand
}
const resp = { id: req.id }