More code
This commit is contained in:
+12
-10
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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...")
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user