add: initial working poc
This commit is contained in:
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,
|
||||
};
|
||||
Reference in New Issue
Block a user