Telegram Bot
Draft!!
A couple of weeks ago my wife and I had a lovely pair of twins. They had a few major complications on the journey, which I may post about later but thanks to the NHS the little ones are doing well. Photos are available by request. This has been the reason I have posted so little in the past few months.
I am now in the middle of my paternity leave and itching to do something technical. My wife at the same time got frustrated working out which baby we had fed at which time, because no matter how hard we try they have their own routings. She asked me to help here.
Queue my desire to write a telegram bot that anyone can use to do simple recordings. Please bear in mind this was written in about 20mins at 4am between feeds.
For simplicity I decided to write this in Node and I chose to use Telegram because I did not want to build my own app, but also wanted to extend it in the future. I haven’t found another messaging app that allows for buts so simply.
Spec:
- Buttons to allow common values to be submitted.
- A command to store how much, how long and which child was fed.
f B 100 15
(feed, babyb, 100ml, 15min, feed) - A command to retrieve the last feed time for each child.
last
The docs for node-telegram-bot-api are great - https://www.npmjs.com/package/node-telegram-bot-api. I will not repeat everything they have written but I will call out some key things.
bot.onText(/[Ff] ([AaCcDd]) ([0-9][0-9]+) ([0-9]+)$/, (msg, match) => {
console.log("In single name with quantity");
const chatId = msg.chat.id;
reply(bot, chatId, "Processing");
processRequest(match[1], msg.date * 1000, match[2], match[3], (msg) => {
console.log(msg);
reply(bot, chatId, msg);
})
});
Using the regex [Ff] ([AaBbDd]) ([0-9][0-9]+) ([0-9]+)$
we can get the child, the milk quantity and the time it took to feed.
function reply(bot, chatId, msg) {
console.log("In Reply");
bot.sendMessage(chatId, msg, {
"reply_markup": {
"keyboard": [
["last"],
["f B 100 15", "f A 100 15"],
["f B 100 30", "f A 100 30"],
["f B 100 45", "f A 100 45"]
]
}
});
}
The above generates the buttons on the telegram client.
The full sample code is below.
Complete Code
const TelegramBot = require('node-telegram-bot-api');
const GoogleSpreadsheet = require('google-spreadsheet').GoogleSpreadsheet;
const JWT = require('google-auth-library').JWT;
const token = '<TelegramToken>';
const bot = new TelegramBot(token, {
polling: true
});
bot.onText(/last/i, (msg, match) => {
const chatId = msg.chat.id;
reply(bot, chatId, "Getting Last Feed");
getLastFeed((msg) => {
reply(bot, chatId, msg);
})
});
bot.onText(/[Ff] ([AaBbDd]) ([0-9][0-9]+) ([0-9]+)$/, (msg, match) => {
console.log("In single name with quantity");
const chatId = msg.chat.id;
reply(bot, chatId, "Processing");
processRequest(match[1], msg.date * 1000, match[2], match[3], (msg) => {
console.log(msg);
reply(bot, chatId, msg);
})
});
bot.on('message', (msg) => {
const chatId = msg.chat.id;
reply(bot, chatId, 'Received your message');
});
function reply(bot, chatId, msg) {
console.log("In Reply");
bot.sendMessage(chatId, msg, {
"reply_markup": {
"keyboard": [
["last"],
["f B 100 15", "f A 100 15"],
["f B 100 30", "f A 100 30"],
["f B 100 45", "f A 100 45"]
]
}
});
}
function processRequest(baby, time, quantity, feedlength, cb) {
console.log("In processRequest", baby);
if (baby.toLowerCase() === "a") {
baby = "Baby1"
console.log("SENDING TO GOOGLE SHEETS WITH, ", baby, time, quantity, feedlength);
publish(baby, time, quantity, feedlength, cb)
} else if (baby.toLowerCase() === "b") {
baby = "Baby2"
console.log("SENDING TO GOOGLE SHEETS WITH, ", baby, time, quantity, feedlength);
publish(baby, time, quantity, feedlength, cb);
} else if (baby.toLowerCase() === "d") {
baby = "DEBUG"
console.log("DEBUG - SENDING TO GOOGLE SHEETS WITH, ", baby, time, quantity), feedlength;
publish(baby, time, quantity, feedlength, cb);
} else {
console.log("In processRequest - other ", baby);
cb("Incorrect Baby Name, please use B or A, given " + baby);
}
}
async function connect(cb) {
const serviceAccountAuth = new JWT({
// env var values here are copied from service account credentials generated by google
// see "Authentication" section in docs for more info
email: "<EMAIL>",
key: "<KEY>",
scopes: [
'https://www.googleapis.com/auth/spreadsheets',
],
});
const doc = new GoogleSpreadsheet('<Sheet ID>', serviceAccountAuth);
await doc.loadInfo(); // loads document properties and worksheets
await doc.updateProperties({
title: 'Baby Feeding'
});
const sheet = doc.sheetsByIndex[0]; // or use `doc.sheetsById[id]` or `doc.sheetsByTitle[title]`
cb(sheet);
}
async function publish(baby, time, quantity, feedlength, cb) {
time = time - (feedlength * 60000)
connect(async (sheet) => {
await sheet.addRow({
name: baby,
time: time,
quantity: quantity,
feedlength: feedlength
});
cb("Recorded");
})
}
async function getLastFeed(cb) {
connect(async (sheet) => {
const rows = await sheet.getRows(); // can pass in { limit, offset }
let cFlag = false;
let aFlag = false;
let aMessage = "unset";
let cMessage = "unset";
for (let i = rows.length - 1; i >= rows.length - 40; i--) {
if (!aFlag) {
if (rows[i].get('name') == "Baby1") {
aFlag = true;
var d = new Date(0); // The 0 there is the key, which sets the date to the epoch
d.setUTCSeconds(rows[i].get('time') / 1000);
aMessage = "Baby1 was fed on " + d
}
}
if (!cFlag) {
if (rows[i].get('name') == "Baby2") {
cFlag = true;
var d = new Date(0); // The 0 there is the key, which sets the date to the epoch
d.setUTCSeconds(rows[i].get('time') / 1000);
cMessage = "Baby2 was fed on " + d
}
}
}
cb(aMessage + "\n" + cMessage);
})
}