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

Update messages to pull emoji from env #5

Merged
merged 2 commits into from
Nov 7, 2024
Merged
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
3 changes: 2 additions & 1 deletion .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ DISCORD_CLIENT_SECRET=<discord_client_secret>
# DISCORD_GUILD_ID=<discord_guild_id>
# DISCORD_SUMMARY_CHANNEL=<discord_channel_id>
# DISCORD_MILESTONE_CHANNEL=<discord_channel_id>
# DISCORD_CAUSES_EMOJI=<discord_causes_emoji_object>
# DISCORD_CAUSES_EMOJI={"Wallace & Gromit's Grand Appeal":"", ...}
# DISCORD_REGULAR_EMOJI={"mascot":"","happy":"","hype":"","love":"","sad":""}
# WORKER_BASE_URL=<worker_base_url>

STATS_API_ENDPOINT=https://dashboard.jinglejam.co.uk/api/tiltify
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
2. Create your `.dev.vars` file.
- Copy `.dev.vars.example` and fill out the information from your Discord application, plus the ID of your test server/guild where you'll use the bot.
- Optionally, `DISCORD_SUMMARY_CHANNEL` + `DISCORD_MILESTONE_CHANNEL` can be set to channel IDs for testing the scheduled messages, as well as the `DISCORD_BOT_TOKEN` to send the messages as.
- Optionally, `DISCORD_CAUSES_EMOJI` can be set to a JSON object mapping the cause names to custom emoji Markdown (which can be [uploaded directly to the Discord application](https://discord.com/developers/docs/resources/emoji#emoji-object-applicationowned-emoji)).
- Optionally, `DISCORD_CAUSES_EMOJI` + `DISCORD_REGULAR_EMOJI` can be set to a JSON object mapping the cause names + regular emoji (see [`const regular` in `src/util/emoji.ts`](src/util//emoji.ts)) to custom emoji Markdown (which can be [uploaded directly to the Discord application](https://discord.com/developers/docs/resources/emoji#emoji-object-applicationowned-emoji)).
3. Authenticate with Wrangler by running `npx wrangler login`.
4. Update `wrangler.toml` for your account.
- Use `npx wrangler whoami` to get your account ID, update the value in `wrangler.toml` to match.
Expand All @@ -26,7 +26,7 @@ Ensure that the environment in `wrangler.toml` has been updated with your chosen

Ensure that the KV namespaces are created for staging/production environments and are configured in `wrangler.toml`. Use `npx wrangler kv:namespace create "STORE" -e <staging/production>`.

You'll also want to set `DISCORD_CLIENT_ID` + `DISCORD_PUBLIC_KEY` + `STATS_API_ENDPOINT` (optionally, `DISCORD_SUMMARY_CHANNEL` + `DISCORD_MILESTONE_CHANNEL` + `DISCORD_BOT_TOKEN` + `DISCORD_CAUSES_EMOJI` + `WORKER_BASE_URL`) as secrets for the worker, which you can do with `npx wrangler secret put <var name> -e <staging/production>` (the channel secrets can contain multiple IDs, separated by a comma).
You'll also want to set `DISCORD_CLIENT_ID` + `DISCORD_PUBLIC_KEY` + `STATS_API_ENDPOINT` (optionally, `DISCORD_SUMMARY_CHANNEL` + `DISCORD_MILESTONE_CHANNEL` + `DISCORD_BOT_TOKEN` + `DISCORD_CAUSES_EMOJI` + `DISCORD_REGULAR_EMOJI` + `WORKER_BASE_URL`) as secrets for the worker, which you can do with `npx wrangler secret put <var name> -e <staging/production>` (the channel secrets can contain multiple IDs, separated by a comma).

If you're deploying for local, make sure that you've got the appropriate environment variables set for `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET` + `DISCORD_GUILD_ID` (otherwise, they'll default to the values in `.dev.vars`).

Expand Down
18 changes: 8 additions & 10 deletions src/commands/causes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import getStats from "../util/stats";
import { error, loading, notStarted, thanks } from "../util/messages";
import getNow from "../util/now";
import { bold, number } from "../util/format";
import causesBreakdown, { parseEmoji } from "../util/causes";
import causesBreakdown from "../util/causes";
import type { CtxWithEnv } from "../env";
import { emojiRegular } from "../util/emoji";

const causesCommand: Command<CtxWithEnv> = {
name: "causes",
Expand All @@ -22,7 +23,7 @@ const causesCommand: Command<CtxWithEnv> = {

// Check if Jingle Jam is running
const start = new Date(stats.event.start);
const check = notStarted(start);
const check = notStarted(start, context.env);
if (check) return edit({ content: check });

// Check if Jingle Jam has finished
Expand All @@ -32,30 +33,27 @@ const causesCommand: Command<CtxWithEnv> = {

await edit({
content: [
`<:JingleJammy:1047503567981903894> Jingle Jam ${
`${emojiRegular(context.env, "mascot")} Jingle Jam ${
stats.event.year
} ${ended ? "supported" : "is supporting"} ${bold(
number(stats.causes.length),
)} amazing causes:`,
"",
causesBreakdown(
stats,
parseEmoji(context.env.DISCORD_CAUSES_EMOJI),
),
causesBreakdown(stats, context.env),
"",
thanks(end, stats.event.year),
thanks(end, stats.event.year, context.env),
].join("\n"),
});
})().catch(async (err) => {
console.error(err);
await edit({ content: error() });
await edit({ content: error(context.env) });
}),
);

return response({
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: loading(),
content: loading(context.env),
flags: MessageFlags.Ephemeral,
},
});
Expand Down
13 changes: 7 additions & 6 deletions src/commands/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { error, loading, notStarted, thanks } from "../util/messages";
import getNow from "../util/now";
import { bold, italic, money, number, timeSince } from "../util/format";
import type { CtxWithEnv } from "../env";
import { emojiRegular } from "../util/emoji";

const statsCommand: Command<CtxWithEnv> = {
name: "stats",
Expand All @@ -21,7 +22,7 @@ const statsCommand: Command<CtxWithEnv> = {

// Check if Jingle Jam is running
const start = new Date(stats.event.start);
const check = notStarted(start);
const check = notStarted(start, context.env);
if (check) return edit({ content: check });

// Check if Jingle Jam has finished
Expand Down Expand Up @@ -111,7 +112,7 @@ const statsCommand: Command<CtxWithEnv> = {
await edit({
content: [
bold(
`<:JingleJammy:1047503567981903894> Jingle Jam ${stats.event.year} Stats`,
`${emojiRegular(context.env, "mascot")} Jingle Jam ${stats.event.year} Stats`,
),
"",
`:money_with_wings: ${
Expand All @@ -126,7 +127,7 @@ const statsCommand: Command<CtxWithEnv> = {
ended ? " this year" : ""
}, supporting the ${countCauses} amazing causes.`,
"",
`<:Jammy_HAPPY:1047503540475674634> This year, ${collections} Games Collections ${
`${emojiRegular(context.env, "happy")} This year, ${collections} Games Collections ${
ended ? "were" : "have already been"
} redeemed, with the average donation being ${average}.`,
`:black_small_square: That works out to an average of ${perHourCollections} collections claimed per hour, or ${perDayCollections} collections per day.`,
Expand All @@ -135,7 +136,7 @@ const statsCommand: Command<CtxWithEnv> = {
`:scroll: Over the past ${historyYears} years, plus this year, we've raised a total of ${historyRaised} for charity!`,
`:black_small_square: Since ${historyOldest}, we've received ${historyDonations} charitable donations as part of Jingle Jam.`,
"",
thanks(end, stats.event.year),
thanks(end, stats.event.year, context.env),
"",
`:chart_with_upwards_trend: ${italic(
"Explore more stats at <https://jinglejam.co.uk/tracker>",
Expand All @@ -144,14 +145,14 @@ const statsCommand: Command<CtxWithEnv> = {
});
})().catch(async (err) => {
console.error(err);
await edit({ content: error() });
await edit({ content: error(context.env) });
}),
);

return response({
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: loading(),
content: loading(context.env),
flags: MessageFlags.Ephemeral,
},
});
Expand Down
13 changes: 7 additions & 6 deletions src/commands/total.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { error, loading, notStarted, thanks } from "../util/messages";
import getNow from "../util/now";
import { bold, money, number } from "../util/format";
import type { CtxWithEnv } from "../env";
import { emojiRegular } from "../util/emoji";

const totalCommand: Command<CtxWithEnv> = {
name: "total",
Expand All @@ -21,7 +22,7 @@ const totalCommand: Command<CtxWithEnv> = {

// Check if Jingle Jam is running
const start = new Date(stats.event.start);
const check = notStarted(start);
const check = notStarted(start, context.env);
if (check) return edit({ content: check });

// Check if Jingle Jam has finished
Expand Down Expand Up @@ -51,28 +52,28 @@ const totalCommand: Command<CtxWithEnv> = {

await edit({
content: [
`<:JingleJammy:1047503567981903894> ${totalRaised} ${
`${emojiRegular(context.env, "mascot")} ${totalRaised} ${
ended ? "was" : "has been"
} raised for charity during Jingle Jam ${
stats.event.year
}${ended ? "!" : " so far!"} `,
`<:Jammy_HAPPY:1047503540475674634> ${collections} Games Collections ${
`${emojiRegular(context.env, "happy")} ${collections} Games Collections ${
ended ? "were" : "have been"
} redeemed, and over all the years, we've now raised ${historyRaised}!`,
"",
thanks(end, stats.event.year),
thanks(end, stats.event.year, context.env),
].join("\n"),
});
})().catch(async (err) => {
console.error(err);
await edit({ content: error() });
await edit({ content: error(context.env) });
}),
);

return response({
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: loading(),
content: loading(context.env),
flags: MessageFlags.Ephemeral,
},
});
Expand Down
1 change: 1 addition & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Env {
DISCORD_SUMMARY_CHANNEL?: string;
DISCORD_MILESTONE_CHANNEL?: string;
DISCORD_CAUSES_EMOJI?: string;
DISCORD_REGULAR_EMOJI?: string;
WORKER_BASE_URL?: string;
STORE: KVNamespace;
}
Expand Down
11 changes: 4 additions & 7 deletions src/scheduled/milestone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { bold, money, number } from "../util/format";
import sendMessage from "../util/send";
import { thanks } from "../util/messages";
import type { Env } from "../env";
import { emojiRegular } from "../util/emoji";

const milestones = [
100_000, 500_000, 1_000_000, 1_500_000, 2_000_000, 2_500_000, 3_000_000,
Expand Down Expand Up @@ -51,16 +52,12 @@ const milestoneScheduled = async (

// Send the messages, in the background, with errors logged to the console
const content = [
`# <:Jammy_HYPE:1047503542212108360> ${money(
"£",
recentMilestone,
false,
)}`,
`# ${emojiRegular(env, "hype")} ${money("£", recentMilestone, false)}`,
"",
`<:JingleJammy:1047503567981903894> Jingle Jam ${stats.event.year} just hit a new milestone, with ${totalRaised} raised so far through the Yogscast and fundraisers.`,
`${emojiRegular(env, "mascot")} Jingle Jam ${stats.event.year} just hit a new milestone, with ${totalRaised} raised so far through the Yogscast and fundraisers.`,
`:black_small_square: There have already been ${collections} Games Collections claimed, and our ${countFundraisers} fundraisers have raised ${totalFundraisers}!`,
"",
thanks(new Date(stats.event.end), stats.event.year),
thanks(new Date(stats.event.end), stats.event.year, env),
].join("\n");
ctx.waitUntil(
Promise.all(
Expand Down
13 changes: 7 additions & 6 deletions src/scheduled/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { notStarted, thanks } from "../util/messages";
import getStats from "../util/stats";
import sendMessage from "../util/send";
import { bold, italic, money, number, timeSince } from "../util/format";
import causesBreakdown, { parseEmoji } from "../util/causes";
import causesBreakdown from "../util/causes";
import type { Env } from "../env";
import { emojiRegular } from "../util/emoji";

// Aim to post at 23:00 UTC every day
const target = () => {
Expand Down Expand Up @@ -47,7 +48,7 @@ const summaryScheduled = async (
// Get the stats, and check if Jingle Jam is running
const stats = await getStats(env.STATS_API_ENDPOINT);
const start = new Date(stats.event.start);
if (notStarted(start)) return;
if (notStarted(start, env)) return;

// Check the end, allowing for a final post after the end
const end = new Date(stats.event.end);
Expand All @@ -68,9 +69,9 @@ const summaryScheduled = async (

// Send the webhooks, in the background, with errors logged to the console
const content = [
`# <:JingleJammy:1047503567981903894> Jingle Jam ${stats.event.year} Day ${daysSinceLaunch} Summary`,
`# ${emojiRegular(env, "mascot")} Jingle Jam ${stats.event.year} Day ${daysSinceLaunch} Summary`,
"",
`<:Jammy_HAPPY:1047503540475674634> ${
`${emojiRegular(env, "happy")} ${
ended ? "We" : "We've"
} raised a total of ${totalRaised} for charity over the ${timeElapsed} of Jingle Jam ${
stats.event.year
Expand All @@ -81,9 +82,9 @@ const summaryScheduled = async (
ended ? "joined" : "have joined"
} to raise money for charity.`,
"",
causesBreakdown(stats, parseEmoji(env.DISCORD_CAUSES_EMOJI)),
causesBreakdown(stats, env),
"",
thanks(end, stats.event.year),
thanks(end, stats.event.year, env),
].join("\n");
ctx.waitUntil(
Promise.all(
Expand Down
44 changes: 4 additions & 40 deletions src/util/causes.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,13 @@
import type { Env } from "../env";
import { emojiCauses } from "./emoji";
import { bold, money } from "./format";
import type { Stats } from "./stats";

export const parseEmoji = (env?: string): Record<string, string> => {
try {
const raw = JSON.parse(env || "{}");
if (
typeof raw !== "object" ||
raw === null ||
Object.entries(raw).some(
([key, value]) =>
typeof key !== "string" || typeof value !== "string",
)
)
throw new Error("Invalid JSON");
return raw;
} catch (e) {
return {};
}
};

const hash = (str: string) => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0;
}
return hash;
};

const hearts = [
":heart:",
":orange_heart:",
":yellow_heart:",
":green_heart:",
":blue_heart:",
":purple_heart:",
];

const heart = (str: string) => hearts[Math.abs(hash(str)) % hearts.length];

const causesBreakdown = (stats: Stats, emoji: Record<string, string>) =>
const causesBreakdown = (stats: Stats, env: Env) =>
stats.causes
.map((cause) => {
return bold(
`${emoji[cause.name] || heart(cause.name)} [${cause.name}](${
`${emojiCauses(env, cause.name)} [${cause.name}](${
cause.url
}): ${money(
"£",
Expand Down
63 changes: 63 additions & 0 deletions src/util/emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Env } from "../env";

const parseEmoji = (env?: string): Record<string, string> => {
try {
const raw = JSON.parse(env || "{}");
if (
typeof raw !== "object" ||
raw === null ||
Object.entries(raw).some(
([key, value]) =>
typeof key !== "string" || typeof value !== "string",
)
)
throw new Error("Invalid JSON");
return raw;
} catch (e) {
return {};
}
};

const hash = (str: string) => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0;
}
return hash;
};

const hearts = [
":heart:",
":orange_heart:",
":yellow_heart:",
":green_heart:",
":blue_heart:",
":purple_heart:",
];

let emojiCausesCache: Record<string, string> | null = null;
export const emojiCauses = (env: Env, cause: string) => {
const obj =
emojiCausesCache ||
(emojiCausesCache = parseEmoji(env.DISCORD_CAUSES_EMOJI));
return obj[cause] || hearts[Math.abs(hash(cause)) % hearts.length];
};

const regular = {
mascot: ":snowflake:",
happy: ":smile:",
hype: ":partying_face:",
love: ":heart_eyes:",
sad: ":sob:",
};

let emojiRegularCache: Record<string, string> | null = null;
export const emojiRegular = (env: Env, type: keyof typeof regular) => {
if (!(type in regular)) throw new Error(`Invalid emoji type: ${type}`);

const obj =
emojiRegularCache ||
(emojiRegularCache = parseEmoji(env.DISCORD_REGULAR_EMOJI));
return obj[type] || regular[type as keyof typeof regular];
};
Loading
Loading