From 8466ef934ec60d1e8d088f73c22efb409ddf12b2 Mon Sep 17 00:00:00 2001 From: Goldstein Date: Thu, 11 Aug 2022 12:58:36 +0300 Subject: [PATCH 1/5] Implement Telegram support --- src/claimDefinitions/index.js | 2 + src/claimDefinitions/telegram.js | 78 ++++++++++++++++++++++ src/enums.js | 2 + src/fetcher/index.js | 1 + src/fetcher/telegram.js | 110 +++++++++++++++++++++++++++++++ src/proxy/api/v2/index.js | 27 ++++++++ 6 files changed, 220 insertions(+) create mode 100644 src/claimDefinitions/telegram.js create mode 100644 src/fetcher/telegram.js diff --git a/src/claimDefinitions/index.js b/src/claimDefinitions/index.js index 49e8240..984400e 100644 --- a/src/claimDefinitions/index.js +++ b/src/claimDefinitions/index.js @@ -18,6 +18,7 @@ const list = [ 'irc', 'xmpp', 'matrix', + 'telegram', 'twitter', 'reddit', 'liberapay', @@ -39,6 +40,7 @@ const data = { irc: require('./irc'), xmpp: require('./xmpp'), matrix: require('./matrix'), + telegram: require('./telegram'), twitter: require('./twitter'), reddit: require('./reddit'), liberapay: require('./liberapay'), diff --git a/src/claimDefinitions/telegram.js b/src/claimDefinitions/telegram.js new file mode 100644 index 0000000..33a0a0f --- /dev/null +++ b/src/claimDefinitions/telegram.js @@ -0,0 +1,78 @@ +/* +Copyright 2021 Yarmo Mackenbach + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +const E = require('../enums') + +const reURI = /^tg:user=([a-zA-Z0-9_]{5,32})&chat=([a-zA-Z0-9_]{5,32})/ + +const processURI = (uri) => { + const match = uri.match(reURI) + + return { + serviceprovider: { + type: 'communication', + name: 'telegram' + }, + match: { + regularExpression: reURI, + isAmbiguous: false + }, + profile: { + display: `@${match[1]}`, + uri: `https://t.me/${match[1]}`, + qr: null + }, + proof: { + uri: null, + request: { + fetcher: E.Fetcher.TELEGRAM, + access: E.ProofAccess.GRANTED, + format: E.ProofFormat.JSON, + data: { + user: match[1], + chat: match[2] + } + } + }, + claim: { + format: E.ClaimFormat.FINGERPRINT, + relation: E.ClaimRelation.EQUALS, + path: ['text'] + } + } +} + +const tests = [ + { + uri: 'tg:user=alice&chat=foobar', + shouldMatch: true + }, + { + uri: 'tg:user=complex_user_1234&chat=complex_chat_1234', + shouldMatch: true + }, + { + uri: 'tg:user=&chat=foobar', + shouldMatch: false + }, + { + uri: 'tg:user=foobar&chat=', + shouldMatch: false + } +] + +exports.reURI = reURI +exports.processURI = processURI +exports.tests = tests diff --git a/src/enums.js b/src/enums.js index f88db5b..6e16b53 100644 --- a/src/enums.js +++ b/src/enums.js @@ -49,6 +49,8 @@ const Fetcher = { XMPP: 'xmpp', /** HTTP request to Matrix API */ MATRIX: 'matrix', + /** HTTP request to Telegram API */ + TELEGRAM: 'telegram', /** HTTP request to Twitter API */ TWITTER: 'twitter' } diff --git a/src/fetcher/index.js b/src/fetcher/index.js index 5454297..df32047 100644 --- a/src/fetcher/index.js +++ b/src/fetcher/index.js @@ -18,5 +18,6 @@ exports.dns = require('./dns') exports.http = require('./http') exports.irc = require('./irc') exports.matrix = require('./matrix') +exports.telegram = require('./telegram') exports.twitter = require('./twitter') exports.xmpp = require('./xmpp') diff --git a/src/fetcher/telegram.js b/src/fetcher/telegram.js new file mode 100644 index 0000000..890e38d --- /dev/null +++ b/src/fetcher/telegram.js @@ -0,0 +1,110 @@ +/* +Copyright 2021 Yarmo Mackenbach + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +const axios = require('axios') +const validator = require('validator') + +/** + * @module fetcher/telegram + */ + +/** + * The single request's timeout value in milliseconds + * This fetcher makes two requests in total + * @constant {number} timeout + */ +module.exports.timeout = 5000 + +/** + * Execute a fetch request + * @function + * @async + * @param {object} data - Data used in the request + * @param {string} data.chat - Telegram public chat username + * @param {string} data.user - Telegram user username + * @param {object} opts - Options used to enable the request + * @param {string} opts.claims.telegram.token - The Telegram Bot API token + * @returns {object|string} + */ +module.exports.fn = async (data, opts) => { + let timeoutHandle + const timeoutPromise = new Promise((resolve, reject) => { + timeoutHandle = setTimeout( + () => reject(new Error('Request was timed out')), + data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout + ) + }) + + const apiPromise = (method) => new Promise((resolve, reject) => { + try { + validator.isAscii(opts.claims.telegram.token) + } catch (err) { + throw new Error(`Telegram fetcher was not set up properly (${err.message})`) + } + + if (!data.chat || !data.user) { + reject(new Error('Both chat name and user name must be provided')) + return + } + + const url = `https://api.telegram.org/bot${opts.claims.telegram.token}/${method}?chat_id=@${data.chat}` + axios.get(url, { + headers: { + Accept: 'application/json', + 'User-Agent': `doipjs/${require('../../package.json').version}` + }, + validateStatus: (status) => status === 200 + }) + .then(res => resolve(res.data)) + .catch(e => reject(e)) + }) + + const fetchPromise = apiPromise('getChatAdministrators').then(admins => { + if (!admins.ok) { + throw new Error('Request to get chat administrators failed') + } + + return apiPromise('getChat').then(chat => { + if (!chat.ok) { + throw new Error('Request to get chat info failed') + } + + let creator + for (const admin of admins.result) { + if (admin.status === 'creator') { + creator = admin.user.username + } + } + + if (!chat.result.description) { + throw new Error('There is no chat description') + } + + if (creator !== data.user) { + throw new Error('User doesn\'t match') + } + + return { + user: creator, + text: chat.result.description + } + }) + }) + + return Promise.race([fetchPromise, timeoutPromise]).then((result) => { + clearTimeout(timeoutHandle) + return result + }) +} diff --git a/src/proxy/api/v2/index.js b/src/proxy/api/v2/index.js index 5cddba4..c16d6ae 100644 --- a/src/proxy/api/v2/index.js +++ b/src/proxy/api/v2/index.js @@ -28,6 +28,9 @@ const opts = { instance: process.env.MATRIX_INSTANCE || null, accessToken: process.env.MATRIX_ACCESS_TOKEN || null }, + telegram: { + token: process.env.TELEGRAM_TOKEN || null + }, xmpp: { service: process.env.XMPP_SERVICE || null, username: process.env.XMPP_USERNAME || null, @@ -172,6 +175,30 @@ router.get( } ) +// Telegram route +router.get( + '/get/telegram', query('chat').isString(), + async (req, res) => { + if (!opts.claims.telegram.token) { + return res.status(501).json({ errors: 'Telegram not enabled on server' }) + } + + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }) + } + + fetcher.telegram + .fn(req.query, opts) + .then((data) => { + return res.status(200).send(data) + }) + .catch((err) => { + return res.status(400).json({ errors: err.message ? err.message : err }) + }) + } +) + // IRC route router.get('/get/irc', query('nick').isString(), async (req, res) => { if (!opts.claims.irc.nick) { From cbeefbc90985d57426efe23759e6fcf80a7daf30 Mon Sep 17 00:00:00 2001 From: Goldstein Date: Thu, 11 Aug 2022 20:17:08 +0300 Subject: [PATCH 2/5] clickable links --- src/claimDefinitions/telegram.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/claimDefinitions/telegram.js b/src/claimDefinitions/telegram.js index 33a0a0f..4c24e1a 100644 --- a/src/claimDefinitions/telegram.js +++ b/src/claimDefinitions/telegram.js @@ -15,7 +15,7 @@ limitations under the License. */ const E = require('../enums') -const reURI = /^tg:user=([a-zA-Z0-9_]{5,32})&chat=([a-zA-Z0-9_]{5,32})/ +const reURI = /https:\/\/t.me\/([A-Za-z0-9_]{5,32})\?user=([A-Za-z0-9_]{5,32})/ const processURI = (uri) => { const match = uri.match(reURI) @@ -30,8 +30,8 @@ const processURI = (uri) => { isAmbiguous: false }, profile: { - display: `@${match[1]}`, - uri: `https://t.me/${match[1]}`, + display: `@${match[2]}`, + uri: `https://t.me/${match[2]}`, qr: null }, proof: { @@ -41,8 +41,8 @@ const processURI = (uri) => { access: E.ProofAccess.GRANTED, format: E.ProofFormat.JSON, data: { - user: match[1], - chat: match[2] + chat: match[1], + user: match[2] } } }, @@ -56,19 +56,23 @@ const processURI = (uri) => { const tests = [ { - uri: 'tg:user=alice&chat=foobar', + uri: 'https://t.me/foobar?user=alice', shouldMatch: true }, { - uri: 'tg:user=complex_user_1234&chat=complex_chat_1234', + uri: 'https://t.me/complex_chat_1234?user=complex_user_1234', shouldMatch: true }, { - uri: 'tg:user=&chat=foobar', + uri: 'https://t.me/foobar', shouldMatch: false }, { - uri: 'tg:user=foobar&chat=', + uri: 'https://t.me/foobar?user=', + shouldMatch: false + }, + { + uri: 'https://t.me/?user=foobar', shouldMatch: false } ] From 9d3b8f8508765a43ce5849864eb537ad82de027f Mon Sep 17 00:00:00 2001 From: Goldstein Date: Fri, 12 Aug 2022 13:02:19 +0300 Subject: [PATCH 3/5] change proof format --- src/claimDefinitions/telegram.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/claimDefinitions/telegram.js b/src/claimDefinitions/telegram.js index 4c24e1a..b5c4fe1 100644 --- a/src/claimDefinitions/telegram.js +++ b/src/claimDefinitions/telegram.js @@ -1,5 +1,5 @@ /* -Copyright 2021 Yarmo Mackenbach +Copyright 2022 Maximilian Siling Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ limitations under the License. */ const E = require('../enums') -const reURI = /https:\/\/t.me\/([A-Za-z0-9_]{5,32})\?user=([A-Za-z0-9_]{5,32})/ +const reURI = /https:\/\/t.me\/([A-Za-z0-9_]{5,32})\?proof=([A-Za-z0-9_]{5,32})/ const processURI = (uri) => { const match = uri.match(reURI) @@ -30,8 +30,8 @@ const processURI = (uri) => { isAmbiguous: false }, profile: { - display: `@${match[2]}`, - uri: `https://t.me/${match[2]}`, + display: `@${match[1]}`, + uri: `https://t.me/${match[1]}`, qr: null }, proof: { @@ -41,13 +41,13 @@ const processURI = (uri) => { access: E.ProofAccess.GRANTED, format: E.ProofFormat.JSON, data: { - chat: match[1], - user: match[2] + user: match[1], + chat: match[2] } } }, claim: { - format: E.ClaimFormat.FINGERPRINT, + format: E.ClaimFormat.URI, relation: E.ClaimRelation.EQUALS, path: ['text'] } @@ -56,11 +56,11 @@ const processURI = (uri) => { const tests = [ { - uri: 'https://t.me/foobar?user=alice', + uri: 'https://t.me/alice?proof=foobar', shouldMatch: true }, { - uri: 'https://t.me/complex_chat_1234?user=complex_user_1234', + uri: 'https://t.me/complex_user_1234?proof=complex_chat_1234', shouldMatch: true }, { @@ -68,11 +68,11 @@ const tests = [ shouldMatch: false }, { - uri: 'https://t.me/foobar?user=', + uri: 'https://t.me/foobar?proof=', shouldMatch: false }, { - uri: 'https://t.me/?user=foobar', + uri: 'https://t.me/?proof=foobar', shouldMatch: false } ] From 5ecdcd0061f369518751539e9893375f6fbb8ba4 Mon Sep 17 00:00:00 2001 From: Goldstein Date: Fri, 12 Aug 2022 13:03:48 +0300 Subject: [PATCH 4/5] update one more copyright --- src/fetcher/telegram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fetcher/telegram.js b/src/fetcher/telegram.js index 890e38d..eac1b4c 100644 --- a/src/fetcher/telegram.js +++ b/src/fetcher/telegram.js @@ -1,5 +1,5 @@ /* -Copyright 2021 Yarmo Mackenbach +Copyright 2022 Maximilian Siling Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 49050d55d2dedcb6be826b7b7002d05c9d468847 Mon Sep 17 00:00:00 2001 From: Goldstein Date: Fri, 12 Aug 2022 17:19:24 +0300 Subject: [PATCH 5/5] add more Telegram URIs --- src/claimDefinitions/telegram.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/claimDefinitions/telegram.js b/src/claimDefinitions/telegram.js index b5c4fe1..f9c0b4c 100644 --- a/src/claimDefinitions/telegram.js +++ b/src/claimDefinitions/telegram.js @@ -32,10 +32,10 @@ const processURI = (uri) => { profile: { display: `@${match[1]}`, uri: `https://t.me/${match[1]}`, - qr: null + qr: `https://t.me/${match[1]}` }, proof: { - uri: null, + uri: `https://t.me/${match[2]}`, request: { fetcher: E.Fetcher.TELEGRAM, access: E.ProofAccess.GRANTED,