From 4372632d93dc31bee81ef844023f45d990f86f83 Mon Sep 17 00:00:00 2001 From: Mohammed Sohail Date: Fri, 14 Jan 2022 14:46:59 +0300 Subject: [PATCH] add: initial working poc --- .gitignore | 1 + package.json | 15 +++++ src/bot.js | 70 +++++++++++++++++++++++ src/cache.js | 6 ++ src/request.js | 30 ++++++++++ src/utils.js | 20 +++++++ yarn.lock | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 290 insertions(+) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 src/bot.js create mode 100644 src/cache.js create mode 100644 src/request.js create mode 100644 src/utils.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7e801fa --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "ussd-tg-proxy", + "version": "0.0.1", + "description": "ussd-tg-proxy", + "main": "src/bot.js", + "repository": "https://git.grassecon.net/grassrootseconomics/ussd-tg-proxy.git", + "author": "Mohamed Sohail ", + "license": "GPL-3", + "private": false, + "dependencies": { + "grammy": "^1.6.1", + "ioredis": "^4.28.3", + "phin": "^3.6.1" + } +} diff --git a/src/bot.js b/src/bot.js new file mode 100644 index 0000000..2d8a91b --- /dev/null +++ b/src/bot.js @@ -0,0 +1,70 @@ +// std lib +const crypto = require("crypto"); + +// npm imports +const { Bot, Keyboard } = require("grammy"); + +// module imports +const cache = require("./cache"); +const request = require("./request"); + +// TODO: get value from confini +// TODO: webhook support +const bot = new Bot(""); + +// TODO: handle errors +bot.command("start", async (ctx) => { + const tgLinked = await cache.get(ctx.msg.from.id); + + if (tgLinked) { + await cache.set(tgLinked, crypto.randomBytes(16).toString("hex")); + const res = await request.proxy(tgLinked); + + return ctx.reply(res.text); + } + + const keyboard = new Keyboard(); + return await ctx.reply( + "Click on the button below to link your phone number.", + { + reply_markup: keyboard.requestContact("Link Phone"), + } + ); +}); + +// TODO: handle errors +bot.on("message:text", async (ctx) => { + const tgLinked = await cache.get(ctx.msg.from.id); + + if (tgLinked) { + const res = await request.proxy(tgLinked, ctx.msg.text); + + if (res.code !== "END") { + return ctx.reply(res.text); + } + + return await cache.del(tgLinked); + } + + return await ctx.reply( + "Session expired or account not linked. /start the bot again to access your Sarafu account." + ); +}); + +// TODO: handle errors +bot.on("msg:contact", async (ctx) => { + // TODO: check if msg is reply from bot to prevent contact share bypass + const contact = ctx.msg.contact; + if (contact.phone_number.slice(0, 4) !== "+254") { + return ctx.reply("Sarafu is only available in Kenya at the moment."); + } + + // TODO: use contact.user_id in actual guard to prevent contact share bypass + await cache.set(ctx.msg.from.id, contact.phone_number.slice(1)); + + return ctx.reply( + "Phone number successfully linked. /start the bot again to access your Sarafu account." + ); +}); + +bot.start(); diff --git a/src/cache.js b/src/cache.js new file mode 100644 index 0000000..e85df29 --- /dev/null +++ b/src/cache.js @@ -0,0 +1,6 @@ +const Redis = require("ioredis"); + +// TODO: get value from confini +const cache = new Redis(); + +module.exports = cache; diff --git a/src/request.js b/src/request.js new file mode 100644 index 0000000..682f6f3 --- /dev/null +++ b/src/request.js @@ -0,0 +1,30 @@ +// npm imports +const phin = require("phin"); + +// module imports +const cache = require("./cache"); +const util = require("./utils"); + +// proxy requests to ussd-server +// TODO: handle errors +async function proxy(phone, input = "") { + const sessionId = await cache.get(phone); + + const { body } = await phin({ + // TODO: get value from confini + url: "", + method: "POST", + parse: "string", + form: { + sessionId: sessionId, + phoneNumber: phone, + // TODO: get value from confini + serviceCode: "", + text: input, + }, + }); + + return util.parseUssdResponse(body); +} + +module.exports = { proxy }; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..0d171bf --- /dev/null +++ b/src/utils.js @@ -0,0 +1,20 @@ +// this regex extracts ussd reply options +const regex = /(\d).\s/g; + +// TODO: converts the text to a telegram keyboard +function createKeyboard(input) { + return Array.from(input.matchAll(regex), (m) => m[1]); +} + +// parses ussd responses to code and text +function parseUssdResponse(input) { + return { + code: input.slice(0, 3), + text: input.slice(4), + }; +} + +module.exports = { + createKeyboard, + parseUssdResponse, +}; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..258e71c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,148 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@grammyjs/types@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@grammyjs/types/-/types-2.5.1.tgz#3fab98b6180440bbe5ddfbe4f2097d99c8643444" + integrity sha512-ZJ+X+w5qo6YxCRDWt63+SD2iX+xitP3Z7ieLUWw1b7OanljAMjPRry2PvBRoA1Zg0nFqulxFtiAHxxN1v8BXiQ== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +centra@^2.4.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/centra/-/centra-2.5.0.tgz#854c30f9a3ff50da49fa69a8cb441aa25aa1e8e8" + integrity sha512-CnSF1HD8vOOgNbE4P2fZEhdhfAohvpcF3DSdSvEcSHDAZvr+Xfw73isT8SXJJc3VMBqSwjXhr29/ikHUgFcypg== + +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + +debug@^4.3.1, debug@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +denque@^1.1.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" + integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +grammy@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/grammy/-/grammy-1.6.1.tgz#7d6390993e830c7330022eddf5f5003054724231" + integrity sha512-ahg+oZCUO6KAYcMXpAEiseR7+JIFyedFLzkdh6Pf7sVjTtLLVG7vQ27ohPTrnTM1tW/J/unlcMdIR8aUgPs/FA== + dependencies: + "@grammyjs/types" "^2.5.1" + abort-controller "^3.0.0" + debug "^4.3.3" + node-fetch "^2.6.5" + +ioredis@^4.28.3: + version "4.28.3" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.3.tgz#b13fce8a6a7c525ba22e666d72980a3c0ba799aa" + integrity sha512-9JOWVgBnuSxpIgfpjc1OeY1OLmA4t2KOWWURTDRXky+eWO0LZhI33pQNT9gYxANUXfh5p/zYephYni6GPRsksQ== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + lodash.isarguments "^3.1.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@^2.6.5: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + +p-map@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +phin@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/phin/-/phin-3.6.1.tgz#652f61b3dbb0957bc0413a15ef55dfa4e39c303c" + integrity sha512-ubcFKk2jRr2WUJEh+QpMkCgOQansJDuIShKbsjpejeyI/aOMLrAf0TaisbMjKwTq9CsypFFJqUaN9ZSRmBJLPw== + dependencies: + centra "^2.4.2" + +redis-commands@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0"