Skip to content

Commit

Permalink
Merge pull request #416 from bettersg/develop
Browse files Browse the repository at this point in the history
3.2.4
  • Loading branch information
sarge1989 authored Aug 17, 2024
2 parents a6f42a3 + 7ee9b76 commit 002f92a
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 125 deletions.
254 changes: 130 additions & 124 deletions functions/src/definitions/eventHandlers/checkerHandlerTelegram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { getThresholds } from "../common/utils"
import { CheckerData } from "../../types"
import { message, callbackQuery } from "telegraf/filters"
import { isNumeric } from "../common/utils"
import { send } from "process"

const TOKEN = String(process.env.TELEGRAM_CHECKER_BOT_TOKEN)
const ADMIN_BOT_TOKEN = String(process.env.TELEGRAM_ADMIN_BOT_TOKEN)
Expand All @@ -28,14 +27,28 @@ const NLB_SURE_IMAGE =
const resources = `Here are some resources 📚 you might find useful:
1) <a href="https://checkmate.sg">Our official CheckMate website</a>
2) <a href="https://bit.ly/checkmates-wiki">Our fact-checking wiki</a>
3) <a href="https://bit.ly/checkmates-quiz">The Typeform quiz you just took</a>`
3) <a href="https://bit.ly/checkmates-quiz">The Typeform quiz you just took</a>
4) <a href="https://www.nlb.gov.sg/main/site/learnx/explore-communities/explore-communities-content/sure-learning-community">The S.U.R.E Learning Community</a> that we're partnering the National Library Board on`

if (!admin.apps.length) {
admin.initializeApp()
}

const db = admin.firestore()

function progressBars(currentStep: number) {
const totalSteps = 6
let progress = "Onboarding Progress: "
for (let i = 0; i < totalSteps; i++) {
if (i < currentStep) {
progress += "🟩"
} else {
progress += "⬜"
}
}
return progress
}

// COMMAND HANDLERS

