stuff for multi person chats

This commit is contained in:
Andrew Ferrazzutti 2021-02-25 22:21:11 -05:00
parent b25bac8cea
commit 6766ef5d55
5 changed files with 69 additions and 27 deletions

View File

@ -58,6 +58,7 @@ class Config(BaseBridgeConfig):
copy("bridge.displayname_max_length") copy("bridge.displayname_max_length")
copy("bridge.initial_conversation_sync") copy("bridge.initial_conversation_sync")
copy("bridge.invite_own_puppet_to_pm")
copy("bridge.login_shared_secret") copy("bridge.login_shared_secret")
copy("bridge.federate_rooms") copy("bridge.federate_rooms")
copy("bridge.backfill.invite_own_puppet") copy("bridge.backfill.invite_own_puppet")

View File

@ -49,6 +49,7 @@ ReuploadedMediaInfo = NamedTuple('ReuploadedMediaInfo', mxc=Optional[ContentURI]
class Portal(DBPortal, BasePortal): class Portal(DBPortal, BasePortal):
invite_own_puppet_to_pm: bool = False
by_mxid: Dict[RoomID, 'Portal'] = {} by_mxid: Dict[RoomID, 'Portal'] = {}
by_chat_id: Dict[int, 'Portal'] = {} by_chat_id: Dict[int, 'Portal'] = {}
config: Config config: Config
@ -77,7 +78,15 @@ class Portal(DBPortal, BasePortal):
@property @property
def is_direct(self) -> bool: def is_direct(self) -> bool:
return self.other_user is not None return self.chat_id[0] == "u"
@property
def is_group(self) -> bool:
return self.chat_id[0] == "c"
@property
def is_room(self) -> bool:
return self.chat_id[0] == "r"
@property @property
def main_intent(self) -> IntentAPI: def main_intent(self) -> IntentAPI:
@ -92,6 +101,7 @@ class Portal(DBPortal, BasePortal):
cls.az = bridge.az cls.az = bridge.az
cls.loop = bridge.loop cls.loop = bridge.loop
cls.bridge = bridge cls.bridge = bridge
cls.invite_own_puppet_to_pm = cls.config["bridge.invite_own_puppet_to_pm"]
NotificationDisabler.puppet_cls = p.Puppet NotificationDisabler.puppet_cls = p.Puppet
NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"] NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"]
@ -145,36 +155,54 @@ class Portal(DBPortal, BasePortal):
self.log.debug(f"{user.mxid} left portal to {self.chat_id}") self.log.debug(f"{user.mxid} left portal to {self.chat_id}")
# TODO cleanup if empty # TODO cleanup if empty
async def handle_remote_message(self, source: 'u.User', evt: Message) -> None: async def _bridge_own_message_pm(self, source: 'u.User', sender: 'p.Puppet', mid: str,
if evt.is_outgoing: invite: bool = True) -> bool:
if self.is_direct and sender.fbid == source.fbid and not sender.is_real_user:
if self.invite_own_puppet_to_pm and invite:
await self.main_intent.invite_user(self.mxid, sender.mxid)
elif await self.az.state_store.get_membership(self.mxid,
sender.mxid) != Membership.JOIN:
self.log.warning(f"Ignoring own {mid} in private chat because own puppet is not in"
" room.")
return False
return True
async def handle_remote_message(self, source: 'u.User', message: Message) -> None:
# TODO
if message.is_outgoing:
if not source.intent: if not source.intent:
self.log.warning(f"Ignoring message {evt.id}: double puppeting isn't enabled") if not await self._bridge_own_message_pm(source, sender, mid, invite):
return return
intent = source.intent if not self.invite_own_puppet_to_pm:
self.log.warning(f"Ignoring message {message.id}: double puppeting isn't enabled")
return
intent = self.main_intent
else:
intent = source.intent
elif self.other_user: elif self.other_user:
intent = (await p.Puppet.get_by_mid(self.other_user)).intent intent = (await p.Puppet.get_by_mid(self.other_user)).intent
else: else:
# TODO group chats # TODO group chats
self.log.warning(f"Ignoring message {evt.id}: group chats aren't supported yet") self.log.warning(f"Ignoring message {message.id}: group chats aren't supported yet")
return return
if await DBMessage.get_by_mid(evt.id): if await DBMessage.get_by_mid(message.id):
self.log.debug(f"Ignoring duplicate message {evt.id}") self.log.debug(f"Ignoring duplicate message {message.id}")
return return
event_id = None event_id = None
if evt.image: if message.image:
content = await self._handle_remote_photo(source, intent, evt) content = await self._handle_remote_photo(source, intent, message)
if content: if content:
event_id = await self._send_message(intent, content, timestamp=evt.timestamp) event_id = await self._send_message(intent, content, timestamp=message.timestamp)
if evt.text and not evt.text.isspace(): if message.text and not message.text.isspace():
content = TextMessageEventContent(msgtype=MessageType.TEXT, body=evt.text) content = TextMessageEventContent(msgtype=MessageType.TEXT, body=message.text)
event_id = await self._send_message(intent, content, timestamp=evt.timestamp) event_id = await self._send_message(intent, content, timestamp=message.timestamp)
if event_id: if event_id:
msg = DBMessage(mxid=event_id, mx_room=self.mxid, mid=evt.id, chat_id=self.chat_id) msg = DBMessage(mxid=event_id, mx_room=self.mxid, mid=message.id, chat_id=self.chat_id)
await msg.insert() await msg.insert()
await self._send_delivery_receipt(event_id) await self._send_delivery_receipt(event_id)
self.log.debug(f"Handled remote message {evt.id} -> {event_id}") self.log.debug(f"Handled remote message {message.id} -> {event_id}")
async def _handle_remote_photo(self, source: 'u.User', intent: IntentAPI, message: Message async def _handle_remote_photo(self, source: 'u.User', intent: IntentAPI, message: Message
) -> Optional[MediaMessageEventContent]: ) -> Optional[MediaMessageEventContent]:
@ -200,15 +228,16 @@ class Portal(DBPortal, BasePortal):
return ReuploadedMediaInfo(mxc, decryption_info, mime_type, file_name, len(data)) return ReuploadedMediaInfo(mxc, decryption_info, mime_type, file_name, len(data))
async def update_info(self, conv: ChatInfo) -> None: async def update_info(self, conv: ChatInfo) -> None:
# TODO Not true: a single-participant chat could be a group! if self.is_direct:
if len(conv.participants) == 1:
self.other_user = conv.participants[0].id self.other_user = conv.participants[0].id
if self._main_intent is self.az.intent: if self._main_intent is self.az.intent:
self._main_intent = (await p.Puppet.get_by_mid(self.other_user)).intent self._main_intent = (await p.Puppet.get_by_mid(self.other_user)).intent
for participant in conv.participants: for participant in conv.participants:
puppet = await p.Puppet.get_by_mid(participant.id) puppet = await p.Puppet.get_by_mid(participant.id)
await puppet.update_info(participant) await puppet.update_info(participant)
changed = await self._update_name(conv.name) # TODO Consider setting no room name for non-group chats.
# But then the LINE bot itself may appear in the title...
changed = await self._update_name(f"{conv.name} (LINE)")
if changed: if changed:
await self.update_bridge_info() await self.update_bridge_info()
await self.update() await self.update()
@ -267,8 +296,10 @@ class Portal(DBPortal, BasePortal):
self.log.debug("Got %d messages from server", len(messages)) self.log.debug("Got %d messages from server", len(messages))
async with NotificationDisabler(self.mxid, source): async with NotificationDisabler(self.mxid, source):
for evt in messages: for message in messages:
await self.handle_remote_message(source, evt) # TODO
#puppet = await p.Puppet.get_by_mid(message.)
await self.handle_remote_message(source, message)
self.log.info("Backfilled %d messages through %s", len(messages), source.mxid) self.log.info("Backfilled %d messages through %s", len(messages), source.mxid)
@property @property

