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