stuff for multi person chats
This commit is contained in:
parent
a43502b45e
commit
328167c5f2
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
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
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -331,7 +337,6 @@ class MautrixController {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_observeChatListMutations(mutations) {
|
_observeChatListMutations(mutations) {
|
||||||
/* TODO
|
|
||||||
const changedChatIDs = new Set()
|
const changedChatIDs = new Set()
|
||||||
for (const change of mutations) {
|
for (const change of mutations) {
|
||||||
console.debug("Chat list mutation:", change)
|
console.debug("Chat list mutation:", change)
|
||||||
|
@ -350,7 +355,6 @@ class MautrixController {
|
||||||
() => console.debug("Chat list mutations dispatched"),
|
() => console.debug("Chat list mutations dispatched"),
|
||||||
err => console.error("Error dispatching chat list mutations:", err))
|
err => console.error("Error dispatching chat list mutations:", err))
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -95,10 +95,8 @@ export default class MessagesPuppeteer {
|
||||||
await this.page.exposeFunction("__mautrixExpiry", this._receiveExpiry.bind(this))
|
await this.page.exposeFunction("__mautrixExpiry", this._receiveExpiry.bind(this))
|
||||||
await this.page.exposeFunction("__mautrixReceiveMessageID",
|
await this.page.exposeFunction("__mautrixReceiveMessageID",
|
||||||
id => this.sentMessageIDs.add(id))
|
id => this.sentMessageIDs.add(id))
|
||||||
/* TODO
|
|
||||||
await this.page.exposeFunction("__mautrixReceiveChanges",
|
await this.page.exposeFunction("__mautrixReceiveChanges",
|
||||||
this._receiveChatListChanges.bind(this))
|
this._receiveChatListChanges.bind(this))
|
||||||
*/
|
|
||||||
await this.page.exposeFunction("__chronoParseDate", chrono.parseDate)
|
await this.page.exposeFunction("__chronoParseDate", chrono.parseDate)
|
||||||
|
|
||||||
// NOTE Must *always* re-login on a browser session, so no need to check if already logged in
|
// NOTE Must *always* re-login on a browser session, so no need to check if already logged in
|
||||||
|
@ -509,6 +507,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)
|
||||||
|
@ -535,6 +534,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) {
|
||||||
|
|
Loading…
Reference in New Issue