a fun bot for the hc slack

feat: select hackatime keys

dunkirk.sh 57ef6acf f3369747

verified
Changed files
+133 -11
src
features
takes
handlers
libs
+87 -2
src/features/takes/handlers/settings.ts
···
import { eq } from "drizzle-orm";
import { users as usersTable } from "../../../libs/schema";
import {
getHackatimeName,
HACKATIME_VERSIONS,
type HackatimeVersion,
···
repo_link: string | undefined;
demo_link: string | undefined;
hackatime_version: string;
} = {
project_name: "",
project_description: "",
repo_link: undefined,
demo_link: undefined,
hackatime_version: "v2",
};
if (prefill) {
···
repo_link: existingUser.repoLink || undefined,
demo_link: existingUser.demoLink || undefined,
hackatime_version: existingUser.hackatimeVersion,
};
}
} catch (error) {
···
type: "plain_text",
text: "Submit",
},
-
clear_on_close: true,
callback_id: "takes_setup_submit",
blocks: [
{
···
})),
},
},
],
},
});
···
const file = values.project_banner?.project_banner_input?.files?.[0]
?.url_private_download as string;
-
console.log(file);
try {
const projectBannerUrl = file
···
| string
| undefined,
hackatimeVersion,
})
.onConflictDoUpdate({
target: usersTable.id,
···
| string
| undefined,
hackatimeVersion,
},
});
} catch (error) {
console.error("Error processing file:", error);
throw error;
···
import { eq } from "drizzle-orm";
import { users as usersTable } from "../../../libs/schema";
import {
+
fetchRecentProjectKeys,
getHackatimeName,
HACKATIME_VERSIONS,
type HackatimeVersion,
···
repo_link: string | undefined;
demo_link: string | undefined;
hackatime_version: string;
+
hackatime_keys: string[];
} = {
project_name: "",
project_description: "",
repo_link: undefined,
demo_link: undefined,
hackatime_version: "v2",
+
hackatime_keys: [],
};
if (prefill) {
···
repo_link: existingUser.repoLink || undefined,
demo_link: existingUser.demoLink || undefined,
hackatime_version: existingUser.hackatimeVersion,
+
hackatime_keys: existingUser.hackatimeKeys
+
? JSON.parse(existingUser.hackatimeKeys)
+
: [],
};
}
} catch (error) {
···
type: "plain_text",
text: "Submit",
},
callback_id: "takes_setup_submit",
blocks: [
{
···
})),
},
},
+
{
+
type: "input",
+
block_id: "project_keys",
+
label: {
+
type: "plain_text",
+
text: "Project Keys",
+
},
+
element: {
+
type: "multi_static_select",
+
action_id: "project_keys_input",
+
initial_options: initialValues.hackatime_keys.map(
+
(key) => ({
+
text: {
+
type: "plain_text",
+
text: key,
+
},
+
value: key,
+
}),
+
),
+
options: (
+
await fetchRecentProjectKeys(
+
user,
+
10,
+
initialValues.hackatime_version as HackatimeVersion,
+
)
+
).map((key) => ({
+
text: {
+
type: "plain_text",
+
text: key,
+
},
+
value: key,
+
})),
+
},
+
},
],
},
});
···
const file = values.project_banner?.project_banner_input?.files?.[0]
?.url_private_download as string;
+
const hackatimeKeys = JSON.stringify(
+
values.project_keys?.project_keys_input?.selected_options?.map(
+
(option) => option.value,
+
) || [],
+
);
try {
const projectBannerUrl = file
···
| string
| undefined,
hackatimeVersion,
+
hackatimeKeys,
})
.onConflictDoUpdate({
target: usersTable.id,
···
| string
| undefined,
hackatimeVersion,
+
hackatimeKeys,
},
});
+
+
// Update the view to show the latest Hackatime project keys
+
await slackClient.views.update({
+
view_id: payload.view.id,
+
view: {
+
type: "modal",
+
title: {
+
type: "plain_text",
+
text: "Add your hackatime keys",
+
},
+
blocks: [
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: ":white_check_mark: Your project has been updated successfully!",
+
},
+
},
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: "*Hackatime Project Keys:*",
+
},
+
},
+
{
+
type: "section",
+
text: {
+
type: "mrkdwn",
+
text: Object.values(HACKATIME_VERSIONS)
+
.map(
+
(v) =>
+
`• *${getHackatimeName(v.id)}*: \`${v.id}\``,
+
)
+
.join("\n"),
+
},
+
},
+
],
+
},
+
});
} catch (error) {
console.error("Error processing file:", error);
throw error;
+46 -9
src/libs/hackatime.ts
···
userId: string,
version: HackatimeVersion = "v2",
projectKeys?: string[],
) {
const apiUrl = getHackatimeApiUrl(version);
-
const response = await fetch(
-
`${apiUrl}/summary?user=${userId}&interval=month`,
-
{
-
headers: {
-
accept: "application/json",
-
Authorization: "Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608",
-
},
},
-
);
if (!response.ok) {
throw new Error(
-
`Failed to fetch Hackatime summary: ${response.status} ${response.statusText}`,
);
}
···
projectsKeys: projectsKeys,
};
}
···
userId: string,
version: HackatimeVersion = "v2",
projectKeys?: string[],
+
from?: Date,
+
to?: Date,
) {
const apiUrl = getHackatimeApiUrl(version);
+
const params = new URLSearchParams({
+
user: userId,
+
});
+
if (!from || !to) {
+
params.append("interval", "month");
+
} else if (from && to) {
+
params.append("from", from.toISOString());
+
params.append("to", to.toISOString());
+
}
+
+
const response = await fetch(`${apiUrl}/summary?${params.toString()}`, {
+
headers: {
+
accept: "application/json",
+
Authorization: "Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608",
},
+
});
if (!response.ok) {
throw new Error(
+
`Failed to fetch Hackatime summary: ${response.status} ${response.statusText}: ${await response.text()}`,
);
}
···
projectsKeys: projectsKeys,
};
}
+
+
/**
+
* Fetches the most recent project keys from a user's Hackatime data
+
* @param userId The user ID to fetch the project keys for
+
* @param limit The maximum number of projects to return (defaults to 10)
+
* @param version The Hackatime version to use (defaults to v2)
+
* @returns A promise that resolves to an array of recent project keys
+
*/
+
export async function fetchRecentProjectKeys(
+
userId: string,
+
limit = 10,
+
version: HackatimeVersion = "v2",
+
): Promise<string[]> {
+
const summary = await fetchHackatimeSummary(userId, version);
+
+
// Extract projects and sort by most recent
+
const sortedProjects =
+
summary.projects?.sort(
+
(a: { last_used_at: string }, b: { last_used_at: string }) =>
+
new Date(b.last_used_at).getTime() -
+
new Date(a.last_used_at).getTime(),
+
) || [];
+
+
// Return the keys of the most recent projects up to the limit
+
return sortedProjects
+
.slice(0, limit)
+
.map((project: { key: string }) => project.key);
+
}