From 34b269754c124c86ace2913e50fa2334cb98ced6 Mon Sep 17 00:00:00 2001 From: Aaron Dalton Date: Tue, 8 Oct 2024 12:05:35 -0600 Subject: [PATCH] Add urgent notice to YourTurn emails. --- locales/en/apback.json | 3 +++ utils/yourturn.ts | 34 +++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/locales/en/apback.json b/locales/en/apback.json index d830ec0..14c54ee 100644 --- a/locales/en/apback.json +++ b/locales/en/apback.json @@ -36,8 +36,11 @@ "YourMove": "It is your turn to move.", "YourMoveBatchedBody_one": "It's your turn in {{ count }} game. Visit https://play.abstractplay.com to take your turns.", "YourMoveBatchedBody_other": "It's your turn in {{ count }} games. Visit https://play.abstractplay.com to take your turns.", + "YourMoveBatchedBodyUrgent_one": "It's your turn in {{ count }} game. At least one game has less than 24 hours left on the clock. Visit https://play.abstractplay.com to take your turns.", + "YourMoveBatchedBodyUrgent_other": "It's your turn in {{ count }} games. At least one game has less than 24 hours left on the clock. Visit https://play.abstractplay.com to take your turns.", "YourMoveBody": "It is now your move in a game of {{- metaGame}}. Please visit https://play.abstractplay.com/ for more details.", "YourMoveSubject": "AbstractPlay: your move", + "YourMoveSubjectUrgent": "AbstractPlay: your move (urgent)", "TournamentStartBody": "Tournament {{number}} of the {{- metaGame}} series has started. See https://play.abstractplay.com/tournaments. You are also registered for the next tournament in the series. It will start one week after this tournament ends.", "TournamentStartBodyVariants": "Tournament {{number}} of the {{- metaGame}}, variants: {{variants}} series has started. See https://play.abstractplay.com/tournaments. You are also registered for the next tournament in the series. It will start one week after this tournament ends.", "TournamentStartSubject": "Your {{- metaGame}} tournament has started", diff --git a/utils/yourturn.ts b/utils/yourturn.ts index ad7f3ad..119e5d4 100644 --- a/utils/yourturn.ts +++ b/utils/yourturn.ts @@ -2,7 +2,7 @@ import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DynamoDBDocumentClient, GetCommand, QueryCommand, } from '@aws-sdk/lib-dynamodb'; -import { SESClient } from '@aws-sdk/client-ses'; +import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'; import i18n from 'i18next'; import { Handler } from "aws-lambda"; import { createSendEmailCommand, logGetItemError, formatReturnError, initi18n, UserSettings } from '../api/abstractplay'; @@ -29,6 +29,7 @@ const ddbDocClient = DynamoDBDocumentClient.from(clnt, translateConfig); type PartialGame = { id: string; metaGame: string; + lastMoveTime: number; players: { id: string; name: string; @@ -59,7 +60,7 @@ export const handler: Handler = async (event: any, context?: any) => { KeyConditionExpression: "#pk = :pk and begins_with(#sk, :sk)", ExpressionAttributeValues: { ":pk": "GAME", ":sk": `${metaGame}#0#` }, ExpressionAttributeNames: { "#pk": "pk", "#id": "id", "#sk": "sk"}, - ProjectionExpression: "#id, metaGame, players, toMove", + ProjectionExpression: "#id, metaGame, players, toMove, lastMoveTime", ReturnConsumedCapacity: "INDEXES", }) ); @@ -115,7 +116,7 @@ export const handler: Handler = async (event: any, context?: any) => { // Get list of users const players: PartialUser[] = []; - const notifications: [PartialUser, number][] = []; + const notifications: {user: PartialUser, total: number, urgent: number}[] = []; try { for (const pid of p2g.keys()) { const data = await ddbDocClient.send( @@ -151,7 +152,17 @@ export const handler: Handler = async (event: any, context?: any) => { } if ( (player.email !== undefined) && (player.email !== null) && (player.email !== "") ) { if ( (player.settings?.all?.notifications === undefined) || (player.settings.all.notifications.yourturn) ) { - notifications.push([player, gs.length]); + let urgent = 0; + for (const game of gs) { + const playerEntry = game.players.find(x => x.id === p); + if (playerEntry !== undefined) { + const remaining = (playerEntry.time || 0) - (Date.now() - game.lastMoveTime); + if (remaining < 24 * 60 * 60 * 1000) { + urgent++; + } + } + } + notifications.push({user: player, total: gs.length, urgent}); } else { console.log(`Player ${player.name} (${player.id}) has elected to not receive YourTurn notifications.`); } @@ -176,19 +187,24 @@ export const handler: Handler = async (event: any, context?: any) => { // Sort by language to minimize locale changes notifications.sort((a, b) => { - if (a[0].language === b[0].language) { - return a[0].name.localeCompare(b[0].name); + if (a.user.language === b.user.language) { + return a.user.name.localeCompare(b.user.name); } else { - return a[0].language.localeCompare(b[0].language) + return a.user.language.localeCompare(b.user.language) } }); let lastlang: string|undefined = undefined; - for (const [p, n] of notifications) { + for (const {user: p, total: n, urgent} of notifications) { if (p.language !== lastlang) { lastlang = p.language; await i18n.changeLanguage(p.language); } - const comm = createSendEmailCommand(p.email, p.name, i18n.t("YourMoveSubject"), i18n.t("YourMoveBatchedBody", { count: n })); + let comm: SendEmailCommand; + if (urgent > 0) { + comm = createSendEmailCommand(p.email, p.name, i18n.t("YourMoveSubjectUrgent"), i18n.t("YourMoveBatchedBodyUrgent", { count: n, urgent })); + } else { + comm = createSendEmailCommand(p.email, p.name, i18n.t("YourMoveSubject"), i18n.t("YourMoveBatchedBody", { count: n })); + } work.push(sesClient.send(comm)); } await Promise.all(work);