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": {
|
||||
"version": "2.1.1",
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"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": {
|
||||
"eslint": "4.11.x",
|
||||
|
||||
+12
-6
@@ -108,7 +108,7 @@ class MautrixTelegram {
|
||||
* @param {TelegramPeer} peer The TelegramPeer object whose portal to get.
|
||||
* @returns {Promise<Portal>} The Portal object.
|
||||
*/
|
||||
async getPortalByPeer(peer) {
|
||||
async getPortalByPeer(peer, { createIfNotFound = true } = {}) {
|
||||
let portal = this.portalsByPeerID.get(peer.id)
|
||||
if (portal) {
|
||||
return portal
|
||||
@@ -132,8 +132,10 @@ class MautrixTelegram {
|
||||
|
||||
if (entries.length) {
|
||||
portal = Portal.fromEntry(this, entries[0])
|
||||
} else {
|
||||
} else if (createIfNotFound) {
|
||||
portal = new Portal(this, undefined, peer)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
this.portalsByPeerID.set(peer.id, portal)
|
||||
if (portal.roomID) {
|
||||
@@ -199,7 +201,7 @@ class MautrixTelegram {
|
||||
* @param {number} id The internal Telegram ID of the user to get.
|
||||
* @returns {Promise<TelegramUser>} The TelegramUser object.
|
||||
*/
|
||||
async getTelegramUser(id) {
|
||||
async getTelegramUser(id, { createIfNotFound = true } = {}) {
|
||||
let user = this.telegramUsersByID.get(id)
|
||||
if (user) {
|
||||
return user
|
||||
@@ -218,8 +220,10 @@ class MautrixTelegram {
|
||||
|
||||
if (entries.length) {
|
||||
user = TelegramUser.fromEntry(this, entries[0])
|
||||
} else {
|
||||
} else if (createIfNotFound) {
|
||||
user = new TelegramUser(this, id)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
this.telegramUsersByID.set(id, user)
|
||||
return user
|
||||
@@ -234,7 +238,7 @@ class MautrixTelegram {
|
||||
* @param {string} id The MXID of the Matrix user to get.
|
||||
* @returns {Promise<MatrixUser>} The MatrixUser object.
|
||||
*/
|
||||
async getMatrixUser(id) {
|
||||
async getMatrixUser(id, { createIfNotFound = true } = {}) {
|
||||
let user = this.matrixUsersByID.get(id)
|
||||
if (user) {
|
||||
return user
|
||||
@@ -253,8 +257,10 @@ class MautrixTelegram {
|
||||
|
||||
if (entries.length) {
|
||||
user = MatrixUser.fromEntry(this, entries[0])
|
||||
} else {
|
||||
} else if (createIfNotFound) {
|
||||
user = new MatrixUser(this, id)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
this.matrixUsersByID.set(id, user)
|
||||
return user
|
||||
|
||||
+67
-2
@@ -32,7 +32,15 @@ function run(sender, command, args, reply, app) {
|
||||
reply("Unknown command. Try \"$cmdprefix help\" for help.")
|
||||
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."
|
||||
@@ -129,7 +137,7 @@ commands.login = 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) => {
|
||||
@@ -145,6 +153,63 @@ commands.logout = async (sender, args, reply) => {
|
||||
// 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 //
|
||||
|
||||
+44
-14
@@ -16,6 +16,7 @@
|
||||
const md5 = require("md5")
|
||||
const TelegramPuppet = require("./telegram-puppet")
|
||||
const TelegramPeer = require("./telegram-peer")
|
||||
const strSim = require("string-similarity");
|
||||
|
||||
/**
|
||||
* MatrixUser represents a Matrix user who probably wants to control their
|
||||
@@ -126,22 +127,9 @@ class MatrixUser {
|
||||
}
|
||||
if (createRooms) {
|
||||
try {
|
||||
const { roomID, created } = await portal.createMatrixRoom(this.telegramPuppet, {
|
||||
await portal.createMatrixRoom(this.telegramPuppet, {
|
||||
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) {
|
||||
console.error(err)
|
||||
console.error(err.stack)
|
||||
@@ -151,6 +139,48 @@ class MatrixUser {
|
||||
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) {
|
||||
if (this._telegramPuppet && this._telegramPuppet.userID) {
|
||||
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
|
||||
}
|
||||
|
||||
async createMatrixRoom(telegramPOV, { invite = [] } = {}) {
|
||||
async createMatrixRoom(telegramPOV, { invite = [], inviteEvenIfNotCreated = true } = {}) {
|
||||
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 {
|
||||
created: false,
|
||||
roomID: this.roomID,
|
||||
|
||||
@@ -70,7 +70,7 @@ class TelegramUser {
|
||||
}
|
||||
}
|
||||
|
||||
async updateInfo(telegramPOV, user, { updateAvatar } = {}) {
|
||||
async updateInfo(telegramPOV, user, { updateAvatar = false } = {}) {
|
||||
let changed = false
|
||||
if (this.firstName !== user.first_name) {
|
||||
this.firstName = user.first_name
|
||||
@@ -115,10 +115,14 @@ class TelegramUser {
|
||||
return this.intent.client.credentials.userId
|
||||
}
|
||||
|
||||
getFirstAndLastName() {
|
||||
return [this.firstName, this.lastName].filter(s => !!s)
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
getDisplayName() {
|
||||
if (this.firstName || this.lastName) {
|
||||
return [this.firstName, this.lastName].filter(s => !!s)
|
||||
.join(" ")
|
||||
return this.getFirstAndLastName()
|
||||
} else if (this.username) {
|
||||
return this.username
|
||||
} else if (this.phoneNumber) {
|
||||
|
||||
Reference in New Issue
Block a user