bot.command("start", async (ctx) => {
Expand Down Expand Up @@ -226,108 +239,99 @@ bot.on(message("text"), async (ctx) => {
await ctx.reply("Sorry, this command is not supported.")
return
}
if (!msg.reply_to_message) {
// Ignore commands as they are handled separately
await ctx.reply(
"Sorry, this bot is unable to respond to free-form messages. Please reply to the last message asking for either your name, phone number, or OTP to continue the onboarding flow."
)
} else if (msg.text && msg.reply_to_message) {
const checkerId = msg.from?.id
const chatId = msg.chat.id

const userQuerySnap = await db
.collection("checkers")
.where("telegramId", "==", checkerId)
.get()
const checkerId = msg.from?.id
const chatId = msg.chat.id

const userSnap = userQuerySnap.docs[0]

if (
userSnap.data().lastTrackedMessageId == msg.reply_to_message.message_id
) {
let currentStep = userSnap.data().onboardingStatus
let whatsappId = ""

switch (currentStep) {
case "name":
const name = msg.text
if (name.replace(/\s+/g, "").length === 0) {
await ctx.reply(
"Name cannot be just spaces. Please enter a valid name."
)
await sendNamePrompt(chatId, userSnap)
return
}
await userSnap.ref.update({
name,
})
await sendNumberPrompt(chatId, userSnap)
break
case "number":
whatsappId = msg.text.replace("+", "").replace(" ", "")

if (
whatsappId.length === 8 &&
(whatsappId.startsWith("9") || whatsappId.startsWith("8"))
) {
whatsappId = `65${whatsappId}`
}
//check if whatsappId is numeric
if (!isNumeric(whatsappId)) {
await ctx.reply(
`The phone number you entered is invalid. Please enter a valid phone number.`
)
return
}
const userQuerySnap = await db
.collection("checkers")
.where("telegramId", "==", checkerId)
.get()

await userSnap.ref.update({
whatsappId,
})
const userSnap = userQuerySnap.docs[0]
let currentStep = userSnap.data().onboardingStatus
const expectingRepliesTo = ["name", "number", "verify"]
if (!expectingRepliesTo.includes(currentStep)) {
await ctx.reply(
"Sorry, this bot is unable to respond to free-form messages."
)
} else if (msg.text) {
let whatsappId = ""

switch (currentStep) {
case "name":
const name = msg.text
if (name.replace(/\s+/g, "").length === 0) {
await ctx.reply(
"Name cannot be just spaces. Please enter a valid name."
)
await sendNamePrompt(chatId, userSnap)
return
}
await userSnap.ref.update({
name,
})
await sendNumberPrompt(chatId, userSnap)
break
case "number":
whatsappId = msg.text.replace("+", "").replace(" ", "")

if (
whatsappId.length === 8 &&
(whatsappId.startsWith("9") || whatsappId.startsWith("8"))
) {
whatsappId = `65${whatsappId}`
}
//check if whatsappId is numeric
if (!isNumeric(whatsappId)) {
await ctx.reply(
`The phone number you entered is invalid. Please enter a valid phone number.`
)
return
}

await sendOTPPrompt(chatId, userSnap, whatsappId)
break
case "verify":
const otpAttempt = msg?.text || ""
whatsappId = userSnap.data().whatsappId

const result = await checkOTP(otpAttempt, whatsappId, userSnap.id)

const status = result.status
const message = result.message
try {
if (status === "success") {
await userSnap.ref.update({
whatsappId,
onboardingStatus: "quiz",
lastTrackedMessageId: null,
})
await sendQuizPrompt(chatId, userSnap, true)
await userSnap.ref.update({
whatsappId,
})

await sendOTPPrompt(chatId, userSnap, whatsappId)
break
case "verify":
const otpAttempt = msg?.text || ""
whatsappId = userSnap.data().whatsappId

const result = await checkOTP(otpAttempt, whatsappId, userSnap.id)

const status = result.status
const message = result.message
try {
if (status === "success") {
await userSnap.ref.update({
whatsappId,
onboardingStatus: "quiz",
lastTrackedMessageId: null,
})
await sendQuizPrompt(chatId, userSnap, true)
} else {
if (message === "OTP mismatch") {
await sendVerificationPrompt(chatId, userSnap, true)
} else if (message === "OTP max attempts") {
await ctx.reply(
`Maximum OTP attempts reached. We will send a new OTP.`
)
await sendOTPPrompt(chatId, userSnap, whatsappId)
} else {
if (message === "OTP mismatch") {
await sendVerificationPrompt(chatId, userSnap, true)
} else if (message === "OTP max attempts") {
await ctx.reply(
`Maximum OTP attempts reached. We will send a new OTP.`
)
await sendOTPPrompt(chatId, userSnap, whatsappId)
} else {
console.error(`OTP error with ${chatId}: ${message}`)
await ctx.reply(
"Apologies - an error occurred, please try again later."
)
}
console.error(`OTP error with ${chatId}: ${message}`)
await ctx.reply(
"Apologies - an error occurred, please try again later."
)
}
break
} catch (error) {
logger.log("Error in OTP verification", error)
}
default:
logger.log("Unhandled onboarding stage: ", currentStep)
}
} else {
await ctx.reply(
"Sorry, we don't support replies to messages except in certain cases. Please reply to the last message asking for either your name, phone number, or OTP to continue the onboarding flow."
)
break
} catch (error) {
logger.log("Error in OTP verification", error)
}
default:
logger.log("Unhandled onboarding stage: ", currentStep)
}
}
} catch (error) {
Expand Down Expand Up @@ -355,7 +359,9 @@ bot.on(callbackQuery("data"), async (ctx) => {
if (checkerDocSnap.data()?.isQuizComplete) {
isUser = await checkCheckerIsUser(whatsappId)
ctx.reply(
"Thank you for completing the quiz! We hope you found it useful."
`Thank you for completing the quiz!💪🎉 We hope you found it useful.
${progressBars(3)}`
)
if (isUser) {
await sendTGGroupPrompt(chatId, checkerDocSnap, true)
Expand All @@ -369,7 +375,9 @@ bot.on(callbackQuery("data"), async (ctx) => {
case "WA_COMPLETED":
isUser = await checkCheckerIsUser(whatsappId)
if (isUser) {
ctx.reply("Thank you for onboarding to the WhatsApp service!")
ctx.reply(`Thank you for onboarding to the WhatsApp service! 🙌
${progressBars(4)}`)
await sendTGGroupPrompt(chatId, checkerDocSnap, true)
} else {
await sendWABotPrompt(chatId, checkerDocSnap, false)
Expand Down Expand Up @@ -400,9 +408,9 @@ bot.on(callbackQuery("data"), async (ctx) => {
case "SEND_OTP":
await sendOTPPrompt(chatId, checkerDocSnap, whatsappId)
break
case "VERIFY_OTP":
await sendVerificationPrompt(chatId, checkerDocSnap, false)
break
// case "VERIFY_OTP":
// await sendVerificationPrompt(chatId, checkerDocSnap, false)
// break
case "ONBOARD_AGAIN":
await checkerDocSnap.ref.update({
onboardingStatus: "name",
Expand All @@ -424,7 +432,7 @@ const sendNamePrompt = async (
) => {
const namePrompt = await bot.telegram.sendMessage(
chatId,
"First up, how shall we address you?",
`First up, how shall we address you?`,
{
reply_markup: { force_reply: true },
}
Expand All @@ -441,7 +449,9 @@ const sendNumberPrompt = async (
) => {
const numberPrompt = await bot.telegram.sendMessage(
chatId,
`What is your WhatsApp phone number? Please include the country code, but omit the "+", e.g 6591234567`,
`What is your WhatsApp phone number? Please include the country code, but omit the "+", e.g 6591234567
${progressBars(1)}`,
{
reply_markup: { force_reply: true },
}
Expand All @@ -468,22 +478,14 @@ const sendOTPPrompt = async (
if (postOTPHandlerRes.status === "success") {
await bot.telegram.sendMessage(
chatId,
`We have sent a 6-digit OTP to your WhatsApp at +${whatsappId}. Please check your WhatsApp for the OTP, and hit "Verify OTP" below.`,
`We have sent a 6-digit OTP to your WhatsApp at +${whatsappId}. Please check your WhatsApp for the OTP.`,
{
reply_markup: {
inline_keyboard: [
[
inlineButtons.verifyOTP,
inlineButtons.resendOTP,
inlineButtons.rekey,
],
],
inline_keyboard: [[inlineButtons.resendOTP, inlineButtons.rekey]],
},
}
)
await checkerSnap.ref.update({
onboardingStatus: "otpSent",
})
sendVerificationPrompt(chatId, checkerSnap, false)
} else {
switch (postOTPHandlerRes.message) {
case "OTP request limit exceeded":
Expand Down Expand Up @@ -552,7 +554,9 @@ const sendQuizPrompt = async (
isFirstPrompt
? "Thank you for verifying your WhatsApp number"
: "We noticed you have not completed the quiz yet"
}. Please proceed to complete the onboarding quiz <a href="${linkURL}">here</a>. This will equip you with the skills and knowledge to be a better checker!`,
}. Please proceed to complete the onboarding quiz <a href="${linkURL}">here</a>. This will equip you with the skills and knowledge to be a better checker!
${progressBars(2)}`,
{
reply_markup: {
inline_keyboard: [
Expand Down Expand Up @@ -648,7 +652,9 @@ const sendNLBPrompt = async (chatId: number, checkerSnap: DocumentSnapshot) => {
await bot.telegram.sendPhoto(chatId, NLB_SURE_IMAGE, {
caption: `One last thing - CheckMate is partnering with the National Library Board to grow a vibrant learning community aimed at safeguarding the community from scams and misinformation.
If you'd like to get better at fact-checking, or if you're keen to meet fellow checkers in person, do check out and join the <a href="https://www.nlb.gov.sg/main/site/learnx/explore-communities/explore-communities-content/sure-learning-community">SURE Learning Community</a>. It'll be fun!`,
If you'd like to get better at fact-checking, or if you're keen to meet fellow checkers in person, do check out and join the <a href="https://www.nlb.gov.sg/main/site/learnx/explore-communities/explore-communities-content/sure-learning-community">SURE Learning Community</a>. It'll be fun!
${progressBars(5)}`,
parse_mode: "HTML",
reply_markup: {
inline_keyboard: [
Expand All @@ -669,15 +675,11 @@ const sendCompletionPrompt = async (
})
await bot.telegram.sendMessage(
chatId,
`You have now successfully onboarded as a checker! You will now receive notifications when there are messages that need checking.
Feel free to explore the Checker's Portal below, or by pressing the button at the bottom left of the screen. Here, you can view the leaderboard, your accuracy, and more.
`Finally, check out the Checker's Portal below, which is where you will vote on messages, and see the leaderboard and your statistics.
${resources}
You may view these resources with the command /resources.
`,
You may view these resources with the command /resources.`,
{
reply_markup: {
inline_keyboard: [
Expand All @@ -692,6 +694,10 @@ You may view these resources with the command /resources.
parse_mode: "HTML",
}
)
await bot.telegram.sendMessage(
chatId,
`Hooray! You've now successfully onboarded as a Checker! 🥳 You can chill for now, but stay tuned - you'll receive notifications in this chat when users submit messages for checking. You'll then do the fact-checks on the Checkers' Portal.`
)
}

const checkCheckerIsUser = async (whatsappId: string) => {
Expand Down
Loading

0 comments on commit 002f92a

Please sign in to comment.