Skip to content

Commit

Permalink
fit: add weekly fitness post
Browse files Browse the repository at this point in the history
  • Loading branch information
hellos3b committed Jan 30, 2024
1 parent 3ed0474 commit 6d9ba7c
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 14 deletions.
1 change: 0 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"eslint:recommended"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-namespace": 0,
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
"@typescript-eslint/ban-ts-comment": "off"
Expand Down
11 changes: 9 additions & 2 deletions bot/src/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { subscribe } from "./commands/subscribe/Subscribe";
import { throw_rps } from "./commands/throw/Throw";
import * as Meetup from "./commands/meetup/RegisterMeetup";
import * as Fit from "./commands/fit/Fit";
import { fitSlashConfig, fitInteractionHandler } from "@bored-bot/fitness";

// slash commands
import { aqi } from "./interactions/aqi";
Expand Down Expand Up @@ -58,10 +59,15 @@ const registerSlashCommands = async() => {
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, SERVER_ID } = env;

const rest = new REST ({ version: "9" }).setToken (DISCORD_TOKEN);

const interactionConfigs = [
fitSlashConfig.toJSON()
]

return rest.put (
Routes.applicationGuildCommands (DISCORD_CLIENT_ID, SERVER_ID),
{ body: interactions.flatMap (it => it.config) }
// @ts-ignore
{ body: interactions.flatMap (it => it.config).concat(interactionConfigs) }
).then (_ => { log.debug ("Slash Commands Registered"); });
};

Expand Down Expand Up @@ -135,7 +141,8 @@ const handleMessage = (message: Discord.Message) => {
const handleCommandInteraction = (interaction: Discord.ChatInputCommandInteraction, world: World) => {
const handlers = interactions
.filter (it => it.config.some (c => c.name === interaction.commandName))
.map (it => it.handle);
.map (it => it.handle)
.concat([fitInteractionHandler (world.mongodb)]);

handlers.forEach (f => f (interaction, world));
};
Expand Down
13 changes: 7 additions & 6 deletions bot/src/commands/fit/Fit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import { channels } from "../../deprecating/channels";
import * as Command from "../../deprecating/Command";
import * as Format from "../../deprecating/Format";

import * as Balance from "./Balance";
import * as Settings from "./Settings";
import * as Admin from "./Admin";
import * as UserAuth from "./UserAuth";
import * as UserProfile from "./UserProfile";
// import * as ActivityWebhook from "./ActivityWebhook";
import * as Promotions from "./Promotions";
import * as LastActive from "./LastActive";

// @ts-ignore
import * as Fit2 from "@bored-bot/fitness";
import {
stravaWebhookHandler,
schedulePost
} from "@bored-bot/fitness";

import { env } from "../../environment";
import { getInstance } from "../../deprecating/legacy_instance";
Expand All @@ -39,7 +39,7 @@ const fit = Command.filtered ({
Filter.inChannel (env.CHANNEL_STRAVA)
),

callback: message =>
callback: message =>
match (Command.route (message))
.with ("auth", () => UserAuth.onBoarding (message))
.with ("profile", () => UserProfile.render(message))
Expand Down Expand Up @@ -109,10 +109,11 @@ export const routes = (client: Discord.Client) => [
{
method: "POST",
path: "/fit/api/webhook",
handler: req => Fit2.stravaWebhookHandler (client, getInstance ().mongodb) (req)
handler: req => stravaWebhookHandler (client, getInstance ().mongodb) (req)
}
];

export const startup = (client: Discord.Client) : void => {
LastActive.beginFlush (client);
schedulePost(client, getInstance().mongodb);
};
1 change: 1 addition & 0 deletions packages/fitness/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"date-fns": "^2.28.0",
"discord.js": "^14.10.2",
"node-schedule": "^2.0.0",
"superagent": "^7.1.3"
},
"devDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions packages/fitness/src/EffortScore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const effortScore = (xp, score = 1) => {
const needs = score * 5;
return xp > needs ? effortScore(xp - needs, score + 1) : score + xp / needs;
};
65 changes: 65 additions & 0 deletions packages/fitness/src/MondayRecap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { startOfWeek } from "date-fns";
import { EmbedBuilder } from "discord.js";
import schedule from "node-schedule";
import { loggedWorkoutCollection } from "./LoggedWorkout";
import { effortScore } from "./EffortScore";

