From 9d705bd5c89605ef235b29f695d0c83ffb1d36c5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 16 Nov 2017 11:54:01 +0200 Subject: [PATCH] Implement room and user creation --- src/commands.js | 28 +++++++++++++++++++++----- src/matrix-user.js | 7 ++----- src/portal.js | 36 ++++++++++++++++++++++----------- src/telegram-peer.js | 45 +++++++++++++++++++++++++++++------------- src/telegram-puppet.js | 31 +++++++++++++++++++++++++++++ src/telegram-user.js | 37 +++++++++++++++++++++++----------- 6 files changed, 137 insertions(+), 47 deletions(-) diff --git a/src/commands.js b/src/commands.js index 98ba8faa..5a5ef6f3 100644 --- a/src/commands.js +++ b/src/commands.js @@ -135,11 +135,29 @@ commands.logout = async (sender, args, reply) => { const TelegramPeer = require("./telegram-peer") const Portal = require("./portal") -commands.test = async (sender, args, reply, app) => { - const peer = new TelegramPeer(args[0], +args[1]) - const hashFound = await peer.getAccessHash(app, sender.telegramPuppet) - reply("Access hash found: " + hashFound) - new Portal(app, "", peer).createMatrixRoom(sender.telegramPuppet) +commands.createRoom = async (sender, args, reply, app) => { + let peer = new TelegramPeer(args[0], +args[1]) + const portal = await app.getPortalByPeer(peer) + const roomID = await portal.createMatrixRoom(sender.telegramPuppet) + if (!roomID) { + reply("Failed to create room.") + return + } + await app.botIntent.invite(roomID, sender.userID) + reply(`Created room ${roomID} and invited ${sender.userID}`) +} + +commands.syncUsers = async (sender, args, reply, app) => { + let peer = new TelegramPeer(args[0], +args[1]) + const portal = await app.getPortalByPeer(peer) + try { + await portal.syncTelegramUsers(sender.telegramPuppet) + reply("Users synchronized successfully.") + } catch (err) { + reply(`Failed to sync users: ${err}`) + console.error(err) + console.error(err.stack) + } } ////////////////////////////// diff --git a/src/matrix-user.js b/src/matrix-user.js index 284939e3..252a76ac 100644 --- a/src/matrix-user.js +++ b/src/matrix-user.js @@ -104,9 +104,7 @@ class MatrixUser { } for (const [index, contact] of Object.entries(contacts.users)) { const telegramUser = await this.app.getTelegramUser(contact.id) - if (telegramUser.updateInfo(this.telegramPuppet, contact)) { - telegramUser.save() - } + await telegramUser.updateInfo(this.telegramPuppet, contact) contacts.users[index] = telegramUser } this.contacts = contacts.users @@ -123,8 +121,7 @@ class MatrixUser { } const peer = new TelegramPeer(dialog._, dialog.id) const portal = await this.app.getPortalByPeer(peer) - if (portal.updateInfo(this.telegramPuppet, dialog)) { - portal.save() + if (await portal.updateInfo(this.telegramPuppet, dialog)) { changed = true } } diff --git a/src/portal.js b/src/portal.js index d26dc075..d38a2ce6 100644 --- a/src/portal.js +++ b/src/portal.js @@ -41,14 +41,24 @@ class Portal { return portal } - async syncParticipants(participants) { - for (const participant of participants) { - const user = this.app.getTelegramUser(participant.id) - if (user.updateInfo(participant)) { - user.save() + async syncTelegramUsers(telegramPOV, users) { + if (!users) { + if (! await this.loadAccessHash(telegramPOV)) { + return false } - user.intent.join(this.roomID) + const data = await this.peer.getInfo(telegramPOV) + users = data.users } + for (const userData of users) { + const user = await this.app.getTelegramUser(userData.id) + await user.updateInfo(telegramPOV, userData) + await user.intent.join(this.roomID) + } + return true + } + + loadAccessHash(telegramPOV) { + return this.peer.loadAccessHash(this.app, telegramPOV, {portal: this}) } handleMatrixEvent(evt) { @@ -62,9 +72,11 @@ class Portal { } try { - const {info, participants} = await this.peer.getInfo(telegramPOV) - console.log(JSON.stringify(info, "", " ")) - console.log(JSON.stringify(participants, "", " ")) + if (! await this.loadAccessHash(telegramPOV)) { + return undefined + } + + const {info, users} = await this.peer.getInfo(telegramPOV) const room = await this.app.botIntent.createRoom({ options: { @@ -77,7 +89,7 @@ class Portal { this.app.portalsByRoomID.set(this.roomID, this) await this.save() - // TODO other things? + await this.syncTelegramUsers(telegramPOV, users) return this.roomID } catch (err) { @@ -90,8 +102,8 @@ class Portal { updateInfo(telegramPOV, dialog) { let changed = false if (this.peer.type === "channel") { - if (telegramPOV && this.accessHashes.get(telegramPOV.userID) !== +dialog.access_hash) { - this.accessHashes.set(telegramPOV.userID, +dialog.access_hash) + if (telegramPOV && this.accessHashes.get(telegramPOV.userID) !== dialog.access_hash) { + this.accessHashes.set(telegramPOV.userID, dialog.access_hash) changed = true } } diff --git a/src/telegram-peer.js b/src/telegram-peer.js index 67ce30b2..92a47de9 100644 --- a/src/telegram-peer.js +++ b/src/telegram-peer.js @@ -18,7 +18,7 @@ class TelegramPeer { constructor(type, id, accessHash) { this.type = type this.id = id - this.accessHash = +(accessHash || 0) + this.accessHash = accessHash this.username = undefined this.title = undefined } @@ -28,26 +28,37 @@ class TelegramPeer { case "peerChat": return new TelegramPeer("chat", peer.chat_id) case "peerUser": - return new TelegramPeer("user", peer.user_id, peer.access_hash || 0) + return new TelegramPeer("user", peer.user_id, peer.access_hash) case "peerChannel": - return new TelegramPeer("channel", peer.channel_id, peer.access_hash || 0) + return new TelegramPeer("channel", peer.channel_id, peer.access_hash) default: throw new Error(`Unrecognized peer type ${peer._}`) } } - async getAccessHash(app, telegramPOV) { - if (this.type === "chat" || this.accessHash > 0) { + /** + * Load the access hash for a specific puppeted Telegram user from the channel portal or TelegramUser info. + * + * @param {MautrixTelegram} app The instance of {@link MautrixTelegram} to use. + * @param {TelegramPuppet} telegramPOV The puppeted Telegram user for whom the access hash is needed. + * @param {Portal} [portal] Optional channel {@link Portal} instance to avoid calling {@link app#getPortalByPeer(peer)}. + * Only used if {@link #type} is {@linkplain user}. + * @param {TelegramUser} [user] Optional {@link TelegramUser} instance to avoid calling {@link app#getTelegramUser(id)}. + * Only used if {@link #type} is {@linkplain channel}. + * @returns {Promise} Whether or not the access hash was found and loaded. + */ + async loadAccessHash(app, telegramPOV, { portal, user }) { + if (this.type === "chat") { return true } else if (this.type === "user") { - const user = await app.getTelegramUser(this.id) + user = user || await app.getTelegramUser(this.id) if (user.accessHashes.has(telegramPOV.userID)) { this.accessHash = user.accessHashes.get(telegramPOV.userID) return true } return false } else if (this.type === "channel") { - const portal = await app.getPortalByPeer(this) + portal = portal || await app.getPortalByPeer(this) if (portal.accessHashes.has(telegramPOV.userID)) { this.accessHash = portal.accessHashes.get(telegramPOV.userID) return true @@ -56,7 +67,7 @@ class TelegramPeer { } } - updateInfo(dialog) { + async updateInfo(dialog) { let changed = false if (this.type === "channel") { if (this.username !== dialog.username) { @@ -68,11 +79,14 @@ class TelegramPeer { this.title = dialog.title changed = true } + if (changed) { + this.save() + } return changed } async getInfo(telegramPOV) { - let info, participants + let info, users switch(this.type) { case "user": throw new Error("Can't get chat info of user") @@ -80,24 +94,27 @@ class TelegramPeer { info = await telegramPOV.client("messages.getFullChat", { chat_id: this.id, }) - participants = info.users + users = info.users break case "channel": - // FIXME I'm broken (Error: CHANNEL_INVALID) info = await telegramPOV.client("channels.getFullChannel", { channel: this.toInputChannel(), }) - participants = await telegramPOV.client("channels.getParticipants", { + const participants = await telegramPOV.client("channels.getParticipants", { channel: this.toInputChannel(), filter: { _: "channelParticipantsRecent" }, offset: 0, limit: 1000, }) - break + users = participants.users + break default: throw new Error(`Unknown peer type ${this.type}`) } - return { info, participants } + return { + info: info.chats[0], + users + } } toInputPeer() { diff --git a/src/telegram-puppet.js b/src/telegram-puppet.js index d03a9e82..f1ee8973 100644 --- a/src/telegram-puppet.js +++ b/src/telegram-puppet.js @@ -18,6 +18,21 @@ const os = require("os") const telegram = require("telegram-mtproto") const TelegramPeer = require("./telegram-peer") +const META_FROM_FILETYPE = { + "storage.fileGif": { + mimetype: "image/gif", + extension: "gif", + }, + "storage.fileJpeg": { + mimetype: "image/jpeg", + extension: "jpeg", + }, + "storage.filePng": { + mimetype: "image/png", + extension: "png", + }, +} + /** * TelegramPuppet represents a Telegram account being controlled from Matrix. */ @@ -306,6 +321,22 @@ class TelegramPuppet { } }, 5000) } + + async getFile(location) { + location = Object.assign({}, location, {_: "inputFileLocation"}) + delete location.dc_id + const file = await this.client("upload.getFile", { + location, + offset: 0, + limit: 100*1024*1024, + }) + const meta = META_FROM_FILETYPE[file.type._] + if (meta) { + file.mimetype = meta.mimetype + file.extension = meta.extension + } + return file + } } module.exports = TelegramPuppet diff --git a/src/telegram-user.js b/src/telegram-user.js index d0350350..ec009b86 100644 --- a/src/telegram-user.js +++ b/src/telegram-user.js @@ -67,12 +67,8 @@ class TelegramUser { } } - updateInfo(telegramPOV, user) { + async updateInfo(telegramPOV, user) { let changed = false - if (telegramPOV && this.accessHashes.get(telegramPOV.userID) !== +user.access_hash) { - this.accessHashes.set(telegramPOV.userID, +user.access_hash) - changed = true - } if (this.firstName !== user.first_name) { this.firstName = user.first_name changed = true @@ -85,12 +81,31 @@ class TelegramUser { this.username = user.username changed = true } + if (await this.updateAvatarImageFrom(telegramPOV, user)) { + changed = true + } + if (telegramPOV && this.accessHashes.get(telegramPOV.userID) !== user.access_hash) { + this.accessHashes.set(telegramPOV.userID, user.access_hash) + changed = true + } + + 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 (changed) { + this.save() + } return changed } get intent() { if (!this._intent) { - this._intent = this.app.getIntentForTelegramID(this.id) + this._intent = this.app.getIntentForTelegramUser(this.id) } return this._intent } @@ -145,7 +160,7 @@ class TelegramUser { async updateAvatarImageFrom(telegramPOV, user) { if (!user.photo) { - return + return false } const photo = user.photo.photo_big @@ -153,7 +168,7 @@ class TelegramUser { this.photo.dc_id === photo.dc_id && this.photo.volume_id === photo.volume_id && this.photo.local_id === photo.local_id) { - return this.avatarURL + return false } const file = await telegramPOV.getFile(photo) @@ -165,15 +180,15 @@ class TelegramUser { type: file.mimetype, }) - this.avatarURL = response.content_uri + this.avatarURL = uploaded.content_uri this.photo = { dc_id: photo.dc_id, volume_id: photo.volume_id, local_id: photo.local_id, } - await this.app.putUser(this) - return this.avatarURL + await this.intent.setAvatarUrl(this.avatarURL) + return true } }