Skip to content

Commit

Permalink
feat(bot): yeet everything to git before high seas end
Browse files Browse the repository at this point in the history
gonna get this shipped to nest btw
  • Loading branch information
ajhalili2006 committed Jan 29, 2025
1 parent 9570b73 commit 00e9b91
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 55 deletions.
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HCB Explorer on Discord

It's the Discord edition of [HCB Stalker], built with [Discord.js] library,
It's the Discord edition of [HCB Stalker] Slack app, built with [Discord.js] library,
running on Hack Club Nest by @ajhalili2006. Most HCB API endpoints are accessible via
slash commands for discoverability and to match the Slack experience.

Expand All @@ -11,10 +11,10 @@ slash commands for discoverability and to match the Slack experience.

* Ongoing
* Port HCB Stalker Slack slash commands to Discord
* Postgres-based DB presistence (via Prisma ORM) for org slug caching and stuff
* Postgres-based DB + Redis presistence (via Prisma ORM) for org slug caching and stuff
* Some basic API server for discoverability and maybe docs via [Hono](https://hono.dev)
* Planned
* Authenicated calls to HCB v4 API via OAuth (once the docs are available)
* Authenicated calls to HCB v4 API via OAuth (once the docs and OAuth support are available)
* HCB organization activity real-time monitoring over Discord-compatible webhooks (aka logging)
* Zulip support?!?

Expand All @@ -34,7 +34,7 @@ LTS release and a Discord app ID + bot token ready.

```sh
git clone https://github.com/recaptime-dev/hcb-explorer-discord && cd hcb-explorer-discord
yarn install
npm install
```

2. Reset the `.env.production` file by blanking it's contents and using `dotenvx` to add your Discord bot token:
Expand All @@ -45,13 +45,16 @@ yarn install
cat /dev/null > .env.production

# required
yarn dotenvx set -f .env.production -- DISCORD_BOT_TOKEN <your-bot-token>
yarn dotenvx set -f .env.production -- DISCORD_BOT_APP_ID <your-app-id>
npm exec dotenvx set -f .env.production -- DISCORD_BOT_TOKEN <your-bot-token>
npm exec dotenvx set -f .env.production -- DISCORD_BOT_APP_ID <your-app-id>
npm exec dotenvx set -f .env.production -- NODE_ENV production

## optional
yarn dotenvx set -f .env.production -- SENTRY_DSN <your-sentry-dsn>
npm exec dotenvx set -f .env.production -- SENTRY_DSN <your-sentry-dsn>
```

3. Just run with `npm start` and you're good to go!

## License

MPL
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
{
"name": "@recaptime-dev/hcb-explorer-discord",
"license": "MPL-2.0",
"packageManager": "[email protected]",
"scripts": {
"start:build ": "npm run build && dotenvx run -f .env.production -- node dist/index.js",
"dev:build": "npm run build && dotenvx run -f .env.development -- node dist/index.js",
"start": "dotenvx run -f .env.production -- tsx src/index.ts",
"dev": "dotenvx run -f .env.development -- tsx watch src/index.ts",
"dev:commands": "dotenvx run -f .env.development -- tsx src/scripts/deploy-commands.ts",
"build": "tsc --build && yarn sentry:sourcemaps",
"build": "tsc --build && npm run sentry:sourcemaps",
"test": "echo \"Error: no test specified\" && exit 1",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org recaptime-dev --project hcb-explorer-discord ./dist && sentry-cli sourcemaps upload --org recaptime-dev --project hcb-explorer-discord ./dist"
},
Expand Down
15 changes: 12 additions & 3 deletions src/commands/hcb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ export const hcb = {
opt.setName("name")
.setDescription("The name or ID of the organization.")
.setRequired(true)
)
)
)
.addSubcommand(sub =>
sub.setName("balance")
.setDescription("Show a HCB organization's balances, upcoming deposits/donations, and total money raised and spent.")
.addStringOption(opt =>
opt.setName("name")
.setDescription("The name or ID of the organization.")
.setRequired(true)
)
),
async execute(interaction: CommandInteraction & ChatInputCommandInteraction, client: Client) {
async execute(interaction: CommandInteraction & ChatInputCommandInteraction) {
const subcommand = interaction.options.getSubcommand();

if (subcommand === "org") {
Expand All @@ -25,7 +34,7 @@ export const hcb = {
return await notFoundEmbed(interaction)
}

await handleOrgDataEmbed(interaction, api);
return await handleOrgDataEmbed(interaction, api);
}
}
}
38 changes: 27 additions & 11 deletions src/commands/utility.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
SlashCommandBuilder,
CommandInteraction,
Client,
EmbedBuilder
} from "discord.js";
import projectConfig from "../config"
Expand All @@ -11,17 +10,16 @@ export const ping = {
.setName("ping")
.setDescription("Replies with pong + latency"),
async execute(
interaction: CommandInteraction,
client: Client
interaction: CommandInteraction
) {
const startTime = Date.now();
const wsPing = client.ws.ping;
const wsPing = interaction.client.ws.ping;

const pingOps = new EmbedBuilder({
title: "🏓 Pong!",
description: `Websocket latency: ${wsPing}ms`,
description: `Websocket latency: \`${wsPing}ms\``,
footer: {
text: `Command executed in ${Date.now() - startTime}ms`
text: `Command executed in \`${Date.now() - startTime}ms\``
}
}).toJSON()

