providing password reset services for a long while: circa 2025
at main 4.2 kB view raw
1import type { LinkUnfurls, MessageAttachment } from "slack-edge"; 2import { slackApp } from "../index"; 3import type { UserData } from "./unfurl.types"; 4 5const unfurl = async () => { 6 slackApp.event("link_shared", async ({ context, payload }) => { 7 const unfurls: LinkUnfurls = {}; 8 9 for (const link of payload.links) { 10 const url = new URL(link.url); 11 const path = url.pathname; 12 if (path.includes("api") || path.includes("swagger")) { 13 unfurls[link.url] = { 14 blocks: [ 15 { 16 type: "section", 17 text: { 18 type: "mrkdwn", 19 text: "*API Documentation*", 20 }, 21 }, 22 { 23 type: "context", 24 elements: [ 25 { 26 type: "mrkdwn", 27 text: "The api has full swagger docs available at <https://waka.hackclub.com/swagger-ui/swagger-ui/index.html|`/swagger-ui`> if you have any questions about the api or need an admin token dm <@U062UG485EE>", 28 }, 29 ], 30 }, 31 { 32 type: "divider", 33 }, 34 { 35 type: "image", 36 image_url: 37 "https://hc-cdn.hel1.your-objectstorage.com/s/v3/c8e02c0a89535d80ce8e0a64ef36f0ead31ad26a_image.png", 38 alt_text: "Hackatime Swagger Docs OG Image", 39 }, 40 ], 41 }; 42 } else { 43 const unfurl = await codingTime(url, payload.user); 44 if (unfurl) unfurls[link.url] = unfurl; 45 } 46 } 47 48 await context.client.chat.unfurl({ 49 channel: payload.channel, 50 ts: payload.message_ts, 51 unfurls, 52 }); 53 }); 54}; 55 56async function codingTime( 57 link: URL, 58 user: string, 59): Promise<MessageAttachment | null> { 60 const interval = link.searchParams.get("interval") || "last_7_days"; 61 62 const userData = await fetchUserData(user, interval); 63 if (!userData) return null; 64 const totalSeconds = userData.projects.reduce( 65 (acc: number, curr: { total: number }) => acc + curr.total, 66 0, 67 ); 68 const hours = Math.floor(totalSeconds / 3600); 69 const minutes = Math.floor((totalSeconds % 3600) / 60); 70 const seconds = Math.floor(totalSeconds % 60); 71 72 const pageTitle = await (async () => { 73 const response = await fetch(link); 74 const html = await response.text(); 75 const match = html.match(/<title>(.*?)<\/title>/); 76 return match ? match[1] : "Coding Activity"; 77 })(); 78 79 return { 80 blocks: [ 81 { 82 type: "section", 83 text: { 84 type: "mrkdwn", 85 text: `*${pageTitle}*\n\nThe best place to track your coding activity :orpheus-leaf-grow:`, 86 }, 87 }, 88 { 89 type: "image", 90 image_url: "https://waka.hackclub.com/assets/images/og.jpg", 91 alt_text: "Hackatime OG Image", 92 }, 93 { 94 type: "divider", 95 }, 96 { 97 type: "context", 98 elements: [ 99 { 100 type: "mrkdwn", 101 text: `<@${user}> has spent ${hours} hours, ${minutes} minutes, and ${seconds} seconds coding in the ${interval.replaceAll("_", " ")}${interval.includes("days") || interval.includes("month") ? "" : " interval"}.${link.toString().includes("settings#danger_zone") ? "\n\ncareful there bud :eyes: the danger zone is no joke" : ""}${link.toString().includes("projects") ? `\n\nthey have over ${userData.projects.length} projects with the biggest being \`${userData.projects.sort((a: { total: number }, b: { total: number }) => b.total - a.total)[0].key}\` at \`${Math.floor(userData.projects.sort((a: { total: number }, b: { total: number }) => b.total - a.total)[0].total / 3600)} hours\`` : ""}`, 102 }, 103 ], 104 }, 105 ], 106 }; 107} 108 109export async function fetchUserData( 110 user: string | undefined, 111 interval?: string, 112): Promise<UserData | null> { 113 const response = await fetch( 114 `https://waka.hackclub.com/api/summary?user=${user}&interval=${interval || "last_7_days"}`, 115 { 116 method: "GET", 117 headers: { 118 accept: "application/json", 119 Authorization: `Bearer ${process.env.HACKATIME_API_KEY}`, 120 }, 121 }, 122 ); 123 124 return response.status === 200 ? ((await response.json()) as UserData) : null; 125} 126 127export default unfurl;