const unique = (arr) => Array.from(new Set(arr));
const notNull = (x) => !!x;

/**
* @param {import("discord.js").Client} discord
* @param {import("mongodb").Db} db
*/
export const createRecap = (discord, db) => async () => {
const workouts = loggedWorkoutCollection(db);
const monday = startOfWeek(new Date(), { weekStartsOn: 1 });

const workoutsThisWeek = await workouts
.find({ insertedDate: { $gt: monday.toISOString() } })
.toArray();

const discordIds = unique(workoutsThisWeek.map((x) => x.discordId));

const guild = await discord.guilds.fetch(process.env.SERVER_ID);
const members = await guild.members.fetch(discordIds);
await guild.members.fetch(discordIds);

return new EmbedBuilder({
color: 0x4287f5,
author: {
icon_url: "https://imgur.com/iRWWVZY.png",
name: "Monday Moist Recap",
},
description:
"Here's everyone's total 💦 score from the last week!\n\n" +
members
.filter((m) => workoutsThisWeek.some((x) => x.discordId === m.id))
.map((member) => {
const exp = workoutsThisWeek
.filter((x) => x.discordId === member.id)
.reduce((sum, a) => sum + a.exp, 0);

return {
username: member.nickname ?? member.user.username,
score: effortScore(exp),
};
})
.filter(notNull)
.sort((a, b) => (a.score > b.score ? -1 : 1))
.map((x) => `🔹 **${x.username}** ${x.score.toFixed(1)}`)
.join("\n"),
});
};

export const schedulePost = async (discord, db) => {
const create = createRecap(discord, db);

const post = async () => {
const embed = await create();
const channel = await discord.channels.fetch(process.env.CHANNEL_STRAVA);
await channel.send({ embeds: [embed] });
};

schedule.scheduleJob("0 9 * * MON", () => post());
};
6 changes: 1 addition & 5 deletions packages/fitness/src/PostWorkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

import { userCollection } from "./User";
import { loggedWorkoutCollection } from "./LoggedWorkout";
import { effortScore } from "./EffortScore";

const log = console.log.bind(console, "[fit/PostWorkout]");

Expand Down Expand Up @@ -36,11 +37,6 @@ const expFromStreams = (maxHR, streams) => {

const expFromTime = (duration) => duration / 60;

const effortScore = (xp, score = 1) => {
const needs = score * 5;
return xp > needs ? effortScore(xp - needs, score + 1) : score + xp / needs;
};

const workoutFromActivity = (user, activity, streams) => {
const exp = user.maxHR ? expFromStreams(user.maxHR, streams) : null;

Expand Down
29 changes: 29 additions & 0 deletions packages/fitness/src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
export { stravaWebhookHandler } from "./LoggedWorkout";
export { schedulePost } from "./MondayRecap";

import { PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
import { createRecap } from "./MondayRecap";

export const fitSlashConfig = new SlashCommandBuilder()
.setName("fit_admin")
.setDescription("foo bar")
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addSubcommand((cmd) =>
cmd
.setName("monday")
.setDescription("Preview the fitness bot's monday post."),
);

export const fitInteractionHandler = (db) => async (interaction) => {
if ("fit_admin" === interaction.commandName) {
const sub = interaction.options.getSubcommand();

if ("monday" === sub) {
const embed = await createRecap(interaction.client, db)();
interaction.reply({
content: "Here is a preview of the upcoming monday recap",
embeds: [embed],
ephemeral: true,
});
}
}
};
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit 6d9ba7c

Please sign in to comment.