Expand All @@ -36,13 +34,12 @@ export const donateLinks = {
.setName("donate")
.setDescription("Support the project by donating to RecapTime.dev"),
async execute(
interaction: CommandInteraction,
client: Client
interaction: CommandInteraction
) {
const donateOps = new EmbedBuilder({
title: "Donate to RecapTime.dev",
author: {
name: "Recap Time Squad",
name: "Recap Time Squad Crew",
icon_url: "https://avatars.githubusercontent.com/u/55875459?v=4",
url: "https://recaptime.dev"
},
Expand All @@ -54,7 +51,7 @@ export const donateLinks = {
},
{
name: "Open Collective",
value: `https://opencollective.com/${projectConfig.donate.opencollective}`
value: `https://opencollective.com/${projectConfig.donate.opencollective}/contribute`
},
{
name: "Crypto and other options",
Expand All @@ -64,10 +61,29 @@ export const donateLinks = {
footer: {
text: "Recap Time Squad is fiscally sponsored by The Hack Foundation (dba Hack Club), a US 501(c)(3) non-profit with EIN 81-2908499."
}
}).toJSON()
})

await interaction.reply({
embeds: [donateOps]
});
}
}

export const inviteLink = {
data: new SlashCommandBuilder()
.setName("invite")
.setDescription("Invite HCB Explorer into your server"),
async execute(interaction: CommandInteraction) {
return await interaction.reply({
embeds: [
new EmbedBuilder({
author: {
name: "Recap Time Squad Crew",
icon_url: "https://avatars.githubusercontent.com/u/55875459?v=4",
url: "https://recaptime.dev"
},
})
]
})
}
}
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export default {
more_details: process.env.DONATE_DETAILS_URL || "https://recaptime.dev/donate"
},
brand_icon_url: "https://hcb.hackclub.com/brand/hcb-icon-icon-dark.png",
// currently HCB doesn't categorize open-source projects yet, so we had
// to verify ourselves manually for now.
opensource_org_ids: [
"org_G3ud13", // https://hcb.hackclub.com/recaptime-dev
"org_RRu9K4", // https://hcb.hackclub.com/lorebooks-wiki
Expand Down
35 changes: 22 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import "./lib/sentry";
import Sentry from './lib/sentry';

import { Events, GatewayIntentBits, ActivityType, Collection } from 'discord.js';
import { Events, GatewayIntentBits, ActivityType, Collection, EmbedBuilder } from 'discord.js';
import { ExtendedClient } from './lib/extendedClient';
import { serve as honoServe, } from '@hono/node-server';
import { Hono, Context } from "hono";
import { Hono } from "hono";
import config from './config';

/** Check if we have the Discord bot token first */
Expand Down Expand Up @@ -45,6 +45,9 @@ app.get("/invite", (c) => {
return c.redirect(`https://discord.com/oauth2/authorize?client_id=${config.appId}`)
})

import { ping, donateLinks, inviteLink } from './commands/utility';
import { hcb } from './commands/hcb';

client.once(Events.ClientReady, async readyClient => {
console.log('[discord]',`Up and running as ${readyClient.user.tag} (app ID: ${readyClient.user.id})`);
await honoServe({
Expand All @@ -53,37 +56,43 @@ client.once(Events.ClientReady, async readyClient => {
});
console.log('[api-server]', `Hono server running on port ${config.port}`);

client.commands.set("ping", require('./commands/utility').ping);
client.commands.set("donate", require('./commands/utility').donateLinks);
client.commands.set("hcb", require('./commands/hcb').hcb);
client.commands.set("ping", ping);
client.commands.set("donate", donateLinks);
client.commands.set("hcb", hcb);
client.commands.set("invite", inviteLink);
})

client.on(Events.InteractionCreate, async interaction => {
if (interaction.isChatInputCommand()) {
const command = client.commands.get(interaction.commandName);
if (!command) {
await interaction.reply({
content: ":warning: That command does not exist here or not yet implemented."
embeds: [
new EmbedBuilder()
.setTitle(":x: That command does not exist here or not yet implemented")
.setDescription("We're working on that soon.")
]
});
return;
}

try {
await command.execute(interaction, client);
await command.execute(interaction);
} catch (error) {
console.error(error);
Sentry.captureException(error);
await interaction.reply({
embeds: [
{
description: "We are looking into it right now. If you need to, [file a new bug report](https://github.com/recaptime-dev/hcb-explorer-discord) in our repo.",
fields: [],
color: 15480656,
title: ":cross: Something went wrong on our side"
}
new EmbedBuilder()
.setTitle(":warning: Something went wrong on our side")
.setDescription("We are looking into it right now. If you need to, [file a new bug report](https://github.com/recaptime-dev/hcb-explorer-discord) in our repo.")
.setColor(15480656)
],
})
}
} else if (interaction.isButton()) {
// todo: implement this
return true;
};
})

Expand Down
4 changes: 2 additions & 2 deletions src/lib/extendedClient.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Client, ClientOptions, Collection } from 'discord.js';
import { SlashCommandBuilder } from '@discordjs/builders';
import { SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from '@discordjs/builders';

interface Command {
data: SlashCommandBuilder;
data: SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder | SlashCommandOptionsOnlyBuilder;
execute: Function;
}

Expand Down
Loading

0 comments on commit 00e9b91

Please sign in to comment.