Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.8.3 #203

Merged
merged 10 commits into from
Dec 4, 2023
Merged

2.8.3 #203

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions documentation/dataSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,25 @@ erDiagram
number referralCount
map utm "map containing utm parameters, source, medium, content, campaign, term"
string language "en or cn, users preferred language"
boolean isSubscribedUpdates "whether to blast msgs to this user"
}

blasts {
string type "image or text"
string text "text or caption (if image)"
string storageUrl "image storage url if applicable"
boolean isActive "the one that should be sent"
timestamp createdDate "is active"
timestamp blastDate ""
collection recipients
}

recipients {
string id PK "phone number"
string feebackCategory "positive, negative or neutral"
timestamp sentTimestamp "when blast was sent to user"
}

message ||--|{ instance: has
user ||--|{ instance: sends
message ||--o{ voteRequest: triggers
Expand Down
1 change: 1 addition & 0 deletions functions/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"firebase": "^9.15.0",
"firebase-admin": "^11.4.1",
"firebase-functions": "^4.5.0",
"google-auth-library": "^9.2.0",
"googleapis": "^112.0.0",
"hashids": "^2.3.0",
"image-hash": "^5.3.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { defineString } from "firebase-functions/params"
import axios, { AxiosError } from "axios"
import * as functions from "firebase-functions"
import { GoogleAuth } from "google-auth-library"

const embedderHost = defineString("EMBEDDER_HOST")
const env = process.env.ENVIRONMENT

interface EmbedResponse {
embedding: number[]
Expand Down Expand Up @@ -93,15 +95,42 @@ async function performOCR(url: string): Promise<camelCasedOCRResponse> {
return camelCaseResponse
}

async function getGoogleIdentityToken(audience: string) {
try {
const auth = new GoogleAuth()
const client = await auth.getIdTokenClient(audience)
const idToken = await client.idTokenProvider.fetchIdToken(audience)
return idToken
} catch (error) {
if (env === "SIT" || env === "DEV") {
functions.logger.log(
"Unable to get Google identity token in lower environments"
)
return ""
} else {
if (error instanceof AxiosError) {
functions.logger.error(error.message)
} else {
functions.logger.error(error)
}
throw new Error("Unable to get Google identity token in prod environment")
}
}
}

async function callAPI<T>(endpoint: string, data: object) {
try {
const hostName = embedderHost.value()
// Fetch identity token
const identityToken = await getGoogleIdentityToken(hostName)
const response = await axios<T>({
method: "POST", // Required, HTTP method, a string, e.g. POST, GET
url: `${embedderHost.value()}/${endpoint}`,
url: `${hostName}/${endpoint}`,
data: data,
headers: {
"Content-Type": "application/json",
apikey: process.env.ML_SERVER_TOKEN ?? "",
Authorization: `Bearer ${identityToken}`,
//apikey: process.env.ML_SERVER_TOKEN ?? "",
},
})
return response
Expand Down
44 changes: 42 additions & 2 deletions functions/src/definitions/common/parameters/userResponses.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"cn": "目前,{%voted}}%的查哥查妹已经对这条短信进行投票评估。但是,他们无法达成一致,或缺乏足够的信息进行评估。\n\n如果您能提供更多信息,例如发信人电话号码或截图,将有助于我们更好地评估。"
},
"INTERIM_PROMPT": {
"en": "Thanks for waiting! We are currently still pending the assessment from some of our network of trusted CheckMate volunteers and will only be able to provide a credible final result once enough votes have come in. \n\nYou may press the button below *to get an interim update of the preliminary result*. However, do note that there may be discrepancies between the preliminary and the final result, and *the preliminary result should be interpreted with caution*. We appreciate your patience and hope to deliver the final result to you soon! 💪🏼",
"en": "Thanks for waiting! Our network of trusted CheckMate volunteers is still assessing your message.\n\nYou may press the button below to see how our volunteers have rated your message so far. However, *do interpret this preliminary rating with caution* as the final result may differ. \n\nWe appreciate your patience and hope to deliver the final result soon!",
"cn": "感谢您的耐心等待!查哥查妹正在对您提交的短信进行投票评估。我们将在足够多的查哥查妹投票后,为您提供最终结果。\n\n在最终结果发布之前,您可以点击“获取初步结果”。请注意,初步结果和最终结果可能存在差异,应谨慎解读初步结果。我们感谢您的耐心,并会尽快提供最终结果!💪🏼"
},
"ALREADY_REPLIED": {
Expand Down Expand Up @@ -151,6 +151,14 @@
"en": "Here's our contact! Do add us to your contact list so you can find us in future. 😊",
"cn": "这是我们的联系方式!请将我们添加到您的联系人列表中,以便您以后能找到我们。😊"
},
"UNSUBSCRIBE": {
"en": "Sorry to see you go.😞 If you change your mind, you can always come back to the menu to subscribe again!",
"cn": "很遗憾看到您离开。😞 如果您改变主意,您可以随时回到菜单重新订阅!"
},
"SUBSCRIBE": {
"en": "Thanks for subscribing!🙏🏻 CheckMate will now send you content occasionally. Hope you find it useful!",
"cn": "感谢您的订阅!查友将偶尔向您发送内容。希望您觉得它有用!"
},
"REFERRAL": {
"en": "Have you started checking and reporting suspicious messages using CheckMate yet? Sign up by clicking this link and sending in the pre-loaded message!! {{link}}",
"cn": "您是否已经开始使用查友?点击此链接进行注册!{{link}}"
Expand Down Expand Up @@ -183,14 +191,18 @@
"en": "*This is an experimental feature powered by generative AI*. Do let us know if it was useful below!\n \n{{rationalisation}}",
"cn": "这是一项由AI技术提供的实验性功能。请在下方告诉我们它是否对您有所帮助!\n\n{{rationalisation}}"
},
"RATIONALISATION_USEFUL": {
"FEEDBACK_THANKS": {
"en": "Thanks for your valuable feedback!",
"cn": "感谢您的反馈!"
},
"RATIONALISATION_NOT_USEFUL": {
"en": "Sorry to hear that, but thanks anyway for your valuable feedback!",
"cn": "很遗憾此功能对您来说不是很有用。 查友已将您的反馈传递给我们的产品团队,以便进一步增强此功能。感谢您的反馈!"
},
"BLAST_FEEDBACK": {
"en": "Did you find this content useful?\n\nIf you wish to unsubscribe, type \"menu\" and select the \"Unsubscribe\" option",
"cn": "是否认为这份内容对您有帮助?\n\n如果您希望取消订阅,请输入“菜单”并选择“取消订阅”选项。"
},
"MENU_BUTTON": {
"en": "Menu",
"cn": "菜单"
Expand Down Expand Up @@ -259,6 +271,22 @@
"en": "Select Language / 选择语言",
"cn": "Select Language / 选择语言"
},
"MENU_TITLE_UNSUB": {
"en": "Unsubscribe",
"cn": "取消订阅"
},
"MENU_DESCRIPTION_UNSUB": {
"en": "Unsubscribe from receiving content from CheckMate",
"cn": "取消订阅查友不时内容"
},
"MENU_TITLE_SUB": {
"en": "Subscribe",
"cn": "订阅"
},
"MENU_DESCRIPTION_SUB": {
"en": "Subscribe to occasional content from CheckMate",
"cn": "订阅查友不时内容"
},
"MENU_DESCRIPTION_NPS_LIKELY": {
"en": "Extremely likely 🤩",
"cn": "一定会🤩"
Expand Down Expand Up @@ -307,6 +335,18 @@
"en": "How'd we tell?",
"cn": "我们是如何判断的?"
},
"BUTTON_SHIOK": {
"en": "😍",
"cn": "😍"
},
"BUTTON_MEH": {
"en": "😐",
"cn": "😐"
},
"BUTTON_BOO": {
"en": "😞",
"cn": "😞"
},
"PLACEHOLDER_SCAM": {
"en": "a scam🚫",
"cn": "是诈骗信息🚫"
Expand Down
2 changes: 1 addition & 1 deletion functions/src/definitions/common/pubsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const env = process.env.ENVIRONMENT

let pubsub: PubSub

if (env === "PROD") {
if (env === "PROD" || env === "UAT") {
pubsub = new PubSub()
} else {
pubsub = new PubSub({
Expand Down
122 changes: 121 additions & 1 deletion functions/src/definitions/common/responseUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import USER_BOT_RESPONSES from "./parameters/userResponses.json"
import CHECKER_BOT_RESPONSES from "./parameters/checkerResponses.json"
import {
sendWhatsappButtonMessage,
sendWhatsappImageMessage,
sendWhatsappTextListMessage,
sendWhatsappTextMessage,
} from "./sendWhatsappMessage"
import { DocumentSnapshot, Timestamp } from "firebase-admin/firestore"
import { getThresholds, sleep } from "./utils"
import { getSignedUrl } from "./mediaUtils"
import { sendTextMessage } from "./sendMessage"
import { getCount } from "./counters"

Expand Down Expand Up @@ -92,7 +94,7 @@ async function respondToRationalisationFeedback(
let response
switch (isUseful) {
case "yes":
response = responses?.RATIONALISATION_USEFUL
response = responses?.FEEDBACK_THANKS
await instanceRef.update({ isRationalisationUseful: true })
break
default:
Expand All @@ -104,13 +106,28 @@ async function respondToRationalisationFeedback(
await sendWhatsappTextMessage("user", from, response)
}

async function respondToBlastFeedback(
blastPath: string,
feedbackCategory: string,
from: string
) {
const blastFeedbackRef = db.doc(blastPath).collection("recipients").doc(from)
const responses = await getResponsesObj("user", from)
blastFeedbackRef.update({
feebackCategory: feedbackCategory,
})
await sendWhatsappTextMessage("user", from, responses.FEEDBACK_THANKS)
}

async function sendMenuMessage(
to: string,
prefixName: string,
platform = "whatsapp",
replyMessageId: string | null = null,
disputedInstancePath: string | null = null
) {
const userSnap = await db.collection("users").doc(to).get()
const isSubscribedUpdates = userSnap.get("isSubscribedUpdates") ?? false
const responses = await getResponsesObj("user", to)
if (!(prefixName in responses)) {
functions.logger.error(`prefixName ${prefixName} not found in responses`)
Expand Down Expand Up @@ -162,6 +179,17 @@ async function sendMenuMessage(
title: responses.MENU_TITLE_CONTACT,
description: responses.MENU_DESCRIPTION_CONTACT,
},
{
id: isSubscribedUpdates
? `${type}_unsubscribeUpdates`
: `${type}_subscribeUpdates`,
title: isSubscribedUpdates
? responses.MENU_TITLE_UNSUB
: responses.MENU_TITLE_SUB,
description: isSubscribedUpdates
? responses.MENU_DESCRIPTION_UNSUB
: responses.MENU_DESCRIPTION_SUB,
},

//TODO: Implement these next time
// {
Expand Down Expand Up @@ -845,6 +873,96 @@ async function sendLanguageSelection(user: string, newUser: boolean) {
await sendWhatsappButtonMessage("user", user, response, buttons)
}

async function sendBlast(user: string) {
const blastQuerySnap = await db
.collection("blasts")
.where("isActive", "==", true)
.orderBy("createdDate", "desc") // Order by createdDate in descending order
.limit(1) // Limit to 1 document
.get()
const responses = await getResponsesObj("user", user)
if (blastQuerySnap.empty) {
functions.logger.warn(
`No active blast found when attempting to send blast to user ${user}`
)
await sendTextMessage("user", user, responses.GENERIC_ERROR)
return
}
const blastSnap = blastQuerySnap.docs[0]
const blastData = blastSnap.data()
switch (blastData.type) {
case "image":
if (!blastData.storageUrl) {
functions.logger.error(
`No image url found for blast ${blastSnap.ref.path}`
)
await sendTextMessage("user", user, responses.GENERIC_ERROR)
return
} else {
//send image to user
const signedUrl = await getSignedUrl(blastData.storageUrl)
await sendWhatsappImageMessage(
"user",
user,
null,
signedUrl,
blastData.text ?? null,
null
)
}
break
case "text":
if (!blastData.text) {
functions.logger.error(`No text found for blast ${blastSnap.ref.path}`)
await sendTextMessage("user", user, responses.GENERIC_ERROR)
return
} else {
//send text to user
await sendTextMessage("user", user, blastData.text)
}
break
}
const buttons = [
{
type: "reply",
reply: {
id: `feedbackBlast_${blastSnap.ref.path}_negative`,
title: responses.BUTTON_BOO,
},
},
{
type: "reply",
reply: {
id: `feedbackBlast_${blastSnap.ref.path}_neutral`,
title: responses.BUTTON_MEH,
},
},
{
type: "reply",
reply: {
id: `feedbackBlast_${blastSnap.ref.path}_positive`,
title: responses.BUTTON_SHIOK,
},
},
]
await blastSnap.ref
.collection("recipients")
.doc(user)
.set(
{
feebackCategory: null,
sentTimestamp: Timestamp.fromDate(new Date()),
},
{ merge: true }
)
await sendWhatsappButtonMessage(
"user",
user,
responses.BLAST_FEEDBACK,
buttons
)
}

export {
getResponsesObj,
respondToInstance,
Expand All @@ -857,4 +975,6 @@ export {
respondToRationalisationFeedback,
updateLanguageAndSendMenu,
sendLanguageSelection,
sendBlast,
respondToBlastFeedback,
}
3 changes: 2 additions & 1 deletion functions/src/definitions/eventHandlers/onMessageUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ const onMessageUpdate = functions
}
if (
before.data().primaryCategory !== primaryCategory &&
primaryCategory === "legitimate"
primaryCategory === "legitimate" &&
text
) {
const anonymisedText = await anonymiseMessage(text, false)
await after.ref.update({
Expand Down
Loading