Skip to content

Commit

Permalink
delete deps.ts, add discord_app
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanThatOneKid committed May 28, 2024
1 parent 9cdf651 commit 9571bb1
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 489 deletions.
13 changes: 8 additions & 5 deletions deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
{
"lock": "./deno.lock",
"tasks": {
"udd": "deno run -r --allow-read=. --allow-write=. --allow-net https://deno.land/x/udd/main.ts deps.ts && deno task lock",
"lock": "deno cache --lock-write deps.ts",
"all": "deno task udd && deno lint && deno fmt",
"test": "deno test --unstable-kv",
"start": "deno run -A --unstable-kv main.ts",
"ngrok": "ngrok http 8080",
"dnt": "deno run -A tasks/dnt/main.ts"
},
"imports": { "@fartlabs/rt": "jsr:@fartlabs/rt@^0.0.3", "lc-dailies/": "./" },
"imports": {
"@discord-applications/app": "jsr:@discord-applications/app@^0.0.4",
"@fartlabs/rt": "jsr:@fartlabs/rt@^0.0.3",
"@std/assert": "jsr:@std/assert@^0.225.3",
"@std/datetime": "jsr:@std/datetime@^0.224.0",
"@std/ulid": "jsr:@std/ulid@^0.224.0",
"lc-dailies/": "./"
},
"fmt": {
"exclude": ["./npm"]
},
Expand Down
28 changes: 26 additions & 2 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 0 additions & 27 deletions deps.ts

This file was deleted.

38 changes: 0 additions & 38 deletions env.ts

This file was deleted.

36 changes: 14 additions & 22 deletions lib/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import { createRouter } from "@fartlabs/rt";
import * as discord from "lc-dailies/lib/discord/mod.ts";
import * as leaderboard from "lc-dailies/lib/leaderboard/mod.ts";
import * as discord_app from "./discord_app/mod.ts";
import * as discord_app from "./discord/mod.ts";
import type { Season } from "./types.ts";

/**
* APIRouterOptions are the options for the API router.
*/
export interface APIRouterOptions {
leaderboardClient: leaderboard.LeaderboardClient;
discordChannelID: string;
discordApplicationID: string;
discordPublicKey: string;
discordChannelID: string;
leaderboardClient: leaderboard.LeaderboardClient;
discordToken: string;
}

/**
* makeAPIRouter creates a router which handles requests on the
* LC-Dailies API.
*/
export function makeAPIRouter(options: APIRouterOptions) {
export async function makeAPIRouter(options: APIRouterOptions) {
const app = await discord_app.makeDiscordAppHandler(
options.leaderboardClient,
options.discordApplicationID,
options.discordChannelID,
options.discordPublicKey,
options.discordToken,
);
return createRouter()
.post(
"/",
(ctx) =>
discord_app.withErrorResponse(
discord_app.makeDiscordAppHandler(
options.leaderboardClient,
options.discordPublicKey,
options.discordChannelID,
),
)(ctx.request),
(ctx) => discord_app.withErrorResponse(app)(ctx.request),
)
.get(
"/invite",
Expand Down Expand Up @@ -92,19 +92,11 @@ export function makeAPIRouter(options: APIRouterOptions) {
export function makeOnListen(
port: number,
discordApplicationID: string,
discordToken: string,
) {
/**
* onLoad is callback which is called when the server starts listening.
*/
return async function onLoad() {
// Overwrite the Discord Application Command.
await discord.registerCommand({
app: discord_app.APP_LC,
applicationID: discordApplicationID,
botToken: discordToken,
});

return function onLoad() {
console.log(
"- Discord application information:",
`https://discord.com/developers/applications/${discordApplicationID}/`,
Expand Down
167 changes: 167 additions & 0 deletions lib/api/discord/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { SECOND } from "@std/datetime";
import type {
APIInteractionResponse,
AppSchema,
} from "@discord-applications/app";
import {
ApplicationCommandOptionType,
createApp,
InteractionResponseType,
MessageFlags,
} from "@discord-applications/app";
import * as api from "lc-dailies/lib/api/mod.ts";
import * as leaderboard from "lc-dailies/lib/leaderboard/mod.ts";

export const lcSchema = {
chatInput: {
name: "lc",
description: "Set of commands to register and submit Leetcode solutions.",
subcommands: {
register: {
description: "Register your Leetcode account",
options: {
lc_username: {
description: "Your Leetcode username",
type: ApplicationCommandOptionType.String,
required: true,
},
},
},
sync: {
description: "Sync the leaderboard with the latest submissions",
options: {
season_id: {
description: "The season ID to sync",
type: ApplicationCommandOptionType.String,
required: true,
},
},
},
},
},
} as const satisfies AppSchema;

/**
* makeDiscordAppHandler creates a handler for Discord application command interactions.
*/
export function makeDiscordAppHandler(
leaderboardClient: leaderboard.LeaderboardClient,
applicationID: string,
channelID: string,
publicKey: string,
token: string,
) {
return createApp(
{
schema: lcSchema,
applicationID,
publicKey,
token,
invite: { path: "/invite", scopes: ["applications.commands"] },
register: true,
},
{
async register(interaction) {
if (interaction.channel.id !== channelID) {
return {
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content:
"This command is only available in the LC-Dailies channel.",
flags: MessageFlags.Ephemeral,
},
};
}

const registerResponse = await leaderboardClient.register(
interaction.user!.id,
interaction.data.parsedOptions.lc_username,
);

return makeRegisterInteractionResponse(registerResponse);
},
async sync(interaction) {
const syncResponse = await leaderboardClient.sync(
interaction.data.parsedOptions.season_id,
);

return makeSyncInteractionResponse(syncResponse);
},
},
);
}

/**
* withErrorResponse wraps around the Discord app handler to catch any errors
* and return a response using the error message.
*/
export function withErrorResponse(
oldHandle: (request: Request) => Promise<Response>,
): (request: Request) => Promise<Response> {
return async function handle(request: Request): Promise<Response> {
return await oldHandle(request)
.catch((error) => {
if (!(error instanceof Error)) {
throw error;
}

return Response.json(
{
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: `Error: ${error.message}`,
flags: MessageFlags.Ephemeral,
},
} satisfies APIInteractionResponse,
);
});
};
}

/**
* makeSyncInteractionResponse makes the interaction response for the sync subcommand.
*/
export function makeSyncInteractionResponse(
r: api.SyncResponse,
): APIInteractionResponse {
return {
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: [
`# Synced leaderboard [\`${r.season.id}\`](https://lc-dailies.deno.dev/seasons/${r.season.id}) for week of ${r.season.start_date} synced ${
toDiscordTimestamp(new Date(r.season.synced_at!))
}`,
"```",
leaderboard.formatScores(r.season),
"```",
].join("\n"),
},
};
}

/**
* toDiscordTimestamp converts a date to a Discord timestamp.
*
* Reference:
* - https://gist.github.com/LeviSnoot/d9147767abeef2f770e9ddcd91eb85aa
* - https://github.com/acmcsufoss/shorter/blob/dbaac9a020a621be0c349a8b9a870b936b988265/main.ts#L235
*/
function toDiscordTimestamp(date: Date) {
return `<t:${~~(date.getTime() / SECOND)}:R>`;
}

/**
* makeRegisterInteractionResponse makes the interaction response for the register subcommand.
*/
export function makeRegisterInteractionResponse(
r: api.RegisterResponse,
): APIInteractionResponse {
return {
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: `Your Leetcode username was ${
r.ok ? "registered" : "not registered"
}.`,
},
};
}
File renamed without changes.
Loading

1 comment on commit 9571bb1

@deno-deploy
Copy link

@deno-deploy deno-deploy bot commented on 9571bb1 May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed to deploy:

Module not found "file:///src/deps.ts".

Please sign in to comment.