Allow hiding debug messages and fix some portal create bugs

This commit is contained in:
Tulir Asokan
2018-01-08 01:26:20 +02:00
parent 406dfe7018
commit 9525fa7776
7 changed files with 134 additions and 39 deletions
+2
View File
@@ -12,6 +12,8 @@ appservice:
port: 8080
id: telegram
debug: false
# Path to the registration file. This is automatically updated when generating a registration.
registration: ./registration.yaml
+1
View File
@@ -10,6 +10,7 @@
"url": "https://github.com/tulir/mautrix-telegram.git"
},
"dependencies": {
"chalk": "^2.3.0",
"colors": "1.1.x",
"commander": "2.12.x",
"escape-html": "1.0.x",
+24 -3
View File
@@ -22,6 +22,7 @@ const MatrixUser = require("./matrix-user")
const TelegramUser = require("./telegram-user")
const TelegramPeer = require("./telegram-peer")
const Portal = require("./portal")
const chalk = require("chalk")
/**
* The base class for the bridge.
@@ -105,6 +106,7 @@ class MautrixTelegram {
onUserQuery(/*user*/) {
return {}
},
onLog: msg => self.debug("blue", msg),
async onEvent(request/*, context*/) {
try {
await self.handleMatrixEvent(request.getData())
@@ -117,16 +119,35 @@ class MautrixTelegram {
})
}
debug(color, ...message) {
if (this.config.appservice.debug) {
console.log(chalk[color](...message))
}
}
debugErr(color, ...message) {
if (this.config.appservice.debug) {
console.error(chalk[color](...message))
}
}
info(...message) {
console.log(...message)
}
warn(...message) {
console.error(chalk.orange(...message))
}
/**
* Start the bridge.
*/
async run() {
console.log("Appservice listening on port %s", this.config.appservice.port)
this.info("Appservice listening on port %s", this.config.appservice.port)
await this.bridge.run(this.config.appservice.port, {})
// Load all Matrix users to cache
const userEntries = await this.bridge.getUserStore()
.select({ type: "matrix" })
const userEntries = await this.bridge.getUserStore().select({ type: "matrix" })
for (const entry of userEntries) {
const user = MatrixUser.fromEntry(this, entry)
+12 -5
View File
@@ -17,6 +17,7 @@ const md5 = require("md5")
const TelegramPuppet = require("./telegram-puppet")
const TelegramPeer = require("./telegram-peer")
const strSim = require("string-similarity")
const chalk = require("chalk")
/**
* MatrixUser represents a Matrix user who probably wants to control their
@@ -211,22 +212,28 @@ class MatrixUser {
if (dialog._ === "chatForbidden" || dialog._ === "channelForbidden" || dialog.deactivated) {
continue
}
const peer = new TelegramPeer(dialog._, dialog.id)
const peer = new TelegramPeer(dialog._, dialog.id, {
accessHash: dialog.access_hash,
})
const portal = await this.app.getPortalByPeer(peer)
if (await portal.updateInfo(this.telegramPuppet, dialog)) {
changed = true
}
this.chats.push(portal)
if (createRooms) {
if (peer.type === "channel") {
portal.accessHashes.set(this.telegramPuppet.userID, dialog.access_hash)
}
try {
await portal.createMatrixRoom(this.telegramPuppet, {
invite: [this.userID],
})
} catch (err) {
console.error(`Failed to create a room for ${dialog._} ${dialog.id}`)
console.error(err)
console.error(err.stack)
continue
}
}
if (await portal.updateInfo(this.telegramPuppet, dialog)) {
changed = true
}
}
await this.save()
return changed
+70 -12
View File
@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
const TelegramPeer = require("./telegram-peer")
const formatter = require("./formatter")
const chalk = require("chalk")
/**
* Portal represents a portal from a Matrix room to a Telegram chat.
@@ -124,7 +125,6 @@ class Portal {
* @returns {Object} The uploaded Matrix file object.
*/
async copyTelegramFile(telegramPOV, sender, location, id) {
console.log(JSON.stringify(location, "", " "))
id = id || location.id
const file = await telegramPOV.getFile(location)
const uploaded = await sender.intent.getClient().uploadContent({
@@ -226,7 +226,7 @@ class Portal {
// We don't care about user deletions on chats without portals
return
}
console.log("Service message received, creating room for", evt.to.id)
this.app.debug("yellow", "Service message received, creating room for", evt.to.id)
await this.createMatrixRoom(evt.source, { invite: [evt.source.matrixUser.userID] })
return
}
@@ -293,11 +293,42 @@ class Portal {
await intent.setRoomName(this.roomID, this.peer.title)
break
default:
console.log("Unhandled service message of type", evt.action._)
console.log(evt.action)
this.app.warn("Unhandled service message of type", evt.action._)
this.app.warn(JSON.stringify(evt.action, "", " "))
}
}
/**
* Context: Matrix user X is logged into mautrix-telegram and has a private chat portal room with Telegram user Y.
* X sends message to Y from another Telegram client.
*
* Problem: We can't control X's Matrix account. We also can't make sure that X's Telegram account's Matrix puppet
* is always in private chat portal rooms, since X could create a private chat portal by inviting Y's
* puppet without giving it, the only AS-controllable user in the room, any power.
*
* Solution: When encountering an error caused by the above situation, this function is called.
* This function first tries to invite X's Matrix puppet to the room.
* If that fails, text messages are sent through the other user as notices and other messages are dropped.
*
* @param {Object} evt The custom event object (see #handleTelegramMessage(evt))
* @param {TelegramUser} sender The Telegram user object of the sender.
* @returns {boolean} Whether or not the puppet for the sender was successfully invited.
*/
async tryFixPrivateChatForOutgoingMessage(evt, sender) {
try {
const intent = await this.getMainIntent()
await intent.invite(this.roomID, sender.mxid)
return true
} catch (_) {
const receiver = await this.app.getTelegramUser(evt.to.id, { createIfNotFound: false })
if (receiver) {
if (evt.text) {
receiver.sendNotice(this.roomID, `[Your message from another client] ${evt.text}`)
}
}
}
return false
}
/**
* Handle a Telegram service message event.
@@ -315,6 +346,8 @@ class Portal {
* @param {messageMediaGeo} [evt.geo] The Telegram {@link https://tjhorner.com/tl-schema/constructor/messageMediaGeo Location} attached to the message.
*/
async handleTelegramMessage(evt) {
const a = Object.assign({}, evt)
delete a.source
if (!this.isMatrixRoomCreated()) {
try {
const result = await this.createMatrixRoom(evt.source, { invite: [evt.source.matrixUser.userID] })
@@ -329,7 +362,18 @@ class Portal {
}
const sender = await this.app.getTelegramUser(evt.from)
await sender.intent.sendTyping(this.roomID, false)
try {
await sender.intent.sendTyping(this.roomID, false)
} catch (err) {
if (evt.to.type === "user") {
if (!await this.tryFixPrivateChatForOutgoingMessage(evt, sender)) {
return
}
await sender.intent.sendTyping(this.roomID, false)
} else {
throw err
}
}
if (evt.text && evt.text.length > 0) {
if (evt.entities) {
@@ -403,7 +447,7 @@ class Portal {
})
break
default:
console.log("Unhandled event:", evt)
this.app.warn("Unhandled event:", JSON.stringify(evt, "", " "))
}
}
@@ -435,13 +479,13 @@ class Portal {
user_id: user.toPeer(telegramPOV).toInputObject(),
fwd_limit: 50,
})
console.log("Chat invite result:", updates)
this.app.debug("green", "Chat invite result:", JSON.stringify(updates, "", " "))
} else if (this.peer.type === "channel") {
const updates = await telegramPOV.client("channels.inviteToChannel", {
channel: this.peer.toInputObject(),
users: [user.toPeer(telegramPOV).toInputObject()],
})
console.log("Channel invite result:", updates)
this.app.debug("green", "Channel invite result:", JSON.stringify(updates, "", " "))
} else {
throw new Error(`Can't invite user to peer type ${this.peer.type}`)
}
@@ -478,11 +522,25 @@ class Portal {
if (Array.isArray(users)) {
for (const userID of users) {
if (typeof userID === "string") {
intent.invite(this.roomID, userID)
try {
await intent.invite(this.roomID, userID)
} catch (err) {
if (err.httpStatus !== 403) {
console.error(`Failed to invite ${userID} to ${this.roomID}:`)
console.error(err)
}
}
}
}
} else if (typeof users === "string") {
intent.invite(this.roomID, users)
try {
await intent.invite(this.roomID, users)
} catch (err) {
if (err.httpStatus !== 403) {
console.error(`Failed to invite ${users} to ${this.roomID}:`)
console.error(err)
}
}
}
}
@@ -586,7 +644,7 @@ class Portal {
if (!await this.loadAccessHash(telegramPOV)) {
this.creatingMatrixRoom = false
throw new Error("Failed to load access hash.")
throw new Error(`Failed to load access hash for ${this.peer.type} ${this.peer.username || this.peer.id}.`)
}
let room, info, users
@@ -661,7 +719,7 @@ class Portal {
async updateInfo(telegramPOV, dialog) {
if (!dialog) {
console.log("updateInfo called without dialog data")
this.app.warn("updateInfo called without dialog data")
const { user } = this.peer.getInfo(telegramPOV)
if (!user) {
throw new Error("Dialog data not given and fetching data failed")
+24 -18
View File
@@ -18,6 +18,7 @@ const { nextRandomInt } = require("telegram-mtproto/lib/bin")
const fileType = require("file-type")
const pkg = require("../package.json")
const TelegramPeer = require("./telegram-peer")
const chalk = require("chalk")
/**
* @module telegram-puppet
@@ -279,7 +280,7 @@ class TelegramPuppet {
async onUpdate(update) {
if (!update) {
console.log("Oh noes! Empty update")
this.app.error("Oh noes! Empty update")
return
}
let to, from, portal
@@ -312,7 +313,7 @@ class TelegramPuppet {
//
case "updateShortMessage":
to = new TelegramPeer("user", update.user_id, { receiverID: this.userID })
from = update.user_id
from = update.out ? this.userID : update.user_id
break
case "updateShortChatMessage":
to = new TelegramPeer("chat", update.chat_id)
@@ -358,7 +359,6 @@ class TelegramPuppet {
})
return
}
console.log(update)
await portal.handleTelegramMessage({
from,
to,
@@ -381,6 +381,9 @@ class TelegramPuppet {
}
async handleUpdate(data) {
if (!data.update || data.update._ !== "updateUserStatus") {
this.app.debug("green", "Raw event for", this.userID, JSON.stringify(data, "", " "))
}
try {
switch (data._) {
case "updateShort":
@@ -389,8 +392,8 @@ class TelegramPuppet {
break
case "updates":
// TODO use data.users and data.chats
console.log("Received updates users:", data.users)
console.log("Received updates chats:", data.chats)
this.app.debug("green", "Received updates users:", JSON.stringify(data.users, "", " "))
this.app.debug("green", "Received updates chats:", JSON.stringify(data.chats, "", " "))
this.date = data.date
const updateHandlers = []
for (const update of data.updates) {
@@ -403,21 +406,24 @@ class TelegramPuppet {
await this.onUpdate(data)
break
case "updatesTooLong":
console.log("Handling updatesTooLong", this.pts, this.date)
if (this.pts === 0) {
this.app.warn("updatesTooLong received, but we don't have a persistent timestamp :(")
break
}
this.app.debug("yellow", "Handling updatesTooLong", this.pts, this.date)
const dat = await this.client("updates.getDifference", {
pts: this.pts,
date: this.date,
qts: -1,
})
console.log("updatesTooLong data:", dat)
this.app.debug("yellow", `updatesTooLong data: ${JSON.stringify(dat, "", " ")}`)
// TODO use updatesTooLong data?
break
default:
console.log("Unrecognized update type:", data._)
this.app.warn("Unrecognized update type:", data._)
}
} catch (err) {
console.error("Error handling update:", err)
console.error(err.stack)
this.app.warn("Error handling update:", err)
}
}
@@ -429,30 +435,30 @@ class TelegramPuppet {
//console.log("Updating online status...")
//const statusUpdate = await this.client("account.updateStatus", { offline: false })
//console.log(statusUpdate)
console.log("Fetching initial state...")
this.app.info("Fetching initial state...")
const state = await this.client("updates.getState", {})
console.log("Initial state:", state)
this.app.debug("green", "Initial state:", JSON.stringify(state, "", " "))
} catch (err) {
console.error("Error getting initial state:", err)
}
try {
console.log("Updating contact list...")
this.app.info("Updating contact list...")
const changed = await this.matrixUser.syncContacts()
if (!changed) {
console.log("Contacts were up-to-date")
this.app.info("Contacts were up-to-date")
} else {
console.log("Contacts updated")
this.app.info("Contacts updated")
}
} catch (err) {
console.error("Failed to update contacts:", err)
}
try {
console.log("Updating dialogs...")
this.app.info("Updating dialogs...")
const changed = await this.matrixUser.syncChats()
if (!changed) {
console.log("Dialogs were up-to-date")
this.app.info("Dialogs were up-to-date")
} else {
console.log("Dialogs updated")
this.app.info("Dialogs updated")
}
} catch (err) {
console.error("Failed to update dialogs:", err)
+1 -1
View File
@@ -73,7 +73,7 @@ class TelegramUser {
async updateInfo(telegramPOV, user, { updateAvatar = false } = {}) {
if (!user) {
console.log("updateInfo called without user data")
this.app.warn("updateInfo called without user data")
user = await telegramPOV.client("users.getFullUser", {
id: this.toPeer(telegramPOV).toInputObject(),
})