add: initial working poc
This commit is contained in:
commit
4372632d93
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
15
package.json
Normal file
15
package.json
Normal file
@ -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 <sohalazim@pm.me>",
|
||||
"license": "GPL-3",
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"grammy": "^1.6.1",
|
||||
"ioredis": "^4.28.3",
|
||||
"phin": "^3.6.1"
|
||||
}
|
||||
}
|
70
src/bot.js
Normal file
70
src/bot.js
Normal file
@ -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();
|
6
src/cache.js
Normal file
6
src/cache.js
Normal file
@ -0,0 +1,6 @@
|
||||
const Redis = require("ioredis");
|
||||
|
||||
// TODO: get value from confini
|
||||
const cache = new Redis();
|
||||
|
||||
module.exports = cache;
|
30
src/request.js
Normal file
30
src/request.js
Normal file
@ -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 };
|
20
src/utils.js
Normal file
20
src/utils.js
Normal file
@ -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,
|
||||
};
|
148
yarn.lock
Normal file
148
yarn.lock
Normal file
@ -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"
|
Loading…
Reference in New Issue
Block a user