Add commands for searching and initiating private chats with users (ref #7)
This commit is contained in:
Generated
+8
@@ -3785,6 +3785,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string-similarity": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-11FTyzg4RjGLejmo2SkrtNtOnDA=",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "4.17.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||||
|
|||||||
+7
-6
@@ -10,13 +10,14 @@
|
|||||||
"url": "https://github.com/tulir/mautrix-telegram.git"
|
"url": "https://github.com/tulir/mautrix-telegram.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"telegram-mtproto": "3.x.x",
|
|
||||||
"matrix-js-sdk": "0.x.x",
|
|
||||||
"matrix-appservice-bridge": "1.x.x",
|
|
||||||
"commander": "2.11.x",
|
|
||||||
"yamljs": "0.3.x",
|
|
||||||
"colors": "1.1.x",
|
"colors": "1.1.x",
|
||||||
"md5": "2.2.x"
|
"commander": "2.11.x",
|
||||||
|
"matrix-appservice-bridge": "1.x.x",
|
||||||
|
"matrix-js-sdk": "0.x.x",
|
||||||
|
"md5": "2.2.x",
|
||||||
|
"string-similarity": "^1.2.0",
|
||||||
|
"telegram-mtproto": "3.x.x",
|
||||||
|
"yamljs": "0.3.x"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "4.11.x",
|
"eslint": "4.11.x",
|
||||||
|
|||||||
+12
-6
@@ -108,7 +108,7 @@ class MautrixTelegram {
|
|||||||
* @param {TelegramPeer} peer The TelegramPeer object whose portal to get.
|
* @param {TelegramPeer} peer The TelegramPeer object whose portal to get.
|
||||||
* @returns {Promise<Portal>} The Portal object.
|
* @returns {Promise<Portal>} The Portal object.
|
||||||
*/
|
*/
|
||||||
async getPortalByPeer(peer) {
|
async getPortalByPeer(peer, { createIfNotFound = true } = {}) {
|
||||||
let portal = this.portalsByPeerID.get(peer.id)
|
let portal = this.portalsByPeerID.get(peer.id)
|
||||||
if (portal) {
|
if (portal) {
|
||||||
return portal
|
return portal
|
||||||
@@ -132,8 +132,10 @@ class MautrixTelegram {
|
|||||||
|
|
||||||
if (entries.length) {
|
if (entries.length) {
|
||||||
portal = Portal.fromEntry(this, entries[0])
|
portal = Portal.fromEntry(this, entries[0])
|
||||||
} else {
|
} else if (createIfNotFound) {
|
||||||
portal = new Portal(this, undefined, peer)
|
portal = new Portal(this, undefined, peer)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
this.portalsByPeerID.set(peer.id, portal)
|
this.portalsByPeerID.set(peer.id, portal)
|
||||||
if (portal.roomID) {
|
if (portal.roomID) {
|
||||||
@@ -199,7 +201,7 @@ class MautrixTelegram {
|
|||||||
* @param {number} id The internal Telegram ID of the user to get.
|
* @param {number} id The internal Telegram ID of the user to get.
|
||||||
* @returns {Promise<TelegramUser>} The TelegramUser object.
|
* @returns {Promise<TelegramUser>} The TelegramUser object.
|
||||||
*/
|
*/
|
||||||
async getTelegramUser(id) {
|
async getTelegramUser(id, { createIfNotFound = true } = {}) {
|
||||||
let user = this.telegramUsersByID.get(id)
|
let user = this.telegramUsersByID.get(id)
|
||||||
if (user) {
|
if (user) {
|
||||||
return user
|
return user
|
||||||
@@ -218,8 +220,10 @@ class MautrixTelegram {
|
|||||||
|
|
||||||
if (entries.length) {
|
if (entries.length) {
|
||||||
user = TelegramUser.fromEntry(this, entries[0])
|
user = TelegramUser.fromEntry(this, entries[0])
|
||||||
} else {
|
} else if (createIfNotFound) {
|
||||||
user = new TelegramUser(this, id)
|
user = new TelegramUser(this, id)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
this.telegramUsersByID.set(id, user)
|
this.telegramUsersByID.set(id, user)
|
||||||
return user
|
return user
|
||||||
@@ -234,7 +238,7 @@ class MautrixTelegram {
|
|||||||
* @param {string} id The MXID of the Matrix user to get.
|
* @param {string} id The MXID of the Matrix user to get.
|
||||||
* @returns {Promise<MatrixUser>} The MatrixUser object.
|
* @returns {Promise<MatrixUser>} The MatrixUser object.
|
||||||
*/
|
*/
|
||||||
async getMatrixUser(id) {
|
async getMatrixUser(id, { createIfNotFound = true } = {}) {
|
||||||
let user = this.matrixUsersByID.get(id)
|
let user = this.matrixUsersByID.get(id)
|
||||||
if (user) {
|
if (user) {
|
||||||
return user
|
return user
|
||||||
@@ -253,8 +257,10 @@ class MautrixTelegram {
|
|||||||
|
|
||||||
if (entries.length) {
|
if (entries.length) {
|
||||||
user = MatrixUser.fromEntry(this, entries[0])
|
user = MatrixUser.fromEntry(this, entries[0])
|
||||||
} else {
|
} else if (createIfNotFound) {
|
||||||
user = new MatrixUser(this, id)
|
user = new MatrixUser(this, id)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
this.matrixUsersByID.set(id, user)
|
this.matrixUsersByID.set(id, user)
|
||||||
return user
|
return user
|
||||||
|
|||||||
+67
-2
@@ -32,7 +32,15 @@ function run(sender, command, args, reply, app) {
|
|||||||
reply("Unknown command. Try \"$cmdprefix help\" for help.")
|
reply("Unknown command. Try \"$cmdprefix help\" for help.")
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return commandFunc(sender, args, reply, app)
|
try {
|
||||||
|
return commandFunc(sender, args, reply, app)
|
||||||
|
} catch (err) {
|
||||||
|
reply(`Error running command: ${err}.`)
|
||||||
|
if (err instanceof Error) {
|
||||||
|
reply("Check bridge console for stack trace")
|
||||||
|
console.error(err.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.cancel = () => "Nothing to cancel."
|
commands.cancel = () => "Nothing to cancel."
|
||||||
@@ -129,7 +137,7 @@ commands.login = async (sender, args, reply) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commands.register = async (sender, args, reply) => {
|
commands.register = async (sender, args, reply) => {
|
||||||
reply("Registration has not yet been implemented. Please use the offical apps for now.")
|
reply("Registration has not yet been implemented. Please use the official apps for now.")
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.logout = async (sender, args, reply) => {
|
commands.logout = async (sender, args, reply) => {
|
||||||
@@ -145,6 +153,63 @@ commands.logout = async (sender, args, reply) => {
|
|||||||
// General command handlers //
|
// General command handlers //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
|
commands.search = async (sender, args, reply, app) => {
|
||||||
|
if (args.length < 1) {
|
||||||
|
reply("Usage: $cmdprefix search [-r|--remote] <query>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let msg = []
|
||||||
|
if (args[0] !== "-r" && args[0] !== "--remote") {
|
||||||
|
const contactResults = await sender.searchContacts(args.join(" "))
|
||||||
|
if (contactResults.length > 0) {
|
||||||
|
msg.push("Following results found from local contacts:")
|
||||||
|
msg.push("")
|
||||||
|
for (const {match, contact} of contactResults) {
|
||||||
|
msg.push(`- ${contact.getDisplayName()}: ${contact.id} (${match}% match)`)
|
||||||
|
}
|
||||||
|
msg.push("")
|
||||||
|
msg.push("To force searching from Telegram servers, add `-r` before the search query.")
|
||||||
|
reply(msg.join("\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args.shift()
|
||||||
|
msg.push("-r flag found: forcing remote search")
|
||||||
|
msg.push("")
|
||||||
|
}
|
||||||
|
const telegramResults = await sender.searchTelegram(args.join(" "))
|
||||||
|
if (telegramResults.length > 0) {
|
||||||
|
msg.push("Following results received from Telegram server:")
|
||||||
|
for (const user of telegramResults) {
|
||||||
|
msg.push(`- ${user.getDisplayName()}: ${user.id}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg.push("No users found.")
|
||||||
|
}
|
||||||
|
reply(msg.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.pm = async (sender, args, reply, app) => {
|
||||||
|
if (args.length < 1) {
|
||||||
|
reply("Usage: $cmdprefix pm <id>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await app.getTelegramUser(+args[0], { createIfNotFound: false })
|
||||||
|
if (!user) {
|
||||||
|
reply("User info not saved. Try searching for the user first?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const peer = user.toPeer(sender.telegramPuppet)
|
||||||
|
|
||||||
|
const userInfo = await peer.getInfo(sender.telegramPuppet)
|
||||||
|
await user.updateInfo(sender.telegramPuppet, userInfo)
|
||||||
|
|
||||||
|
const portal = await app.getPortalByPeer(peer)
|
||||||
|
await portal.createMatrixRoom(sender.telegramPuppet, {
|
||||||
|
invite: [sender.userID],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////
|
////////////////////////////
|
||||||
// Debug command handlers //
|
// Debug command handlers //
|
||||||
|
|||||||
+44
-14
@@ -16,6 +16,7 @@
|
|||||||
const md5 = require("md5")
|
const md5 = require("md5")
|
||||||
const TelegramPuppet = require("./telegram-puppet")
|
const TelegramPuppet = require("./telegram-puppet")
|
||||||
const TelegramPeer = require("./telegram-peer")
|
const TelegramPeer = require("./telegram-peer")
|
||||||
|
const strSim = require("string-similarity");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatrixUser represents a Matrix user who probably wants to control their
|
* MatrixUser represents a Matrix user who probably wants to control their
|
||||||
@@ -126,22 +127,9 @@ class MatrixUser {
|
|||||||
}
|
}
|
||||||
if (createRooms) {
|
if (createRooms) {
|
||||||
try {
|
try {
|
||||||
const { roomID, created } = await portal.createMatrixRoom(this.telegramPuppet, {
|
await portal.createMatrixRoom(this.telegramPuppet, {
|
||||||
invite: [this.userID],
|
invite: [this.userID],
|
||||||
})
|
})
|
||||||
if (!created) {
|
|
||||||
// Make sure the user is invited, since the room already exists.
|
|
||||||
|
|
||||||
const intent = 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) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
console.error(err.stack)
|
console.error(err.stack)
|
||||||
@@ -151,6 +139,48 @@ class MatrixUser {
|
|||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchContacts(query, {maxResults=5, minSimilarity = 0.45} = {}) {
|
||||||
|
const results = []
|
||||||
|
for (const contact of this.contacts) {
|
||||||
|
let displaynameSimilarity = 0, usernameSimilarity = 0, numberSimilarity = 0
|
||||||
|
if (contact.firstName || contact.lastName) {
|
||||||
|
displaynameSimilarity = strSim.compareTwoStrings(query, contact.getFirstAndLastName())
|
||||||
|
}
|
||||||
|
if (contact.username) {
|
||||||
|
usernameSimilarity = strSim.compareTwoStrings(query, contact.username)
|
||||||
|
}
|
||||||
|
if (contact.phoneNumber) {
|
||||||
|
numberSimilarity = strSim.compareTwoStrings(query, contact.phoneNumber)
|
||||||
|
}
|
||||||
|
const similarity = Math.max(displaynameSimilarity, usernameSimilarity, numberSimilarity)
|
||||||
|
console.log(contact.getDisplayName(), similarity, displaynameSimilarity, usernameSimilarity, numberSimilarity)
|
||||||
|
if (similarity >= minSimilarity) {
|
||||||
|
results.push({
|
||||||
|
similarity,
|
||||||
|
match: Math.round(similarity * 1000) / 10,
|
||||||
|
contact,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
.sort((a, b) => b.similarity - a.similarity)
|
||||||
|
.slice(0, maxResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchTelegram(query, {maxResults=5} = {}) {
|
||||||
|
const results = await this.telegramPuppet.client("contacts.search", {
|
||||||
|
q: query,
|
||||||
|
limit: maxResults,
|
||||||
|
})
|
||||||
|
const resultUsers = []
|
||||||
|
for (const userInfo of results.users) {
|
||||||
|
const user = await this.app.getTelegramUser(userInfo.id)
|
||||||
|
user.updateInfo(this.telegramPuppet, userInfo)
|
||||||
|
resultUsers.push(user)
|
||||||
|
}
|
||||||
|
return resultUsers
|
||||||
|
}
|
||||||
|
|
||||||
async sendTelegramCode(phoneNumber) {
|
async sendTelegramCode(phoneNumber) {
|
||||||
if (this._telegramPuppet && this._telegramPuppet.userID) {
|
if (this._telegramPuppet && this._telegramPuppet.userID) {
|
||||||
throw new Error("You are already logged in. Please log out before logging in again.")
|
throw new Error("You are already logged in. Please log out before logging in again.")
|
||||||
|
|||||||
+10
-1
@@ -122,8 +122,17 @@ class Portal {
|
|||||||
return !!this.roomID
|
return !!this.roomID
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMatrixRoom(telegramPOV, { invite = [] } = {}) {
|
async createMatrixRoom(telegramPOV, { invite = [], inviteEvenIfNotCreated = true } = {}) {
|
||||||
if (this.roomID) {
|
if (this.roomID) {
|
||||||
|
if (invite && inviteEvenIfNotCreated) {
|
||||||
|
const intent = this.peer.type === "user"
|
||||||
|
? (await this.app.getTelegramUser(this.peer.id)).intent
|
||||||
|
: this.app.botIntent
|
||||||
|
for (const userID of invite) {
|
||||||
|
// TODO check membership before inviting
|
||||||
|
intent.invite(this.roomID, userID)
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
created: false,
|
created: false,
|
||||||
roomID: this.roomID,
|
roomID: this.roomID,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class TelegramUser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateInfo(telegramPOV, user, { updateAvatar } = {}) {
|
async updateInfo(telegramPOV, user, { updateAvatar = false } = {}) {
|
||||||
let changed = false
|
let changed = false
|
||||||
if (this.firstName !== user.first_name) {
|
if (this.firstName !== user.first_name) {
|
||||||
this.firstName = user.first_name
|
this.firstName = user.first_name
|
||||||
@@ -115,10 +115,14 @@ class TelegramUser {
|
|||||||
return this.intent.client.credentials.userId
|
return this.intent.client.credentials.userId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFirstAndLastName() {
|
||||||
|
return [this.firstName, this.lastName].filter(s => !!s)
|
||||||
|
.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
getDisplayName() {
|
getDisplayName() {
|
||||||
if (this.firstName || this.lastName) {
|
if (this.firstName || this.lastName) {
|
||||||
return [this.firstName, this.lastName].filter(s => !!s)
|
return this.getFirstAndLastName()
|
||||||
.join(" ")
|
|
||||||
} else if (this.username) {
|
} else if (this.username) {
|
||||||
return this.username
|
return this.username
|
||||||
} else if (this.phoneNumber) {
|
} else if (this.phoneNumber) {
|
||||||
|
|||||||
Reference in New Issue
Block a user