diff --git a/src/app.js b/src/app.js index 9ad36fa8..ff1173e7 100644 --- a/src/app.js +++ b/src/app.js @@ -82,10 +82,14 @@ class MautrixTelegram { return portal } - const entries = await this.bridge.getRoomStore().select({ + const query = { type: "portal", id: peer.id, - }) + } + if (peer.type === "user") { + query.receiverID = peer.receiverID + } + const entries = await this.bridge.getRoomStore().select(query) // Handle possible db query race conditions portal = this.portalsByPeerID.get(peer.id) diff --git a/src/commands.js b/src/commands.js index f39cedd5..fa07fe0d 100644 --- a/src/commands.js +++ b/src/commands.js @@ -18,17 +18,17 @@ const makePasswordHash = require("telegram-mtproto").plugins.makePasswordHash const commands = {} function run(sender, command, args, reply, app) { - if (sender.commandStatus) { - if (command === "cancel") { - reply(`${sender.commandStatus.action} cancelled.`) - sender.commandStatus = undefined - return - } - args.unshift(command) - return sender.commandStatus.next(sender, args, reply, app) - } command = this.commands[command] if (!command) { + if (sender.commandStatus) { + if (command === "cancel") { + reply(`${sender.commandStatus.action} cancelled.`) + sender.commandStatus = undefined + return + } + args.unshift(command) + return sender.commandStatus.next(sender, args, reply, app) + } reply("Unknown command. Try \"$cmdprefix help\" for help.") return } @@ -60,7 +60,7 @@ syncUsers - Sync user info and join status in the given portal. Same /** * Two-factor authentication handler. */ -const enterPassword = async (sender, args, reply) => { +commands.enterPassword = async (sender, args, reply) => { if (args.length === 0) { reply("Usage: $cmdprefix ") return @@ -80,7 +80,7 @@ const enterPassword = async (sender, args, reply) => { /* * Login code send handler. */ -const enterCode = async (sender, args, reply) => { +commands.enterCode = async (sender, args, reply) => { if (args.length === 0) { reply("Usage: $cmdprefix ") return @@ -95,7 +95,7 @@ const enterCode = async (sender, args, reply) => { reply(`You have two-factor authentication enabled. Password hint: ${data.hint}\nEnter your password using "$cmdprefix "`) sender.commandStatus = { action: "Two-factor authentication", - next: enterPassword, + next: commands.enterPassword, salt: data.salt, } } else { @@ -122,7 +122,7 @@ commands.login = async (sender, args, reply) => { reply(`Login code sent to ${args[0]}.\nEnter the code using "$cmdprefix "`) sender.commandStatus = { action: "Phone code authentication", - next: enterCode, + next: commands.enterCode , } } catch (err) { reply(`Failed to send code: ${err}`) diff --git a/src/portal.js b/src/portal.js index 16402649..4c829479 100644 --- a/src/portal.js +++ b/src/portal.js @@ -29,6 +29,10 @@ class Portal { return this.peer.id } + get receiverID() { + return this.peer.receiverID + } + static fromEntry(app, entry) { if (entry.type !== "portal") { throw new Error("MatrixUser can only be created from entry type \"portal\"") @@ -51,7 +55,7 @@ class Portal { } for (const userData of users) { const user = await this.app.getTelegramUser(userData.id) - await user.updateInfo(telegramPOV, userData) + await user.updateInfo(telegramPOV, userData, true) await user.intent.join(this.roomID) } return true @@ -92,11 +96,20 @@ class Portal { return undefined } - const {info, users} = await this.peer.getInfo(telegramPOV) + let title, info, users + if (this.peer.type !== "user") { + ({info, users} = await this.peer.getInfo(telegramPOV)) + title = info.title + } else { + ({info} = await this.peer.getInfo(telegramPOV)) + users = await this.app.getTelegramUser(info.id) + await users.updateInfo(telegramPOV, info) + title = users.getDisplayName() + } const room = await this.app.botIntent.createRoom({ options: { - name: info.title, + name: title, visibility: "private", } }) @@ -104,9 +117,11 @@ class Portal { this.roomID = room.room_id this.app.portalsByRoomID.set(this.roomID, this) await this.save() - - await this.syncTelegramUsers(telegramPOV, users) - + if (this.peer.type !== "user") { + await this.syncTelegramUsers(telegramPOV, users) + } else { + await users.intent.join(this.roomID) + } return this.roomID } catch (err) { console.error(err) @@ -134,6 +149,7 @@ class Portal { return { type: this.type, id: this.id, + receiverID: this.receiverID, data: { roomID: this.roomID, peer: this.peer.toSubentry(), diff --git a/src/telegram-peer.js b/src/telegram-peer.js index 447c9aeb..cc9952c0 100644 --- a/src/telegram-peer.js +++ b/src/telegram-peer.js @@ -15,20 +15,21 @@ // along with this program. If not, see . class TelegramPeer { - constructor(type, id, accessHash) { + constructor(type, id, accessHash, receiverID) { this.type = type this.id = id this.accessHash = accessHash + this.receiverID = receiverID this.username = undefined this.title = undefined } - static fromTelegramData(peer) { + static fromTelegramData(peer, receiverID) { switch(peer._) { case "peerChat": return new TelegramPeer("chat", peer.chat_id) case "peerUser": - return new TelegramPeer("user", peer.user_id, peer.access_hash) + return new TelegramPeer("user", peer.user_id, peer.access_hash, receiverID) case "peerChannel": return new TelegramPeer("channel", peer.channel_id, peer.access_hash) default: @@ -86,7 +87,11 @@ class TelegramPeer { let info, users switch(this.type) { case "user": - throw new Error("Can't get chat info of user") + info = await telegramPOV.client("users.getFullUser", { + id: this.toInputObject() + }) + users = [info.user] + info = info.user case "chat": info = await telegramPOV.client("messages.getFullChat", { chat_id: this.id, @@ -95,10 +100,10 @@ class TelegramPeer { break case "channel": info = await telegramPOV.client("channels.getFullChannel", { - channel: this.toInputChannel(), + channel: this.toInputObject(), }) const participants = await telegramPOV.client("channels.getParticipants", { - channel: this.toInputChannel(), + channel: this.toInputObject(), filter: { _: "channelParticipantsRecent" }, offset: 0, limit: 1000, @@ -138,15 +143,22 @@ class TelegramPeer { } } - toInputChannel() { - if (this.type !== "channel") { - throw new Error(`Cannot convert peer of type ${this.type} into an inputChannel`) - } - - return { - _: "inputChannel", - channel_id: this.id, - access_hash: this.accessHash, + toInputObject() { + switch(this.type) { + case "user": + return { + _: "inputUser", + user_id: this.id, + access_hash: this.accessHash, + } + case "channel": + return { + _: "inputChannel", + channel_id: this.id, + access_hash: this.accessHash, + } + default: + throw new Error(`Unrecognized type ${this.type}`) } } @@ -154,6 +166,7 @@ class TelegramPeer { const peer = new TelegramPeer(entry.type, entry.id) peer.username = entry.username peer.title = entry.title + peer.receiverID = entry.receiverID return peer } @@ -163,6 +176,7 @@ class TelegramPeer { id: this.id, username: this.username, title: this.title, + receiverID: this.receiverID, } } diff --git a/src/telegram-puppet.js b/src/telegram-puppet.js index cfac94c9..1911cef1 100644 --- a/src/telegram-puppet.js +++ b/src/telegram-puppet.js @@ -56,6 +56,7 @@ class TelegramPuppet { return value }, set: async (key, value) => { + console.warn("SET", key, "=", JSON.stringify(value)) if (this.data[key] === value) { return } @@ -64,10 +65,12 @@ class TelegramPuppet { await this.matrixUser.save() }, remove: async (...keys) => { + console.warn("DEL", JSON.stringify(value)) keys.forEach((key) => delete this.data[key]) await this.matrixUser.save() }, clear: async () => { + console.warn("CLR") this.data = {} await this.matrixUser.save() }, @@ -225,6 +228,7 @@ class TelegramPuppet { console.log("Oh noes! Empty update") return } + let peer, portal switch(update._) { case "updateUserStatus": const user = await this.app.getTelegramUser(update.user_id) @@ -237,25 +241,29 @@ class TelegramPuppet { default: status = "offline" } - user.intent.getClient().setPresence({presence: status}) + + await user.intent.getClient().setPresence({presence: status}) break case "updateUserTyping": - console.log(update.user_id, "is typing in a 1-1 chat") - break + peer = new TelegramPeer("user", update.user_id, undefined, this.userID) case "updateChatUserTyping": - console.log(update.user_id, "is typing in", update.chat_id) + peer = peer || new TelegramPeer("chat", update.chat_id) + portal = await this.app.getPortalByPeer(peer) + if (portal.isMatrixRoomCreated()) { + const sender = await this.app.getTelegramUser(update.user_id) + // The Intent API currently doesn't allow you to set the + // typing timeout. If it does, we should set it to ~5.5s as + // Telegram resends typing notifications every 5 seconds. + await sender.intent.sendTyping(portal.roomID, true/*, 5500*/) + } break case "updateShortMessage": - await this.handleMessage({ - from: update.user_id, - to: new TelegramPeer("user", update.user_id), - text: update.message, - }) - break + peer = new TelegramPeer("user", update.user_id, undefined, this.userID) case "updateShortChatMessage": + peer = peer || new TelegramPeer("chat", update.chat_id) await this.handleMessage({ from: update.user_id, - to: new TelegramPeer("chat", update.chat_id), + to: peer, text: update.message, }) break @@ -265,7 +273,7 @@ class TelegramPuppet { update = update.message // Message defined at message#90dddc11 in layer 71 await this.handleMessage({ from: update.from_id, - to: TelegramPeer.fromTelegramData(update.to_id), + to: TelegramPeer.fromTelegramData(update.to_id, this.userID), text: update.message, }) break @@ -275,6 +283,7 @@ class TelegramPuppet { } handleUpdate(data) { + console.log("UPDATE", data) try { switch (data._) { case "updateShort": diff --git a/src/telegram-user.js b/src/telegram-user.js index ae8e520c..b707d56b 100644 --- a/src/telegram-user.js +++ b/src/telegram-user.js @@ -88,10 +88,8 @@ class TelegramUser { const userInfo = await this.intent.getProfileInfo(this.mxid, "displayname") if (userInfo.displayname !== this.getDisplayName()) { - console.log(userInfo.displayname) this.intent.setDisplayName( this.app.config.bridge.displayname_template.replace("${DISPLAYNAME}", this.getDisplayName())) - console.log((await this.intent.getProfileInfo(this.mxid, "displayname")).displayname) } if (!dontUpdateAvatar && this.updateAvatarImageFrom(telegramPOV, user)) { changed = true