More code

This commit is contained in:
Tulir Asokan
2017-11-21 00:09:39 +02:00
parent 217017099a
commit 5ef0a202a2
9 changed files with 145 additions and 116 deletions
+12 -10
View File
@@ -21,13 +21,19 @@ bridge:
username_template: "telegram_${ID}"
# ${DISPLAYNAME} is replaced with the display name of the Telegram user.
displayname_template: "${DISPLAYNAME} (Telegram)"
# ${NAME} is replaced with the name part of the public channel/group invite link ( https://t.me/${NAME} )
alias_template: "telegram_${NAME}"
# Username of the bot. The registration must be regenerated to change this.
bot_username: telegrambot
command_prefix: "!tg"
# Bridge management command configuration
commands:
# The prefix for all management commands.
prefix: "!tg"
# The key used to encrypt Telegram authentication tokens
# You can generate a new key using `pwgen 32`.
auth_key_password: long_string_to_encrypt_telegram_auth_keys
# Enables the !tg api ... commands for debugging.
# Do not enable this in production, it allows all whitelisted users to call any Telegram API methods freely.
allow_direct_api_calls: false
# Whitelist of user IDs that are allowed to use this bridge. Leave empty to disable.
# You can enter a domain without the localpart to allow all users from that homeserver to use the bridge.
@@ -35,12 +41,8 @@ bridge:
- "internal-hs.example.com"
- "@user:public.example.com"
# Telegram app config. Generate your own keys at https://my.telegram.org/apps
# Telegram config
telegram:
# Enables the !tg api ... commands for debugging.
# Do not enable this in production, it allows all whitelisted users to call any Telegram API methods freely.
allow_direct_api_calls: false
server_config:
dev: false
# Get your own API keys at https://my.telegram.org/apps
api_id: 12345
api_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz
+1 -1
View File
@@ -234,7 +234,7 @@ class MautrixTelegram {
const user = await this.getMatrixUser(evt.sender)
const cmdprefix = this.config.bridge.command_prefix
const cmdprefix = this.config.bridge.commands.prefix
if (evt.content.body.startsWith(cmdprefix + " ")) {
if (!user.whitelisted) {
this.botIntent.sendText(evt.room_id, "You are not authorized to use this bridge.")
+2 -34
View File
@@ -45,11 +45,7 @@ cancel - Cancel an ongoing action (such as login).
login <phone> - Request an authentication code.
logout - Log out from Telegram.
api <method> <args> - Call a Telegram API method. Args is always a JSON object. Disabled by default.
Temporary commands that will be replaced with better commands in the future:
createRoom <type> <id> - Create a portal room. <type> is user, chat or channel and <id> is the numeric ID of the Telegram chat.
syncUsers <type> <id> - Sync user info and join status in the given portal. Same arguments as createRoom.`)
api <method> <args> - Call a Telegram API method. Args is always a JSON object. Disabled by default.`)
}
@@ -143,34 +139,6 @@ commands.logout = async (sender, args, reply) => {
}
}
const TelegramPeer = require("./telegram-peer")
const Portal = require("./portal")
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)
}
}
//////////////////////////////
// General command handlers //
//////////////////////////////
@@ -181,7 +149,7 @@ commands.syncUsers = async (sender, args, reply, app) => {
////////////////////////////
commands.api = async (sender, args, reply, app) => {
if (!app.config.telegram.allow_direct_api_calls) {
if (!app.config.bridge.commands.allow_direct_api_calls) {
reply("Direct API calls are forbidden on this mautrix-telegram instance.")
return
}
+4 -1
View File
@@ -43,7 +43,10 @@ if (commander.generateRegistration) {
exclusive: true,
regex: `@${config.bridge.username_template.replace("${ID}", ".+")}:${config.homeserver.domain}`
}],
aliases: [],
aliases: [{
exclusive: true,
regex: `#${config.bridge.alias_template.replace("${NAME}", ".+")}:${config.homeserver.domain}`
}],
rooms: [],
},
url: `${config.appservice.protocol}://${config.appservice.hostname}:${config.appservice.port}`,
+30 -3
View File
@@ -112,18 +112,45 @@ class MatrixUser {
return true
}
async syncDialogs() {
async syncDialogs({createRooms=true} = {}) {
const dialogs = await this.telegramPuppet.client("messages.getDialogs", {})
let changed = false
for (const dialog of dialogs.chats) {
for (const dialog of dialogs.chats.concat(dialogs.users)) {
if (dialog._ === "chatForbidden" || dialog.deactivated) {
continue
}
const peer = new TelegramPeer(dialog._, dialog.id)
const peer = new TelegramPeer(dialog._, dialog.id, {
receiverID: dialog._ === "user"
? this.telegramPuppet.userID
: undefined
})
const portal = await this.app.getPortalByPeer(peer)
if (await portal.updateInfo(this.telegramPuppet, dialog)) {
changed = true
}
if (createRooms) {
try {
const {roomID, created} = await portal.createMatrixRoom(this.telegramPuppet, {
invite: [this.userID],
})
if (!created) {
// Make sure the user is invited, since the room already exists.
const intent = await (dialog._ === "user"
? this.app.getTelegramUser(peer.id)
: this.app.botIntent)
// FIXME check membership before re-inviting
//const membership = intent.getClient().getRoom(roomID).getMember(this.userID).membership
//if (membership !== "join") {
try {
await intent.invite(roomID, this.userID)
} catch (_) {}
//}
}
} catch (err) {
console.error(err)
console.error(err.stack)
}
}
}
return changed
}
+59 -34
View File
@@ -120,60 +120,85 @@ class Portal {
return !!this.roomID
}
async createMatrixRoom(telegramPOV) {
async createMatrixRoom(telegramPOV, {invite = []} = {}) {
if (this.roomID) {
return this.roomID
return {
created: false,
roomID: this.roomID,
}
}
try {
if (!await this.loadAccessHash(telegramPOV)) {
return undefined
}
if (!await this.loadAccessHash(telegramPOV)) {
throw new Error("Failed to load access hash.")
}
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, { updateAvatar: true })
title = users.getDisplayName()
}
const room = await this.app.botIntent.createRoom({
let room
const { info, users } = await this.peer.getInfo(telegramPOV)
if (this.peer.type === "chat") {
room = await this.app.botIntent.createRoom({
options: {
name: title,
name: info.title,
topic: info.about,
visibility: "private",
invite,
},
})
} else if (this.peer.type === "channel") {
room = await this.app.botIntent.createRoom({
options: {
name: info.title,
topic: info.about,
visibility: info.username ? "public" : "private",
room_alias_name: info.username
? this.app.config.bridge.alias_template.replace("${NAME}", info.username)
: "",
invite,
},
})
} else if (this.peer.type === "user") {
const user = await this.app.getTelegramUser(info.id)
await user.updateInfo(telegramPOV, info, { updateAvatar: true })
room = await user.intent.createRoom({
createAsClient: true,
options: {
name: user.getDisplayName(),
topic: "Telegram private chat",
visibility: "private",
invite,
},
})
} else {
throw new Error(`Unrecognized peer type: ${this.peer.type}`)
}
this.roomID = room.room_id
this.app.portalsByRoomID.set(this.roomID, this)
await this.save()
if (this.peer.type !== "user") {
await this.updateAvatar(telegramPOV, info)
this.roomID = room.room_id
this.app.portalsByRoomID.set(this.roomID, this)
await this.save()
if (this.peer.type !== "user") {
try {
await this.syncTelegramUsers(telegramPOV, users)
} else {
await users.intent.join(this.roomID)
await this.updateAvatar(telegramPOV, info)
} catch (err) {
console.error(err)
console.error(err.stack)
}
return this.roomID
} catch (err) {
console.error(err)
console.error(err.stack)
return undefined
}
return {
created: true,
roomID: this.roomID,
}
}
updateInfo(telegramPOV, dialog) {
async 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)
changed = true
}
} else if (this.peer.type === "user") {
const user = await this.app.getTelegramUser(this.peer.id)
await user.updateInfo(telegramPOV, dialog)
}
changed = this.peer.updateInfo(dialog) || changed
if (changed) {
+26 -21
View File
@@ -15,23 +15,28 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
class TelegramPeer {
constructor(type, id, accessHash, receiverID) {
constructor(type, id, { accessHash, receiverID, username, title } = {}) {
this.type = type
this.id = id
this.accessHash = accessHash
this.receiverID = receiverID
this.username = undefined
this.title = undefined
this.username = username
this.title = title
}
static fromTelegramData(peer, receiverID) {
switch(peer._) {
static fromTelegramData(peer, sender, receiverID) {
switch (peer._) {
case "peerChat":
return new TelegramPeer("chat", peer.chat_id)
case "peerUser":
return new TelegramPeer("user", peer.user_id, peer.access_hash, receiverID)
return new TelegramPeer("user", sender, {
accessHash: peer.access_hash,
receiverID,
})
case "peerChannel":
return new TelegramPeer("channel", peer.channel_id, peer.access_hash)
return new TelegramPeer("channel", peer.channel_id, {
accessHash: peer.access_hash,
})
default:
throw new Error(`Unrecognized peer type ${peer._}`)
}
@@ -48,7 +53,7 @@ class TelegramPeer {
* Only used if {@link #type} is {@linkplain channel}.
* @returns {Promise<boolean>} Whether or not the access hash was found and loaded.
*/
async loadAccessHash(app, telegramPOV, { portal, user }) {
async loadAccessHash(app, telegramPOV, { portal, user } = {}) {
if (this.type === "chat") {
return true
} else if (this.type === "user") {
@@ -70,7 +75,7 @@ class TelegramPeer {
async updateInfo(dialog) {
let changed = false
if (this.type === "channel") {
if (this.type === "channel" || this.type === "user") {
if (this.username !== dialog.username) {
this.username = dialog.username
changed = true
@@ -84,24 +89,28 @@ class TelegramPeer {
}
async getInfo(telegramPOV) {
let info, users
switch(this.type) {
let info,
users
switch (this.type) {
case "user":
info = await telegramPOV.client("users.getFullUser", {
id: this.toInputObject()
id: this.toInputObject(),
})
users = [info.user]
info = info.user
break
case "chat":
info = await telegramPOV.client("messages.getFullChat", {
chat_id: this.id,
})
users = info.users
info = info.chats[0]
break
case "channel":
info = await telegramPOV.client("channels.getFullChannel", {
channel: this.toInputObject(),
})
info = info.chats[0]
const participants = await telegramPOV.client("channels.getParticipants", {
channel: this.toInputObject(),
filter: { _: "channelParticipantsRecent" },
@@ -114,13 +123,13 @@ class TelegramPeer {
throw new Error(`Unknown peer type ${this.type}`)
}
return {
info: info.chats[0],
users
info: info,
users,
}
}
toInputPeer() {
switch(this.type) {
switch (this.type) {
case "chat":
return {
_: "inputPeerChat",
@@ -144,7 +153,7 @@ class TelegramPeer {
}
toInputObject() {
switch(this.type) {
switch (this.type) {
case "user":
return {
_: "inputUser",
@@ -163,11 +172,7 @@ class TelegramPeer {
}
static fromSubentry(entry) {
const peer = new TelegramPeer(entry.type, entry.id)
peer.username = entry.username
peer.title = entry.title
peer.receiverID = entry.receiverID
return peer
return new TelegramPeer(entry.type, entry.id, entry)
}
toSubentry() {
+6 -6
View File
@@ -257,20 +257,20 @@ class TelegramPuppet {
await user.intent.getClient().setPresence({presence: status})
break
case "updateUserTyping":
peer = new TelegramPeer("user", update.user_id, undefined, this.userID)
peer = new TelegramPeer("user", update.user_id, { receiverID: this.userID })
case "updateChatUserTyping":
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.
// typing timeout. Once 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":
peer = new TelegramPeer("user", update.user_id, undefined, this.userID)
peer = new TelegramPeer("user", update.user_id, { receiverID: this.userID })
case "updateShortChatMessage":
peer = peer || new TelegramPeer("chat", update.chat_id)
await this.handleMessage({
@@ -285,7 +285,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, this.userID),
to: TelegramPeer.fromTelegramData(update.to_id, update.from_id, this.userID),
text: update.message,
})
break
@@ -322,7 +322,7 @@ class TelegramPuppet {
this.client.bus.untypedMessage.observe(data => this.handleUpdate(data.message))
try {
console.log("Updating online status...")
//console.log("Updating online status...")
//const statusUpdate = await this.client("account.updateStatus", { offline: false })
//console.log(statusUpdate)
console.log("Fetching initial state...")
+5 -6
View File
@@ -48,7 +48,10 @@ class TelegramUser {
}
toPeer(telegramPOV) {
return new TelegramPeer("user", this.id, this.accessHashes.get(telegramPOV.userID))
return new TelegramPeer("user", this.id, {
accessHash: this.accessHashes.get(telegramPOV.userID),
receiverID: telegramPOV.userID,
})
}
toEntry() {
@@ -67,7 +70,7 @@ class TelegramUser {
}
}
async updateInfo(telegramPOV, user, { updateAvatar }) {
async updateInfo(telegramPOV, user, { updateAvatar } = {}) {
let changed = false
if (this.firstName !== user.first_name) {
this.firstName = user.first_name
@@ -141,10 +144,6 @@ class TelegramUser {
})
}
sendSelfStateEvent(roomID, type, content) {
return this.intent.sendStateEvent(roomID, type, this.getMxid(), content)
}
uploadContent(opts) {
return this.intent.getClient()
.uploadContent({