Inbound channel photo & description
This commit is contained in:
parent
d843fcf5d2
commit
a12efc92c4
|
@ -38,10 +38,12 @@ class Portal:
|
||||||
kt_type: ChannelType
|
kt_type: ChannelType
|
||||||
mxid: RoomID | None
|
mxid: RoomID | None
|
||||||
name: str | None
|
name: str | None
|
||||||
|
description: str | None
|
||||||
photo_id: str | None
|
photo_id: str | None
|
||||||
avatar_url: ContentURI | None
|
avatar_url: ContentURI | None
|
||||||
encrypted: bool
|
encrypted: bool
|
||||||
name_set: bool
|
name_set: bool
|
||||||
|
topic_set: bool
|
||||||
avatar_set: bool
|
avatar_set: bool
|
||||||
relay_user_id: UserID | None
|
relay_user_id: UserID | None
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ class Portal:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_by_ktid(cls, ktid: int, kt_receiver: int) -> Portal | None:
|
async def get_by_ktid(cls, ktid: int, kt_receiver: int) -> Portal | None:
|
||||||
q = """
|
q = """
|
||||||
SELECT ktid, kt_receiver, kt_type, mxid, name, photo_id, avatar_url, encrypted,
|
SELECT ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url, encrypted,
|
||||||
name_set, avatar_set, relay_user_id
|
name_set, avatar_set, relay_user_id
|
||||||
FROM portal WHERE ktid=$1 AND kt_receiver=$2
|
FROM portal WHERE ktid=$1 AND kt_receiver=$2
|
||||||
"""
|
"""
|
||||||
|
@ -66,7 +68,7 @@ class Portal:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_by_mxid(cls, mxid: RoomID) -> Portal | None:
|
async def get_by_mxid(cls, mxid: RoomID) -> Portal | None:
|
||||||
q = """
|
q = """
|
||||||
SELECT ktid, kt_receiver, kt_type, mxid, name, photo_id, avatar_url, encrypted,
|
SELECT ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url, encrypted,
|
||||||
name_set, avatar_set, relay_user_id
|
name_set, avatar_set, relay_user_id
|
||||||
FROM portal WHERE mxid=$1
|
FROM portal WHERE mxid=$1
|
||||||
"""
|
"""
|
||||||
|
@ -76,7 +78,7 @@ class Portal:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_all_by_receiver(cls, kt_receiver: int) -> list[Portal]:
|
async def get_all_by_receiver(cls, kt_receiver: int) -> list[Portal]:
|
||||||
q = """
|
q = """
|
||||||
SELECT ktid, kt_receiver, kt_type, mxid, name, photo_id, avatar_url, encrypted,
|
SELECT ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url, encrypted,
|
||||||
name_set, avatar_set, relay_user_id
|
name_set, avatar_set, relay_user_id
|
||||||
FROM portal WHERE kt_receiver=$1
|
FROM portal WHERE kt_receiver=$1
|
||||||
"""
|
"""
|
||||||
|
@ -86,7 +88,7 @@ class Portal:
|
||||||
@classmethod
|
@classmethod
|
||||||
async def all(cls) -> list[Portal]:
|
async def all(cls) -> list[Portal]:
|
||||||
q = """
|
q = """
|
||||||
SELECT ktid, kt_receiver, kt_type, mxid, name, photo_id, avatar_url, encrypted,
|
SELECT ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url, encrypted,
|
||||||
name_set, avatar_set, relay_user_id
|
name_set, avatar_set, relay_user_id
|
||||||
FROM portal
|
FROM portal
|
||||||
"""
|
"""
|
||||||
|
@ -101,6 +103,7 @@ class Portal:
|
||||||
self.kt_type,
|
self.kt_type,
|
||||||
self.mxid,
|
self.mxid,
|
||||||
self.name,
|
self.name,
|
||||||
|
self.description,
|
||||||
self.photo_id,
|
self.photo_id,
|
||||||
self.avatar_url,
|
self.avatar_url,
|
||||||
self.encrypted,
|
self.encrypted,
|
||||||
|
@ -111,9 +114,9 @@ class Portal:
|
||||||
|
|
||||||
async def insert(self) -> None:
|
async def insert(self) -> None:
|
||||||
q = """
|
q = """
|
||||||
INSERT INTO portal (ktid, kt_receiver, kt_type, mxid, name, photo_id, avatar_url,
|
INSERT INTO portal (ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url,
|
||||||
encrypted, name_set, avatar_set, relay_user_id)
|
encrypted, name_set, avatar_set, relay_user_id)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
"""
|
"""
|
||||||
await self.db.execute(q, *self._values)
|
await self.db.execute(q, *self._values)
|
||||||
|
|
||||||
|
@ -123,8 +126,8 @@ class Portal:
|
||||||
|
|
||||||
async def save(self) -> None:
|
async def save(self) -> None:
|
||||||
q = """
|
q = """
|
||||||
UPDATE portal SET kt_type=$3, mxid=$4, name=$5, photo_id=$6, avatar_url=$7,
|
UPDATE portal SET kt_type=$3, mxid=$4, name=$5, description=$6, photo_id=$7, avatar_url=$8,
|
||||||
encrypted=$8, name_set=$9, avatar_set=$10, relay_user_id=$11
|
encrypted=$9, name_set=$10, avatar_set=$11, relay_user_id=$12
|
||||||
WHERE ktid=$1 AND kt_receiver=$2
|
WHERE ktid=$1 AND kt_receiver=$2
|
||||||
"""
|
"""
|
||||||
await self.db.execute(q, *self._values)
|
await self.db.execute(q, *self._values)
|
||||||
|
|
|
@ -3,3 +3,4 @@ from mautrix.util.async_db import UpgradeTable
|
||||||
upgrade_table = UpgradeTable()
|
upgrade_table = UpgradeTable()
|
||||||
|
|
||||||
from . import v01_initial_revision
|
from . import v01_initial_revision
|
||||||
|
from . import v02_channel_meta
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
||||||
|
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from mautrix.util.async_db import Connection
|
||||||
|
|
||||||
|
from . import upgrade_table
|
||||||
|
|
||||||
|
|
||||||
|
@upgrade_table.register(description="Support channel descriptions")
|
||||||
|
async def upgrade_v2(conn: Connection) -> None:
|
||||||
|
await conn.execute("ALTER TABLE portal ADD COLUMN description TEXT")
|
||||||
|
await conn.execute("ALTER TABLE portal ADD COLUMN topic_set BOOLEAN NOT NULL DEFAULT false")
|
|
@ -500,6 +500,13 @@ class Client:
|
||||||
str(data["channelType"]),
|
str(data["channelType"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _on_channel_meta_change(self, data: dict[str, JSON]) -> Awaitable[None]:
|
||||||
|
return self.user.on_channel_meta_change(
|
||||||
|
PortalChannelInfo.deserialize(data["info"]),
|
||||||
|
Long.deserialize(data["channelId"]),
|
||||||
|
str(data["channelType"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _on_listen_disconnect(self, data: dict[str, JSON]) -> Awaitable[None]:
|
def _on_listen_disconnect(self, data: dict[str, JSON]) -> Awaitable[None]:
|
||||||
try:
|
try:
|
||||||
|
@ -532,6 +539,7 @@ class Client:
|
||||||
self._add_event_handler("channel_kicked", self._on_channel_kicked)
|
self._add_event_handler("channel_kicked", self._on_channel_kicked)
|
||||||
self._add_event_handler("user_join", self._on_user_join)
|
self._add_event_handler("user_join", self._on_user_join)
|
||||||
self._add_event_handler("user_left", self._on_user_left)
|
self._add_event_handler("user_left", self._on_user_left)
|
||||||
|
self._add_event_handler("channel_meta_change", self._on_channel_meta_change)
|
||||||
self._add_event_handler("disconnected", self._on_listen_disconnect)
|
self._add_event_handler("disconnected", self._on_listen_disconnect)
|
||||||
self._add_event_handler("switch_server", self._on_switch_server)
|
self._add_event_handler("switch_server", self._on_switch_server)
|
||||||
self._add_event_handler("error", self._on_error)
|
self._add_event_handler("error", self._on_error)
|
||||||
|
|
|
@ -69,8 +69,9 @@ setattr(UserInfoUnion, "deserialize", deserialize_user_info_union)
|
||||||
@dataclass
|
@dataclass
|
||||||
class PortalChannelInfo(SerializableAttrs):
|
class PortalChannelInfo(SerializableAttrs):
|
||||||
name: str
|
name: str
|
||||||
participants: list[UserInfoUnion]
|
description: Optional[str] = None
|
||||||
# TODO Image
|
photoURL: Optional[str] = None
|
||||||
|
participants: Optional[list[UserInfoUnion]] = None # May set to None to skip participant update
|
||||||
channel_info: Optional[ChannelInfoUnion] = None # Should be set manually by caller
|
channel_info: Optional[ChannelInfoUnion] = None # Should be set manually by caller
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -146,10 +146,12 @@ class Portal(DBPortal, BasePortal):
|
||||||
kt_type: ChannelType,
|
kt_type: ChannelType,
|
||||||
mxid: RoomID | None = None,
|
mxid: RoomID | None = None,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
photo_id: str | None = None,
|
photo_id: str | None = None,
|
||||||
avatar_url: ContentURI | None = None,
|
avatar_url: ContentURI | None = None,
|
||||||
encrypted: bool = False,
|
encrypted: bool = False,
|
||||||
name_set: bool = False,
|
name_set: bool = False,
|
||||||
|
topic_set: bool = False,
|
||||||
avatar_set: bool = False,
|
avatar_set: bool = False,
|
||||||
relay_user_id: UserID | None = None,
|
relay_user_id: UserID | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -159,10 +161,12 @@ class Portal(DBPortal, BasePortal):
|
||||||
kt_type,
|
kt_type,
|
||||||
mxid,
|
mxid,
|
||||||
name,
|
name,
|
||||||
|
description,
|
||||||
photo_id,
|
photo_id,
|
||||||
avatar_url,
|
avatar_url,
|
||||||
encrypted,
|
encrypted,
|
||||||
name_set,
|
name_set,
|
||||||
|
topic_set,
|
||||||
avatar_set,
|
avatar_set,
|
||||||
relay_user_id,
|
relay_user_id,
|
||||||
)
|
)
|
||||||
|
@ -317,17 +321,18 @@ class Portal(DBPortal, BasePortal):
|
||||||
changed = any(
|
changed = any(
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
self._update_name(info.name),
|
self._update_name(info.name),
|
||||||
# TODO
|
self._update_description(info.description),
|
||||||
#self._update_photo(source, info.image),
|
self._update_photo(source, info.photoURL),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
changed = await self._update_participants(source, info.participants) or changed
|
if info.participants is not None:
|
||||||
|
changed = await self._update_participants(source, info.participants) or changed
|
||||||
|
if self.mxid and self.is_open:
|
||||||
|
user_power_levels = await self._get_mapped_participant_power_levels(info.participants, skip_default=False)
|
||||||
|
asyncio.create_task(self.set_power_levels(user_power_levels))
|
||||||
if changed or force_save:
|
if changed or force_save:
|
||||||
await self.update_bridge_info()
|
await self.update_bridge_info()
|
||||||
await self.save()
|
await self.save()
|
||||||
if self.mxid and self.is_open:
|
|
||||||
user_power_levels = await self._get_mapped_participant_power_levels(info.participants, skip_default=False)
|
|
||||||
await self.set_power_levels(user_power_levels)
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
async def _get_mapped_participant_power_levels(self, participants: list[UserInfoUnion], skip_default: bool) -> dict[UserID, int]:
|
async def _get_mapped_participant_power_levels(self, participants: list[UserInfoUnion], skip_default: bool) -> dict[UserID, int]:
|
||||||
|
@ -434,23 +439,37 @@ class Portal(DBPortal, BasePortal):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
"""
|
async def _update_description(self, description: str | None) -> bool:
|
||||||
async def _update_photo(self, source: u.User, photo: graphql.Picture) -> bool:
|
if self.description != description or not self.topic_set:
|
||||||
|
self.log.trace("Updating description %s -> %s", self.description, description)
|
||||||
|
self.description = description
|
||||||
|
if self.mxid and (self.encrypted or not self.is_direct):
|
||||||
|
try:
|
||||||
|
await self.main_intent.set_room_topic(self.mxid, self.description)
|
||||||
|
self.topic_set = True
|
||||||
|
except Exception:
|
||||||
|
self.log.exception("Failed to set room description")
|
||||||
|
self.topic_set = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _update_photo(self, source: u.User, photo_id: str | None) -> bool:
|
||||||
if self.is_direct and not self.encrypted:
|
if self.is_direct and not self.encrypted:
|
||||||
return False
|
return False
|
||||||
photo_id = self.get_photo_id(photo)
|
if self.photo_id is not None and photo_id is None:
|
||||||
|
self.log.warning("Portal previously had a photo_id, but new photo_id is None. Leaving it as it is")
|
||||||
|
return False
|
||||||
if self.photo_id != photo_id or not self.avatar_set:
|
if self.photo_id != photo_id or not self.avatar_set:
|
||||||
self.photo_id = photo_id
|
self.photo_id = photo_id
|
||||||
if photo:
|
if photo_id:
|
||||||
if self.photo_id != photo_id or not self.avatar_url:
|
if self.photo_id != photo_id or not self.avatar_url:
|
||||||
# Reset avatar_url first in case the upload fails
|
# Reset avatar_url first in case the upload fails
|
||||||
self.avatar_url = None
|
self.avatar_url = None
|
||||||
self.avatar_url = await p.Puppet.reupload_avatar(
|
self.avatar_url = await p.Puppet.reupload_avatar(
|
||||||
source,
|
source,
|
||||||
self.main_intent,
|
self.main_intent,
|
||||||
photo.uri,
|
photo_id,
|
||||||
self.ktid,
|
self.ktid,
|
||||||
use_graph=self.is_direct and (photo.height or 0) < 500,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.avatar_url = ContentURI("")
|
self.avatar_url = ContentURI("")
|
||||||
|
@ -463,7 +482,6 @@ class Portal(DBPortal, BasePortal):
|
||||||
self.avatar_set = False
|
self.avatar_set = False
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
"""
|
|
||||||
|
|
||||||
async def _update_photo_from_puppet(self, puppet: p.Puppet) -> bool:
|
async def _update_photo_from_puppet(self, puppet: p.Puppet) -> bool:
|
||||||
if self.photo_id == puppet.photo_id and self.avatar_set:
|
if self.photo_id == puppet.photo_id and self.avatar_set:
|
||||||
|
|
|
@ -39,7 +39,7 @@ from .db import User as DBUser
|
||||||
|
|
||||||
from .kt.client import Client
|
from .kt.client import Client
|
||||||
from .kt.client.errors import AuthenticationRequired, ResponseError
|
from .kt.client.errors import AuthenticationRequired, ResponseError
|
||||||
from .kt.client.types import SettingsStruct, FROM_PERM_MAP
|
from .kt.client.types import PortalChannelInfo, SettingsStruct, FROM_PERM_MAP
|
||||||
from .kt.types.bson import Long
|
from .kt.types.bson import Long
|
||||||
from .kt.types.channel.channel_info import ChannelInfo, NormalChannelInfo, NormalChannelData
|
from .kt.types.channel.channel_info import ChannelInfo, NormalChannelInfo, NormalChannelData
|
||||||
from .kt.types.channel.channel_type import ChannelType, KnownChannelType
|
from .kt.types.channel.channel_type import ChannelType, KnownChannelType
|
||||||
|
@ -55,6 +55,7 @@ METRIC_CONNECT_AND_SYNC = Summary("bridge_connect_and_sync", "calls to connect_a
|
||||||
METRIC_CHAT = Summary("bridge_on_chat", "calls to on_chat")
|
METRIC_CHAT = Summary("bridge_on_chat", "calls to on_chat")
|
||||||
METRIC_CHAT_DELETED = Summary("bridge_on_chat_deleted", "calls to on_chat_deleted")
|
METRIC_CHAT_DELETED = Summary("bridge_on_chat_deleted", "calls to on_chat_deleted")
|
||||||
METRIC_CHAT_READ = Summary("bridge_on_chat_read", "calls to on_chat_read")
|
METRIC_CHAT_READ = Summary("bridge_on_chat_read", "calls to on_chat_read")
|
||||||
|
METRIC_CHANNEL_META_CHANGE = Summary("bridge_on_channel_meta_change", "calls to on_channel_meta_change")
|
||||||
METRIC_PROFILE_CHANGE = Summary("bridge_on_profile_changed", "calls to on_profile_changed")
|
METRIC_PROFILE_CHANGE = Summary("bridge_on_profile_changed", "calls to on_profile_changed")
|
||||||
METRIC_PERM_CHANGE = Summary("bridge_on_perm_changed", "calls to on_perm_changed")
|
METRIC_PERM_CHANGE = Summary("bridge_on_perm_changed", "calls to on_perm_changed")
|
||||||
METRIC_CHANNEL_JOIN = Summary("bridge_on_channel_join", "calls to on_channel_join")
|
METRIC_CHANNEL_JOIN = Summary("bridge_on_channel_join", "calls to on_channel_join")
|
||||||
|
@ -741,6 +742,21 @@ class User(DBUser, BaseUser):
|
||||||
await portal.backfill_lock.wait(f"read receipt from {sender_id}")
|
await portal.backfill_lock.wait(f"read receipt from {sender_id}")
|
||||||
await portal.handle_kakaotalk_chat_read(self, puppet, chat_id)
|
await portal.handle_kakaotalk_chat_read(self, puppet, chat_id)
|
||||||
|
|
||||||
|
@async_time(METRIC_CHANNEL_META_CHANGE)
|
||||||
|
async def on_channel_meta_change(
|
||||||
|
self,
|
||||||
|
info: PortalChannelInfo,
|
||||||
|
channel_id: Long,
|
||||||
|
channel_type: ChannelType,
|
||||||
|
) -> None:
|
||||||
|
portal = await po.Portal.get_by_ktid(
|
||||||
|
channel_id,
|
||||||
|
kt_receiver=self.ktid,
|
||||||
|
kt_type=channel_type,
|
||||||
|
)
|
||||||
|
if portal:
|
||||||
|
await portal.update_info(self, info)
|
||||||
|
|
||||||
@async_time(METRIC_PROFILE_CHANGE)
|
@async_time(METRIC_PROFILE_CHANGE)
|
||||||
async def on_profile_changed(self, info: OpenLinkChannelUserInfo) -> None:
|
async def on_profile_changed(self, info: OpenLinkChannelUserInfo) -> None:
|
||||||
puppet = await pu.Puppet.get_by_ktid(info.userId)
|
puppet = await pu.Puppet.get_by_ktid(info.userId)
|
||||||
|
|
|
@ -191,6 +191,33 @@ class UserClient {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.#talkClient.on("meta_change", (channel, type, newMeta) => {
|
||||||
|
this.log(`Channel ${channel.channelId} metadata changed`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.#talkClient.on("push_packet", (method, data) => {
|
||||||
|
// TODO Find a better way to do this...but data doesn't have much.
|
||||||
|
if (method == "SYNCLINKUP") {
|
||||||
|
if (!data?.ol) return
|
||||||
|
const linkURL = data.ol?.lu
|
||||||
|
if (!linkURL) return
|
||||||
|
for (const channel of this.#talkClient.channelList.open.all()) {
|
||||||
|
if (channel.info.openLink?.linkURL == linkURL) {
|
||||||
|
this.write("channel_meta_change", {
|
||||||
|
info: {
|
||||||
|
name: data.ol?.ln,
|
||||||
|
description: data.ol?.desc || null,
|
||||||
|
photoURL: data.ol?.liu || null,
|
||||||
|
},
|
||||||
|
channelId: channel.channelId,
|
||||||
|
channelType: channel.info.type,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.#talkClient.on("disconnected", (reason) => {
|
this.#talkClient.on("disconnected", (reason) => {
|
||||||
this.log(`Disconnected (reason=${reason})`)
|
this.log(`Disconnected (reason=${reason})`)
|
||||||
this.disconnect()
|
this.disconnect()
|
||||||
|
@ -568,8 +595,10 @@ export default class PeerClient {
|
||||||
|
|
||||||
return makeCommandResult({
|
return makeCommandResult({
|
||||||
name: talkChannel.getDisplayName(),
|
name: talkChannel.getDisplayName(),
|
||||||
|
description: talkChannel.info.openLink?.description,
|
||||||
|
// TODO Find out why linkCoverURL is blank, despite having updated the channel!
|
||||||
|
photoURL: talkChannel.info.openLink?.linkCoverURL || null,
|
||||||
participants: Array.from(talkChannel.getAllUserInfo()),
|
participants: Array.from(talkChannel.getAllUserInfo()),
|
||||||
// TODO Image
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue