providing password reset services for a long while: circa 2025

feat: add summary command

Changed files
+243 -27
features
+135 -25
features/command.ts
···
import { slackApp } from "../index";
const command = async () => {
-
slackApp.command("/hackatime", async ({ context }) => {
const hackatimeUser: { apiKey: string } | null = await fetch(
`https://waka.hackclub.com/api/special/apikey?user=${context.userId}`,
{
···
},
},
).then((res) => (res.status === 200 ? res.json() : null));
-
if (hackatimeUser) {
-
if (context?.respond)
await context.respond({
response_type: "ephemeral",
-
text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
-
text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:",
},
},
{
-
type: "section",
-
text: {
-
type: "mrkdwn",
-
text: `It looks like you already have an account! You can log in to the <https://waka.hackclub.com/login|Hackatime dashboard> with your username \`${context.userId}\` and password :3c:`,
-
},
},
{
-
type: "actions",
elements: [
{
-
type: "button",
-
text: {
-
type: "plain_text",
-
text: "Reset Password",
-
},
-
action_id: "reset-password",
-
style: "danger",
},
{
-
type: "button",
-
text: {
-
type: "plain_text",
-
text: "gutentag!",
-
},
-
action_id: "bye",
},
],
},
{
type: "context",
elements: [
{
type: "mrkdwn",
-
text: `your api key for the Hackatime API is \`${hackatimeUser.apiKey}\``,
},
],
},
],
});
return;
}
···
import { slackApp } from "../index";
+
import { fetchUserData } from "./unfurl";
const command = async () => {
+
slackApp.command("/hackatime", async ({ context, payload }) => {
+
if (!context?.respond) return;
const hackatimeUser: { apiKey: string } | null = await fetch(
`https://waka.hackclub.com/api/special/apikey?user=${context.userId}`,
{
···
},
},
).then((res) => (res.status === 200 ? res.json() : null));
+
console.log(payload.text);
if (hackatimeUser) {
+
if (payload.text.includes("summary")) {
+
const interval = payload.text.split(" ")[1] || "month";
+
const userData = await fetchUserData(context.userId, interval);
+
if (!userData) {
+
await context.respond({
+
response_type: "ephemeral",
+
text: "uh oh! something went wrong :ohnoes:",
+
blocks: [
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: "uh oh! something went wrong :ohnoes:",
+
},
+
},
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: "if this keeps happening dm <@U062UG485EE> and let them know",
+
},
+
],
+
},
+
],
+
});
+
return;
+
}
+
+
const projectTotal = userData.projects.reduce((total, project) => {
+
return total + project.total;
+
}, 0);
+
userData.projects.sort((a, b) => b.total - a.total);
+
await context.respond({
response_type: "ephemeral",
+
text: "here's your summary! :yay:",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
+
text: `here's your summary <@${context.userId}>! :roo-yay:`,
},
},
{
+
type: "divider",
},
{
+
type: "context",
elements: [
{
+
type: "mrkdwn",
+
text: `you have spent ${Math.floor(projectTotal / 3600)} hours, ${Math.floor((projectTotal % 3600) / 60)} minutes, and ${projectTotal % 60} seconds coding in the ${interval.replaceAll("_", " ")}${interval.includes("days") || interval.includes("month") ? "" : " interval"}`,
},
+
],
+
},
+
{
+
type: "divider",
+
},
+
{
+
type: "context",
+
elements: [
{
+
type: "mrkdwn",
+
text: `your most active project was \`${userData.projects[0].key}\`, where you spent ${Math.floor(userData.projects[0].total / 3600)} hours, ${Math.floor((userData.projects[0].total % 3600) / 60)} minutes, and ${userData.projects[0].total % 60} seconds`,
},
],
},
{
+
type: "divider",
+
},
+
{
type: "context",
elements: [
{
type: "mrkdwn",
+
text: `here's a list of the rest of your projects:\n\n${userData.projects
+
.slice(1)
+
.map(
+
(project) =>
+
`\`${project.key}\`: ${Math.floor(project.total / 3600)} hours, ${Math.floor((project.total % 3600) / 60)} minutes, and ${project.total % 60} seconds`,
+
)
+
.join("\n")}`,
+
},
+
],
+
},
+
{
+
type: "divider",
+
},
+
{
+
type: "actions",
+
elements: [
+
{
+
type: "button",
+
text: {
+
type: "plain_text",
+
text: "share with channel",
+
},
+
value: interval,
+
action_id: "share-summary",
},
],
},
],
});
+
return;
+
}
+
+
await context.respond({
+
response_type: "ephemeral",
+
text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:",
+
blocks: [
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:",
+
},
+
},
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: `It looks like you already have an account! You can log in to the <https://waka.hackclub.com/login|Hackatime dashboard> with your username \`${context.userId}\` and password :3c:`,
+
},
+
},
+
{
+
type: "actions",
+
elements: [
+
{
+
type: "button",
+
text: {
+
type: "plain_text",
+
text: "Reset Password",
+
},
+
action_id: "reset-password",
+
style: "danger",
+
},
+
{
+
type: "button",
+
text: {
+
type: "plain_text",
+
text: "gutentag!",
+
},
+
action_id: "bye",
+
},
+
],
+
},
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: `your api key for the Hackatime API is \`${hackatimeUser.apiKey}\``,
+
},
+
],
+
},
+
],
+
});
return;
}
+1
features/index.ts
···
export { default as signup } from "./signup";
export { default as resetPassword } from "./reset-password";
export { default as unfurl } from "./unfurl";
···
export { default as signup } from "./signup";
export { default as resetPassword } from "./reset-password";
export { default as unfurl } from "./unfurl";
+
export { default as summary } from "./summary";
+105
features/summary.ts
···
···
+
import { slackApp } from "../index";
+
import { fetchUserData } from "./unfurl";
+
+
const summary = async () => {
+
slackApp.action("share-summary", async ({ context, payload }) => {
+
if (!context?.respond) return;
+
+
// @ts-expect-error
+
const interval = payload.actions[0].value;
+
const userData = await fetchUserData(context.userId, interval);
+
if (!userData) {
+
return;
+
}
+
+
const projectTotal = userData.projects.reduce((total, project) => {
+
return total + project.total;
+
}, 0);
+
userData.projects.sort((a, b) => b.total - a.total);
+
+
await context.respond({
+
text: "sent to channel :3c:",
+
blocks: [
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: "sent to channel :3c:",
+
},
+
],
+
},
+
],
+
});
+
+
await context.client.chat.postMessage({
+
channel: context.channelId as string,
+
text: `here's a new hackatime summary for <@${context.userId}>!`,
+
blocks: [
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: `here's a new hackatime summary for <@${context.userId}>! :roo-yay:`,
+
},
+
},
+
{
+
type: "divider",
+
},
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: `they have spent ${Math.floor(projectTotal / 3600)} hours, ${Math.floor((projectTotal % 3600) / 60)} minutes, and ${projectTotal % 60} seconds coding in the ${interval.replaceAll("_", " ")}${interval.includes("days") || interval.includes("month") ? "" : " interval"}`,
+
},
+
],
+
},
+
{
+
type: "divider",
+
},
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: `their most active project was \`${userData.projects[0].key}\`, where you spent ${Math.floor(userData.projects[0].total / 3600)} hours, ${Math.floor((userData.projects[0].total % 3600) / 60)} minutes, and ${userData.projects[0].total % 60} seconds`,
+
},
+
],
+
},
+
{
+
type: "divider",
+
},
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: `here's a list of the rest of their projects:\n\n${userData.projects
+
.slice(1)
+
.map(
+
(project) =>
+
`\`${project.key}\`: ${Math.floor(project.total / 3600)} hours, ${Math.floor((project.total % 3600) / 60)} minutes, and ${project.total % 60} seconds`,
+
)
+
.join("\n")}`,
+
},
+
],
+
},
+
{
+
type: "divider",
+
},
+
{
+
type: "context",
+
elements: [
+
{
+
type: "mrkdwn",
+
text: "get your own summary by running `/hackatime summary`!",
+
},
+
],
+
},
+
],
+
});
+
});
+
};
+
+
export default summary;
+2 -2
features/unfurl.ts
···
};
}
-
async function fetchUserData(
-
user: string,
interval?: string,
): Promise<UserData | null> {
const response = await fetch(
···
};
}
+
export async function fetchUserData(
+
user: string | undefined,
interval?: string,
): Promise<UserData | null> {
const response = await fetch(