providing password reset services for a long while: circa 2025

feat: add summary command

+243 -27
+135 -25
features/command.ts
··· 1 1 import { slackApp } from "../index"; 2 + import { fetchUserData } from "./unfurl"; 2 3 3 4 const command = async () => { 4 - slackApp.command("/hackatime", async ({ context }) => { 5 + slackApp.command("/hackatime", async ({ context, payload }) => { 6 + if (!context?.respond) return; 5 7 const hackatimeUser: { apiKey: string } | null = await fetch( 6 8 `https://waka.hackclub.com/api/special/apikey?user=${context.userId}`, 7 9 { ··· 10 12 }, 11 13 }, 12 14 ).then((res) => (res.status === 200 ? res.json() : null)); 13 - 15 + console.log(payload.text); 14 16 if (hackatimeUser) { 15 - if (context?.respond) 17 + if (payload.text.includes("summary")) { 18 + const interval = payload.text.split(" ")[1] || "month"; 19 + const userData = await fetchUserData(context.userId, interval); 20 + if (!userData) { 21 + await context.respond({ 22 + response_type: "ephemeral", 23 + text: "uh oh! something went wrong :ohnoes:", 24 + blocks: [ 25 + { 26 + type: "section", 27 + text: { 28 + type: "mrkdwn", 29 + text: "uh oh! something went wrong :ohnoes:", 30 + }, 31 + }, 32 + { 33 + type: "context", 34 + elements: [ 35 + { 36 + type: "mrkdwn", 37 + text: "if this keeps happening dm <@U062UG485EE> and let them know", 38 + }, 39 + ], 40 + }, 41 + ], 42 + }); 43 + return; 44 + } 45 + 46 + const projectTotal = userData.projects.reduce((total, project) => { 47 + return total + project.total; 48 + }, 0); 49 + userData.projects.sort((a, b) => b.total - a.total); 50 + 16 51 await context.respond({ 17 52 response_type: "ephemeral", 18 - text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:", 53 + text: "here's your summary! :yay:", 19 54 blocks: [ 20 55 { 21 56 type: "section", 22 57 text: { 23 58 type: "mrkdwn", 24 - text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:", 59 + text: `here's your summary <@${context.userId}>! :roo-yay:`, 25 60 }, 26 61 }, 27 62 { 28 - type: "section", 29 - text: { 30 - type: "mrkdwn", 31 - 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:`, 32 - }, 63 + type: "divider", 33 64 }, 34 65 { 35 - type: "actions", 66 + type: "context", 36 67 elements: [ 37 68 { 38 - type: "button", 39 - text: { 40 - type: "plain_text", 41 - text: "Reset Password", 42 - }, 43 - action_id: "reset-password", 44 - style: "danger", 69 + type: "mrkdwn", 70 + 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"}`, 45 71 }, 72 + ], 73 + }, 74 + { 75 + type: "divider", 76 + }, 77 + { 78 + type: "context", 79 + elements: [ 46 80 { 47 - type: "button", 48 - text: { 49 - type: "plain_text", 50 - text: "gutentag!", 51 - }, 52 - action_id: "bye", 81 + type: "mrkdwn", 82 + 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`, 53 83 }, 54 84 ], 55 85 }, 56 86 { 87 + type: "divider", 88 + }, 89 + { 57 90 type: "context", 58 91 elements: [ 59 92 { 60 93 type: "mrkdwn", 61 - text: `your api key for the Hackatime API is \`${hackatimeUser.apiKey}\``, 94 + text: `here's a list of the rest of your projects:\n\n${userData.projects 95 + .slice(1) 96 + .map( 97 + (project) => 98 + `\`${project.key}\`: ${Math.floor(project.total / 3600)} hours, ${Math.floor((project.total % 3600) / 60)} minutes, and ${project.total % 60} seconds`, 99 + ) 100 + .join("\n")}`, 101 + }, 102 + ], 103 + }, 104 + { 105 + type: "divider", 106 + }, 107 + { 108 + type: "actions", 109 + elements: [ 110 + { 111 + type: "button", 112 + text: { 113 + type: "plain_text", 114 + text: "share with channel", 115 + }, 116 + value: interval, 117 + action_id: "share-summary", 62 118 }, 63 119 ], 64 120 }, 65 121 ], 66 122 }); 123 + return; 124 + } 125 + 126 + await context.respond({ 127 + response_type: "ephemeral", 128 + text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:", 129 + blocks: [ 130 + { 131 + type: "section", 132 + text: { 133 + type: "mrkdwn", 134 + text: "Hi there! I'm the Hackatime bot :hyper-dino-wave:", 135 + }, 136 + }, 137 + { 138 + type: "section", 139 + text: { 140 + type: "mrkdwn", 141 + 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:`, 142 + }, 143 + }, 144 + { 145 + type: "actions", 146 + elements: [ 147 + { 148 + type: "button", 149 + text: { 150 + type: "plain_text", 151 + text: "Reset Password", 152 + }, 153 + action_id: "reset-password", 154 + style: "danger", 155 + }, 156 + { 157 + type: "button", 158 + text: { 159 + type: "plain_text", 160 + text: "gutentag!", 161 + }, 162 + action_id: "bye", 163 + }, 164 + ], 165 + }, 166 + { 167 + type: "context", 168 + elements: [ 169 + { 170 + type: "mrkdwn", 171 + text: `your api key for the Hackatime API is \`${hackatimeUser.apiKey}\``, 172 + }, 173 + ], 174 + }, 175 + ], 176 + }); 67 177 return; 68 178 } 69 179
+1
features/index.ts
··· 2 2 export { default as signup } from "./signup"; 3 3 export { default as resetPassword } from "./reset-password"; 4 4 export { default as unfurl } from "./unfurl"; 5 + export { default as summary } from "./summary";
+105
features/summary.ts
··· 1 + import { slackApp } from "../index"; 2 + import { fetchUserData } from "./unfurl"; 3 + 4 + const summary = async () => { 5 + slackApp.action("share-summary", async ({ context, payload }) => { 6 + if (!context?.respond) return; 7 + 8 + // @ts-expect-error 9 + const interval = payload.actions[0].value; 10 + const userData = await fetchUserData(context.userId, interval); 11 + if (!userData) { 12 + return; 13 + } 14 + 15 + const projectTotal = userData.projects.reduce((total, project) => { 16 + return total + project.total; 17 + }, 0); 18 + userData.projects.sort((a, b) => b.total - a.total); 19 + 20 + await context.respond({ 21 + text: "sent to channel :3c:", 22 + blocks: [ 23 + { 24 + type: "context", 25 + elements: [ 26 + { 27 + type: "mrkdwn", 28 + text: "sent to channel :3c:", 29 + }, 30 + ], 31 + }, 32 + ], 33 + }); 34 + 35 + await context.client.chat.postMessage({ 36 + channel: context.channelId as string, 37 + text: `here's a new hackatime summary for <@${context.userId}>!`, 38 + blocks: [ 39 + { 40 + type: "section", 41 + text: { 42 + type: "mrkdwn", 43 + text: `here's a new hackatime summary for <@${context.userId}>! :roo-yay:`, 44 + }, 45 + }, 46 + { 47 + type: "divider", 48 + }, 49 + { 50 + type: "context", 51 + elements: [ 52 + { 53 + type: "mrkdwn", 54 + 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"}`, 55 + }, 56 + ], 57 + }, 58 + { 59 + type: "divider", 60 + }, 61 + { 62 + type: "context", 63 + elements: [ 64 + { 65 + type: "mrkdwn", 66 + 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`, 67 + }, 68 + ], 69 + }, 70 + { 71 + type: "divider", 72 + }, 73 + { 74 + type: "context", 75 + elements: [ 76 + { 77 + type: "mrkdwn", 78 + text: `here's a list of the rest of their projects:\n\n${userData.projects 79 + .slice(1) 80 + .map( 81 + (project) => 82 + `\`${project.key}\`: ${Math.floor(project.total / 3600)} hours, ${Math.floor((project.total % 3600) / 60)} minutes, and ${project.total % 60} seconds`, 83 + ) 84 + .join("\n")}`, 85 + }, 86 + ], 87 + }, 88 + { 89 + type: "divider", 90 + }, 91 + { 92 + type: "context", 93 + elements: [ 94 + { 95 + type: "mrkdwn", 96 + text: "get your own summary by running `/hackatime summary`!", 97 + }, 98 + ], 99 + }, 100 + ], 101 + }); 102 + }); 103 + }; 104 + 105 + export default summary;
+2 -2
features/unfurl.ts
··· 106 106 }; 107 107 } 108 108 109 - async function fetchUserData( 110 - user: string, 109 + export async function fetchUserData( 110 + user: string | undefined, 111 111 interval?: string, 112 112 ): Promise<UserData | null> { 113 113 const response = await fetch(