From daeaca8c4fbc08eb5674ea3ed1665426ef074c26 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Nov 2017 01:19:12 +0200 Subject: [PATCH] Authentication may or may not now work --- .editorconfig | 3 + .eslintrc.json | 166 +++++++++++----- .gitignore | 3 + README.md | 10 +- example-config.yaml | 40 ++++ mautrix-telegram | 1 + package-lock.json | 433 ++++++++++++++++++++++++++++++++++------- package.json | 9 +- src/app.js | 151 ++++++++++++++ src/commands.js | 126 ++++++++++++ src/index.js | 60 ++++++ src/matrix-user.js | 104 ++++++++++ src/telegram-puppet.js | 115 +++++++++++ 13 files changed, 1100 insertions(+), 121 deletions(-) create mode 100644 example-config.yaml create mode 120000 mautrix-telegram create mode 100644 src/app.js create mode 100644 src/commands.js create mode 100755 src/index.js create mode 100644 src/matrix-user.js create mode 100644 src/telegram-puppet.js diff --git a/.editorconfig b/.editorconfig index 1b832673..21d312a1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,6 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true + +[*.{yaml,yml}] +indent_style = space diff --git a/.eslintrc.json b/.eslintrc.json index 94cb51e1..1508fcf4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,55 +13,125 @@ "import" ], "rules": { - "indent": ["error", "tab", { - "FunctionDeclaration": { - "parameters": 2, - "body": 1 - }, - "FunctionExpression": { - "parameters": 2, - "body": 1 - }, - "VariableDeclarator": 2, - "CallExpression": { - "arguments": 2 - }, - "MemberExpression": "off", - "ImportDeclaration": "first" - }], - "object-curly-newline": ["error", { - "minProperties": 5, - "consistent": true - }], - "one-var": ["error", { - "initialized": "never", - "uninitialized": "always" - }], - "one-var-declaration-per-line": ["error", "initializations"], - "quotes": ["error", "double"], - "semi": ["error", "never"], - "comma-dangle": ["error", "always-multiline"], - "max-len": ["warn", 80], - "camelcase": ["error", {"properties": "always"}], - "space-before-function-paren": ["error", "never"], - "func-style": ["warn", "declaration", {"allowArrowFunctions": true}], - "id-length": ["warn", {"max": 25, "exceptions": ["i", "x", "y", "$"]}], + "indent": [ + "error", + "tab", + { + "FunctionDeclaration": { + "parameters": 2, + "body": 1 + }, + "FunctionExpression": { + "parameters": 2, + "body": 1 + }, + "VariableDeclarator": 2, + "CallExpression": { + "arguments": 2 + }, + "MemberExpression": "off", + "ImportDeclaration": "first" + } + ], + "object-curly-newline": [ + "error", + { + "minProperties": 5, + "consistent": true + } + ], + "one-var": [ + "error", + { + "initialized": "never", + "uninitialized": "always" + } + ], + "one-var-declaration-per-line": [ + "error", + "initializations" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "never" + ], + "comma-dangle": [ + "error", + "always-multiline" + ], + "max-len": [ + "warn", + 80 + ], + "camelcase": [ + "error", + { + "properties": "always" + } + ], + "space-before-function-paren": [ + "error", + "never" + ], + "func-style": [ + "warn", + "declaration", + { + "allowArrowFunctions": true + } + ], + "id-length": [ + "warn", + { + "max": 25, + "exceptions": [ + "i", + "x", + "y", + "$" + ] + } + ], "import/no-nodejs-modules": "error", - "import/order": ["warn", { - "groups": ["builtin", "external", "internal", "parent", "sibling", - "index"], - "newlines-between": "never" - }], - "arrow-body-style": ["error", "as-needed"], - "complexity": ["warn", 11], - "new-cap": ["warn", { - "newIsCap": true, - "capIsNew": true - }], - "no-empty": ["error", { - "allowEmptyCatch": true - }], - + "import/order": [ + "warn", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index" + ], + "newlines-between": "never" + } + ], + "arrow-body-style": [ + "error", + "as-needed" + ], + "complexity": [ + "warn", + 11 + ], + "new-cap": [ + "warn", + { + "newIsCap": true, + "capIsNew": true + } + ], + "no-empty": [ + "error", + { + "allowEmptyCatch": true + } + ], "function-paren-newline": "off", "no-labels": "off", "no-control-regex": "off", diff --git a/.gitignore b/.gitignore index 78f2710d..ebebc999 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules/ .idea/ +config.yaml +registration.yaml +*.db diff --git a/README.md b/README.md index c1a7db71..82cac029 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ # mautrix-telegram +**Not yet functional.** + A Matrix-Telegram puppeting bridge. -Not yet functional. +## Usage +1. Create a copy of `example-config.yaml` and fill out the fields. +2. Generate the appservice registration with `./mautrix-telegram -g`. + You can use the `-c` and `-r` flags to change the location of the config and registration files. + They default to `config.yaml` and `registration.yaml` respectively. +3. Run the bridge `./mautrix-telegram`. You can also use forever: `forever start mautrix-telegram`. +4. Invite the appservice bot to a private room and view the commands with `!help`. diff --git a/example-config.yaml b/example-config.yaml new file mode 100644 index 00000000..18546df6 --- /dev/null +++ b/example-config.yaml @@ -0,0 +1,40 @@ +# Homeserver details +homeserver: + address: https://matrix.org + domain: matrix.org + +# Application service host/registration related details +appservice: + # The protocol the homeserver should use when connecting to the appservice. + # Usually "http" or "https". + protocol: http + hostname: localhost + port: 8080 + id: telegram + + # Path to the registration file. This is automatically updated when generating a registration. + registration: ./registration.yaml + +# Bridge config +bridge: + # ${ID} is replaced with the user ID of the Telegram user. + username_template: "telegram_${ID}" + bot_username: telegrambot + # The displayname to set to the bot automatically. + bot_displayname: Telegram Bridge + + command_prefix: "!tg" + + # 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. + whitelist: + - "internal-hs.example.com" + - "@user:public.example.com" + +# Telegram app config. Generate your own keys at https://my.telegram.org/apps +telegram: + server_config: + dev: false + api_config: + api_id: 12345 + api_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz diff --git a/mautrix-telegram b/mautrix-telegram new file mode 120000 index 00000000..7f850106 --- /dev/null +++ b/mautrix-telegram @@ -0,0 +1 @@ +src/index.js \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2f3f977b..44819fb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,19 @@ "resolved": "https://registry.npmjs.org/@goodmind/node-cryptojs-aes/-/node-cryptojs-aes-0.5.0.tgz", "integrity": "sha1-hNJ9Lsdavw2u05IYHRwgUs4EbPI=" }, + "@most/multicast": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@most/multicast/-/multicast-1.3.0.tgz", + "integrity": "sha512-DWH8AShgp5bXn+auGzf5tzPxvpmEvQJd0CNsApOci1LDF4eAEcnw4HQOr2Jaa+L92NbDYFKBSXxll+i7r1ikvw==", + "requires": { + "@most/prelude": "1.6.4" + } + }, + "@most/prelude": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@most/prelude/-/prelude-1.6.4.tgz", + "integrity": "sha512-RsT1xRIEc+rCCTZPL3v/tAC+dX1qt1q00ZofEtCJEMEsVg7zT+WLXiVQdhcRqqh4baQTYmDPArXrQWyd7GkqAA==" + }, "Base64": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", @@ -103,6 +116,11 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "apropos": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/apropos/-/apropos-0.6.2.tgz", + "integrity": "sha512-iz4Yk3iNe46RofpRq6vrAb+M66cwsnRLqkJHHNDxHsAl7ND4a7QCIx38Z0gd4LxOX/R8NOAcRh7VmOGTkYWU3g==" + }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", @@ -111,6 +129,11 @@ "sprintf-js": "1.0.3" } }, + "array-flatten": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", + "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -266,11 +289,6 @@ "tweetnacl": "0.14.5" } }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" - }, "binary-search-tree": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz", @@ -584,7 +602,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", @@ -595,7 +612,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, "requires": { "color-convert": "1.9.1" } @@ -604,7 +620,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, "requires": { "has-flag": "2.0.0" } @@ -650,7 +665,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -658,8 +672,12 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" }, "combine-source-map": { "version": "0.6.1", @@ -680,6 +698,11 @@ "delayed-stream": "0.0.5" } }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, "commondir": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", @@ -713,6 +736,15 @@ } } }, + "concurrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/concurrify/-/concurrify-1.0.1.tgz", + "integrity": "sha1-H+vktGFdSobuKTo5cT/61f65aB4=", + "requires": { + "sanctuary-type-classes": "6.1.0", + "sanctuary-type-identifiers": "2.0.1" + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -842,6 +874,14 @@ "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=" }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "0.10.35" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -901,6 +941,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=" }, + "denque": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.2.2.tgz", + "integrity": "sha512-x92Ql74lcTbGylXILO9Xf9S0cMpEPP04zVp2bB9e2C7G/n/Q1SgLl78RaSYEPSgpDX9uLgQXCEGAS5BI5dP3yA==" + }, "depd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", @@ -1027,11 +1072,6 @@ "minimalistic-crypto-utils": "1.0.1" } }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" - }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -1058,6 +1098,45 @@ } } }, + "es5-ext": { + "version": "0.10.35", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz", + "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=", + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-symbol": "3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.35" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, "escape-html": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", @@ -1066,8 +1145,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "4.11.0", @@ -1346,6 +1424,15 @@ "crc": "3.2.1" } }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.35" + } + }, "eventemitter2": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz", @@ -1540,6 +1627,23 @@ "write": "0.2.1" } }, + "fluture": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/fluture/-/fluture-7.2.2.tgz", + "integrity": "sha512-V9eRvHDUEvS/jkVNbexi4tzAe+2KLUuzYpSajOTY8JhTBRrsGoSbJJVzengcFWqU8TTsAmEHa6H94QaPJYiF1w==", + "requires": { + "concurrify": "1.0.1", + "denque": "1.2.2", + "inspect-f": "1.2.1", + "sanctuary-type-classes": "6.1.0", + "sanctuary-type-identifiers": "2.0.1" + } + }, + "folktale": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/folktale/-/folktale-2.0.1.tgz", + "integrity": "sha512-3kDSWVkSlErHIt/dC73vu+5zRqbW1mlnL46s2QfYN7Ps0JcS9MVtuLCrDQOBa7sanA+d9Fd8F+bn0VcyNe68Jw==" + }, "follow-redirects": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.5.tgz", @@ -1593,11 +1697,20 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz", "integrity": "sha1-NYJJkgbJcjcUGQ7ddLRgT+tKYUw=" }, + "fs-extra": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", + "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", @@ -1684,8 +1797,7 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "har-schema": { "version": "2.0.0", @@ -1721,8 +1833,7 @@ "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" }, "hash-base": { "version": "2.0.2", @@ -1913,6 +2024,11 @@ "xtend": "4.0.1" } }, + "inspect-f": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/inspect-f/-/inspect-f-1.2.1.tgz", + "integrity": "sha1-EXVztd9XpQneb+QBEx1Ca6wVkrU=" + }, "ipaddr.js": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz", @@ -1971,8 +2087,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-resolvable": { "version": "1.0.0", @@ -2015,8 +2130,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { "version": "3.10.0", @@ -2068,10 +2182,13 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.1.11" + } }, "jsonify": { "version": "0.0.0", @@ -2177,16 +2294,6 @@ "strip-bom": "3.0.0" } }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" - } - }, "localforage": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.5.3.tgz", @@ -2216,8 +2323,12 @@ "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", - "dev": true + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash-es": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", + "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" }, "lodash.cond": { "version": "4.5.2", @@ -2230,6 +2341,14 @@ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "requires": { + "js-tokens": "3.0.2" + } + }, "lru-cache": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", @@ -2240,6 +2359,14 @@ "yallist": "2.1.2" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "0.10.35" + } + }, "matrix-appservice": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/matrix-appservice/-/matrix-appservice-0.3.4.tgz", @@ -2369,6 +2496,21 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "memoizee": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.11.tgz", + "integrity": "sha1-vemBdmPJ5A/bKk6hw2cpYIeujI8=", + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-weak-map": "2.0.2", + "event-emitter": "0.3.5", + "is-promise": "2.1.0", + "lru-queue": "0.1.0", + "next-tick": "1.0.0", + "timers-ext": "0.1.2" + } + }, "merge-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz", @@ -2510,11 +2652,64 @@ } } }, + "most": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/most/-/most-1.7.2.tgz", + "integrity": "sha512-jDSzUa7HPT79RqLEdrBnYpGZ5DXx8SSDYkS7W6ErBUgU0ewaNDLIdqLJ6GlzXK90eCaDzXm0B1C0KmfTT0XdZQ==", + "requires": { + "@most/multicast": "1.3.0", + "@most/prelude": "1.6.4", + "symbol-observable": "1.0.4" + } + }, + "most-subject": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/most-subject/-/most-subject-5.3.0.tgz", + "integrity": "sha1-Om4+/UNvqTvYszwqI5yQiCMKYQI=", + "requires": { + "@most/multicast": "1.3.0", + "@most/prelude": "1.6.4", + "most": "1.7.2" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mtproto-logger": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/mtproto-logger/-/mtproto-logger-0.1.9.tgz", + "integrity": "sha512-YohXpOVXQuc6DMASbyvLrGevhBZLATh371d3LJaqyuFjcY73C+dpn2tyApgJnJN1mraPEPzPi60BiRONLvZa7g==", + "requires": { + "array-flatten": "2.1.1", + "chalk": "2.3.0", + "debug": "2.6.9", + "eventemitter2": "4.1.2", + "most": "1.7.2", + "mtproto-shared": "0.1.8" + } + }, + "mtproto-shared": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/mtproto-shared/-/mtproto-shared-0.1.8.tgz", + "integrity": "sha512-dyr4azn8JW3KWS7IJa8dEgIZtpWsFDdYNYBK6WL4u+jRSWmA2MSycIiHLDHYbAbM+7akn5Xi3l/RsDAYGUqtgg==", + "requires": { + "bluebird": "3.5.1", + "memoizee": "0.4.11" + } + }, + "mtproto-storage-fs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/mtproto-storage-fs/-/mtproto-storage-fs-0.3.1.tgz", + "integrity": "sha512-QcsQf359e/8LGa9+6LsrA5IESBSZPw+1K0iNH1p269AmLeEOKx+dF7lcbtE/L0ydeyjqbYVPZZLsOeGUE0Lkjg==", + "requires": { + "bluebird": "3.5.1", + "fs-extra": "4.0.2", + "mtproto-logger": "0.1.9", + "mtproto-shared": "0.1.8" + } + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -2551,6 +2746,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", @@ -2718,8 +2918,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -2885,9 +3084,9 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, "ramda": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.23.0.tgz", - "integrity": "sha1-zNE//3NJepOXTj6GMnv9h71ujis=" + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", + "integrity": "sha1-w7d1UZfzW43DUCIoJixMkd22uFc=" }, "randombytes": { "version": "2.0.5", @@ -3023,6 +3222,30 @@ } } }, + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "requires": { + "lodash": "4.17.4", + "lodash-es": "4.17.4", + "loose-envify": "1.3.1", + "symbol-observable": "1.0.4" + } + }, + "redux-act": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/redux-act/-/redux-act-1.5.0.tgz", + "integrity": "sha512-DTTYHDJjukWwHoR9B+j4ZEhQVoQ+4OOrluEjFl7ddzVcrzNFHRSoxSTHzGS82QLJ3kshg/Dsr/HXitBD5kn9Cw==" + }, + "redux-most": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/redux-most/-/redux-most-0.6.2.tgz", + "integrity": "sha1-BFHaHWH3aqv/mtw4DTINPmc7FoY=", + "requires": { + "most-subject": "5.3.0" + } + }, "request": { "version": "2.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", @@ -3291,14 +3514,26 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "sanctuary-type-classes": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sanctuary-type-classes/-/sanctuary-type-classes-6.1.0.tgz", + "integrity": "sha1-a9ZwRDWMhEDVYebiVWq4jdjNgAE=", "requires": { - "ajv": "5.3.0" + "sanctuary-type-identifiers": "1.0.0" + }, + "dependencies": { + "sanctuary-type-identifiers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sanctuary-type-identifiers/-/sanctuary-type-identifiers-1.0.0.tgz", + "integrity": "sha1-6PNZ8AbLXmJM+4RkYD/BFGCL3p8=" + } } }, + "sanctuary-type-identifiers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sanctuary-type-identifiers/-/sanctuary-type-identifiers-2.0.1.tgz", + "integrity": "sha1-/FJM9t2Szr/LsN2VCe/xkxWaIO0=" + }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", @@ -3593,6 +3828,11 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "symbol-observable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + }, "syntax-error": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz", @@ -3616,23 +3856,41 @@ } }, "telegram-mtproto": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/telegram-mtproto/-/telegram-mtproto-2.2.8.tgz", - "integrity": "sha1-HmVsSjk2omxiTgkD6IPAC7HnPhU=", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/telegram-mtproto/-/telegram-mtproto-3.2.11.tgz", + "integrity": "sha512-WZPC5TfZCtvBc+TQd6jX1WzcMEBn7w4EIKanusBP1nicFn6hIiUK2XXcmVnt+ObA6Aq1+GZht11qgTZb8JSPOQ==", "requires": { "@goodmind/node-cryptojs-aes": "0.5.0", + "@most/prelude": "1.6.4", "ajv": "5.3.0", "ajv-keywords": "2.1.1", + "apropos": "0.6.2", "axios": "0.16.2", "bluebird": "3.5.1", - "debug": "2.6.9", "detect-node": "2.0.3", "eventemitter2": "4.1.2", + "fluture": "7.2.2", + "folktale": "2.0.1", + "most": "1.7.2", + "most-subject": "5.3.0", + "mtproto-logger": "0.1.9", + "mtproto-shared": "0.1.8", + "mtproto-storage-fs": "0.3.1", "pako": "1.0.6", - "ramda": "0.23.0", + "ramda": "0.24.1", "randombytes": "2.0.5", + "redux": "3.7.2", + "redux-act": "1.5.0", + "redux-most": "0.6.2", "rusha": "0.8.7", - "worker-loader": "0.8.1" + "uuid": "3.1.0" + }, + "dependencies": { + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + } } }, "text-table": { @@ -3676,6 +3934,15 @@ "process": "0.11.10" } }, + "timers-ext": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz", + "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=", + "requires": { + "es5-ext": "0.10.35", + "next-tick": "1.0.0" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -3748,6 +4015,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, "unreachable-branch-transform": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz", @@ -3871,15 +4143,6 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, - "worker-loader": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-0.8.1.tgz", - "integrity": "sha1-6OmVMx6jTfW/aCloJL+38K1XjUM=", - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3904,6 +4167,38 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true + }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "requires": { + "argparse": "1.0.9", + "glob": "7.1.2" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + } + } } } } diff --git a/package.json b/package.json index 375a2bc2..d76b2544 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,18 @@ "description": "A Matrix-Telegram puppeting bridge", "author": "Tulir Asokan ", "license": "GPL-3.0", - "main": "index.js", + "main": "src/index.js", "repository": { "type": "git", "url": "https://github.com/tulir/mautrix-telegram.git" }, "dependencies": { - "telegram-mtproto": "2.x.x", + "telegram-mtproto": "3.2.x", "matrix-js-sdk": "0.x.x", - "matrix-appservice-bridge": "1.x.x" + "matrix-appservice-bridge": "1.x.x", + "commander": "2.11.x", + "yamljs": "0.3.x", + "colors": "1.1.x" }, "devDependencies": { "eslint": "4.11.x", diff --git a/src/app.js b/src/app.js new file mode 100644 index 00000000..804a4ffe --- /dev/null +++ b/src/app.js @@ -0,0 +1,151 @@ +// mautrix-telegram - A Matrix-Telegram puppeting bridge +// Copyright (C) 2017 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +const {Bridge} = require("matrix-appservice-bridge") +const commands = require("./commands") +const MatrixUser = require("./matrix-user") + +class MautrixTelegram { + constructor(config) { + this.config = config + + this.matrixUsersByID = new Map() + this.telegramUsersByID = new Map() + + const self = this + this.bridge = new Bridge({ + homeserverUrl: config.homeserver.address, + domain: config.homeserver.domain, + registration: config.appservice.registration, + controller: { + onUserQuery(user) { + return {} + }, + onEvent(request, context) { + self.handleMatrixEvent(request.getData()) + }, + }, + }) + } + + run() { + console.log("Appservice listening on port %s", this.config.appservice.port) + this.bridge.run(this.config.appservice.port, {}) + //this.botIntent.setDisplayName(this.config.bridge.bot_displayname) + } + + get bot() { + return this.bridge.getBot() + } + + get botIntent() { + return this.bridge.getIntent() + } + + getMatrixUser(id) { + let user = this.matrixUsersByID.get(id) + if (user) { + console.log(id, "found in cache") + return Promise.resolve(user) + } + + return this.bridge.getUserStore().select({ + type: "matrix", + id, + }).then(entries => { + this.matrixUsersByID.get(id) + if (user) { + console.log(id, "found in cache (after race)") + return Promise.resolve(user) + } + + if (entries.length) { + user = MatrixUser.fromEntry(this, entries[0]) + console.log(id, "loaded from database") + } else { + user = new MatrixUser(this, id) + console.log(id, "created") + } + this.matrixUsersByID.set(id, user) + return user + }) + } + + putUser(user) { + const entry = user.toEntry() + return this.bridge.getUserStore().upsert({ + type: entry.type, + id: entry.id, + }, entry) + } + + handleMatrixEvent(evt) { + const asBotID = this.bridge.getBot().getUserId() + if (evt.type === "m.room.member" && evt.state_key === asBotID) { + if (evt.content.membership === "invite") { + // Accept all invites + this.botIntent.join(evt.room_id) + .catch(err => { + console.warn(`Failed to join room ${evt.room_id}:`, err) + if (e instanceof Error) { + console.warn(e.stack) + } + }) + } + return + } + if (evt.sender === asBotID || evt.type !== "m.room.message" || !evt.content) { + // Ignore own messages and non-message events. + return; + } + const cmdprefix = this.config.bridge.command_prefix + if (evt.content.body.startsWith(cmdprefix + " ")) { + this.getMatrixUser(evt.sender).then(user => { + if (!user.whitelisted) { + this.botIntent.sendText(evt.room_id, "You are not authorized to use this bridge.") + return + } + + const prefixLength = cmdprefix.length + 1 + const args = evt.content.body.substr(prefixLength).split(" ") + const command = args.shift() + commands.run(user, command, args, reply => + this.botIntent.sendText( + evt.room_id, + reply.replace("$cmdprefix", cmdprefix))) + }) + return + } + } + + checkWhitelist(userID) { + if (!this.config.bridge.whitelist || this.config.bridge.whitelist.length === 0) { + return true + } + + userID = userID.toLowerCase() + const userIDCapture = /\@.+\:(.+)/.exec(userID) + const homeserver = userIDCapture && userIDCapture.length > 1 ? userIDCapture[1] : undefined + for (let whitelisted of this.config.bridge.whitelist) { + whitelisted = whitelisted.toLowerCase() + if (whitelisted === userID || (homeserver && whitelisted === homeserver)) { + return true + } + } + return false + } +} + +module.exports = MautrixTelegram diff --git a/src/commands.js b/src/commands.js new file mode 100644 index 00000000..0042658c --- /dev/null +++ b/src/commands.js @@ -0,0 +1,126 @@ +// mautrix-telegram - A Matrix-Telegram puppeting bridge +// Copyright (C) 2017 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +const makePasswordHash = require("telegram-mtproto").plugins.makePasswordHash + +class Command { + constructor(description, usage, func) { + this.description = description + this.usage = usage + this.func = func + } + + run(app, roomID, args) { + this.func(args, message => + app.botIntent.sendText(roomID, message)) + } +} + +const commands = {} + +function run(sender, command, args, reply) { + if (sender.commandStatus) { + if (command === "cancel") { + sender.commandStatus = undefined + reply(`${sender.commandStatus.action} cancelled.`) + return + } + args.unshift(command) + sender.commandStatus.next(sender, args, reply) + return + } + command = this.commands[command] + if (!command) { + reply("Unknown command. Try \"$cmdprefix help\" for help.") + } + command(sender, args, reply) +} + +commands.cancel = () => "Nothing to cancel." + +const enterPassword = (sender, args, reply) => { + if (args.length === 0) { + reply("Usage: $cmdprefix ") + return + } + + const hash = makePasswordHash(sender.commandStatus.salt, args[0]) + sender.checkPassword(hash) + .then(() => { + // TODO show who the user logged in as + reply(`Logged in successfully.`) + sender.commandStatus = undefined + }, err => { + reply(`Login failed: ${err}`) + console.log(err) + }) +} + +const enterCode = (sender, args, reply) => { + if (args.length === 0) { + reply("Usage: $cmdprefix ") + return + } + + sender.signInToTelegram(args[0]) + .then(data => { + if (data.status === "ok") { + // TODO show who the user logged in as + reply(`Logged in successfully.`) + sender.commandStatus = undefined + } else if (data.status === "need-password") { + reply(`You have two-factor authentication enabled. Password hint: ${data.hint} \nEnter your password using "$cmdprefix "`) + sender.commandStatus = { + action: "Two-factor authentication", + next: enterPassword, + salt: data.salt, + } + } else { + reply(`Unexpected sign in response, status=${data.status}`) + } + }, err => { + reply(`Login failed: ${err}`) + console.log(err) + }) +} + +commands.login = (sender, args, reply) => { + if (args.length === 0) { + reply("Usage: $cmdprefix login ") + return + } + + sender.sendTelegramCode(args[0]) + .then(data => { + reply(`Login code sent to ${args[0]}. \nEnter the code using "$cmdprefix "`) + sender.commandStatus = { + action: "Phone code authentication", + next: enterCode, + } + console.log(data) + }, err => { + reply(`Failed to send code: ${err}`) + console.log(err) + }) +} + +commands.help = (sender, args, reply) => { + reply("Help not yet implemented 3:") +} + +module.exports = { + commands, + run, +} diff --git a/src/index.js b/src/index.js new file mode 100755 index 00000000..f3e3c837 --- /dev/null +++ b/src/index.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node +// mautrix-telegram - A Matrix-Telegram puppeting bridge +// Copyright (C) 2017 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +const {AppServiceRegistration} = require("matrix-appservice-bridge") +const commander = require("commander") +const YAML = require("yamljs") +const fs = require("fs") +const MautrixTelegram = require("./app") +const pkg = require("../package.json") + +commander + .version(pkg.version) + .option("-c, --config ", "the file to load the config from. defaults to ./config.yaml") + .option("-g, --generate-registration", "generate a registration based on the config") + .option("-r, --registration ", "the file to save the registration to. defaults to ./registration.yaml") + .parse(process.argv) + +commander.registration = commander.registration || "./registration.yaml" +commander.config = commander.config || "./config.yaml" + +const config = YAML.load(commander.config) + +if (commander.generateRegistration) { + const registration = { + id: config.appservice.id, + hs_token: AppServiceRegistration.generateToken(), + as_token: AppServiceRegistration.generateToken(), + namespaces: { + users: [{ + exclusive: true, + regex: `@${config.bridge.username_template.replace("${ID}", ".+")}:${config.homeserver.domain}` + }], + aliases: [], + rooms: [], + }, + url: `${config.appservice.protocol}://${config.appservice.hostname}:${config.appservice.port}`, + sender_localpart: config.bridge.bot_username, + rate_limited: false, + } + fs.writeFileSync(commander.registration, YAML.stringify(registration, 10)) + config.appservice.registration = commander.registration + fs.writeFileSync(commander.config, YAML.stringify(config, 10)) + return +} + +const app = new MautrixTelegram(config) +app.run() diff --git a/src/matrix-user.js b/src/matrix-user.js new file mode 100644 index 00000000..d05addc7 --- /dev/null +++ b/src/matrix-user.js @@ -0,0 +1,104 @@ +// mautrix-telegram - A Matrix-Telegram puppeting bridge +// Copyright (C) 2017 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +const TelegramPuppet = require("./telegram-puppet") + +class MatrixUser { + constructor(app, userID) { + this.app = app + this.userID = userID + this.whitelisted = app.checkWhitelist(userID) + this.phoneNumber = undefined + this.phoneCodeHash = undefined + this.commandStatus = undefined + this._telegramPuppet = undefined + } + + static fromEntry(app, entry) { + if (entry.type !== "matrix") { + throw new Error("MatrixUser can only be created from entry type \"matrix\"") + } + + const user = new MatrixUser(app, entry.id) + user.phoneNumber = entry.data.phone_number + user.phoneCodeHash = entry.data.phone_code_hash + if (entry.data.puppet) { + user.puppetData = entry.data.puppet + user.telegramPuppet + } + return user + } + + toEntry() { + if (this.puppet) { + this.puppetData = this.puppet.toSubentry() + } + return { + type: "matrix", + id: this.userID, + data: { + phone_number: this.phoneNumber, + phone_code_hash: this.phoneCodeHash, + puppet: this.puppetData, + }, + } + } + + get telegramPuppet() { + if (!this._telegramPuppet) { + this._telegramPuppet = TelegramPuppet.fromSubentry(this.app, this, this.puppetData || {}) + } + return this._telegramPuppet + } + + parseTelegramError(err) { + const message = err.toPrintable ? err.toPrintable() : err.toString() + + if (err instanceof Error) { + throw err + } + throw new Error(message) + } + + sendTelegramCode(phoneNumber) { + // TODO handle existing login? + + return this.telegramPuppet.sendCode(phoneNumber) + .then(result => { + this.phoneNumber = phoneNumber + this.phoneCodeHash = result.phone_code_hash + this.app.putUser(this) + return result + }, err => this.parseTelegramError(err)) + } + + signInToTelegram(phoneCode) { + if (!this.phoneNumber) throw new Error("Phone number not set") + if (!this.phoneCodeHash) throw new Error("Phone code not sent") + + return this.telegramPuppet.signIn(this.phoneNumber, this.phoneCodeHash, phoneCode) + .then(result => { + this.phoneCodeHash = undefined + return this.app.putUser(this).then(() => result) + }) + } + + checkPassword(password_hash) { + return this.telegramPuppet.checkPassword(password_hash) + .then(() => this.app.putUser(this)) + } +} + +module.exports = MatrixUser diff --git a/src/telegram-puppet.js b/src/telegram-puppet.js new file mode 100644 index 00000000..c56936ed --- /dev/null +++ b/src/telegram-puppet.js @@ -0,0 +1,115 @@ +// mautrix-telegram - A Matrix-Telegram puppeting bridge +// Copyright (C) 2017 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +const pkg = require("../package.json") +const os = require("os") +const telegram = require("telegram-mtproto") + +class TelegramPuppet { + constructor(opts) { + this._client = undefined + this.userID = opts.userID + this.matrixUser = opts.matrixUser + this.data = opts.data + + this.app = opts.app + + this.serverConfig = Object.assign({}, opts.server_config) + + this.api_hash = opts.api_hash + this.api_id = opts.api_id + + this.apiConfig = Object.assign({}, { + app_version: pkg.version, + lang_code: "en", + api_id: opts.api_id, + }, opts.api_config) + } + + static fromSubentry(app, matrixUser, data) { + const userID = data.user_id + delete data.user_id + return new TelegramPuppet(Object.assign({ + userID, + matrixUser, + data, + app, + }, app.config.telegram)) + } + + toSubentry() { + return Object.assign({ + user_id: this.userID + }, this.data) + } + + get datacenter() { + return { dcID: 1 } + } + + get client() { + if (!this._client) { + this._client = telegram.MTProto({ + api: this.apiConfig, + server: this.serverConfig, + }) + } + return this._client + } + + sendCode(phone_number) { + return this.client("auth.sendCode", { + phone_number, + current_number: true, + api_id: this.api_id, + api_hash: this.api_hash, + }) + + } + + signIn(phone_number, phone_code_hash, phone_code) { + return this.client("auth.signIn", { + phone_number, phone_code, phone_code_hash + }) + .then( + result => this.signInComplete(result), + err => { + if (err.type !== "SESSION_PASSWORD_NEEDED") { + throw err + } + this.client("account.getPassword", {}).then(data => { + return { + status: "need-password", + hint: data.hint, + salt: data.current_salt + } + }) + }) + } + + checkPassword(password_hash) { + return this.client("auth.checkPassword", {password_hash}) + .then((result) => this.signInComplete(result)) + } + + signInComplete(data) { + this.userID = data.user.id + return { + status: "ok" + } + } +} + +module.exports = TelegramPuppet