View File

@ -48,6 +48,8 @@ class Message(SerializableAttrs['Message']):
id: int id: int
chat_id: int chat_id: int
is_outgoing: bool is_outgoing: bool
# TODO
sender: Optional[str]
timestamp: int = None timestamp: int = None
text: Optional[str] = None text: Optional[str] = None
image: Optional[str] = None image: Optional[str] = None

View File

@ -104,6 +104,7 @@ class MautrixController {
* @property {number} id - The ID of the message. Seems to be sequential. * @property {number} id - The ID of the message. Seems to be sequential.
* @property {number} timestamp - The unix timestamp of the message. Not very accurate. * @property {number} timestamp - The unix timestamp of the message. Not very accurate.
* @property {boolean} is_outgoing - Whether or not this user sent the message. * @property {boolean} is_outgoing - Whether or not this user sent the message.
* @property {null|string} sender - The ID of the user who sent the message, or null if outgoing.
* @property {string} [text] - The text in the message. * @property {string} [text] - The text in the message.
* @property {string} [image] - The URL to the image in the message. * @property {string} [image] - The URL to the image in the message.
*/ */
@ -117,10 +118,17 @@ class MautrixController {
* @private * @private
*/ */
_tryParseMessage(date, element) { _tryParseMessage(date, element) {
const is_outgoing: element.classList.contains("mdRGT07Own")
const messageData = { const messageData = {
id: +element.getAttribute("data-local-id"), id: +element.getAttribute("data-local-id"),
timestamp: date ? date.getTime() : null, timestamp: date ? date.getTime() : null,
is_outgoing: element.classList.contains("mdRGT07Own"), is_outgoing: is_outgoing,
// TODO The sender's mid isn't available, so must validate their display name somehow...
// Get it from contact list? It's always available in "#contact_wrap_friends > ul"
// <li .mdMN02Li data-mid, title=name>
// But what about non-friends?
// TODO Also, sender is always there, but not actually needed for DMs
sender: !is_outgoing ? element.querySelector(".mdRGT07Body > .mdRGT07Ttl") : null
} }
const messageElement = element.querySelector(".mdRGT07Body > .mdRGT07Msg") const messageElement = element.querySelector(".mdRGT07Body > .mdRGT07Msg")
if (messageElement.classList.contains("mdRGT07Text")) { if (messageElement.classList.contains("mdRGT07Text")) {
@ -226,10 +234,8 @@ class MautrixController {
* @return {[Participant]} - The list of participants. * @return {[Participant]} - The list of participants.
*/ */
parseParticipantList(element) { parseParticipantList(element) {
// TODO Slice to exclude first member, which is always yourself (right?) // TODO The first member is always yourself, right?
// TODO Only slice if double-puppeting is enabled! return Array.from(element.children).slice(1).map(child => {
//return Array.from(element.children).slice(1).map(child => {
return Array.from(element.children).map(child => {
return { return {
id: child.getAttribute("data-mid"), id: child.getAttribute("data-mid"),
// TODO avatar: child.querySelector("img").src, // TODO avatar: child.querySelector("img").src,

View File

@ -508,6 +508,7 @@ export default class MessagesPuppeteer {
return messages.filter(msg => msg.id > minID && !this.sentMessageIDs.has(msg.id)) return messages.filter(msg => msg.id > minID && !this.sentMessageIDs.has(msg.id))
} }
// TODO
async _processChatListChangeUnsafe(id) { async _processChatListChangeUnsafe(id) {
this.updatedChats.delete(id) this.updatedChats.delete(id)
this.log("Processing change to", id) this.log("Processing change to", id)
@ -534,6 +535,7 @@ export default class MessagesPuppeteer {
} }
} }
// TODO
_receiveChatListChanges(changes) { _receiveChatListChanges(changes) {
this.log("Received chat list changes:", changes) this.log("Received chat list changes:", changes)
for (const item of changes) { for (const item of changes) {