Skip to content

Commit

Permalink
feat(global): ✨ publish the discord bot source for sentry (and why not)
Browse files Browse the repository at this point in the history
Still doing the thing behind the scenes, will going to ship it soon.
  • Loading branch information
ajhalili2006 committed Dec 22, 2024
1 parent 8928fc9 commit 97b3168
Show file tree
Hide file tree
Showing 14 changed files with 1,365 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ DOTENV_PUBLIC_KEY_DEVELOPMENT="03c54ef5c94ed89c7ba03d4560e4af440d1663d15bf29181c
DISCORD_BOT_TOKEN="encrypted:BDn/ODeUJlzC8SORoRO/jcCDHlSf8eYHKJUjMh5X9oGCiKVu2awi+IePO3EDV8dRSY/E+4ySdHL7f3hQUSGYnUvVazO+xBqkEReyXmhuOsUnneC64mXiEO8gsF0S9x4WdqPkp4Svmq+ZXN6nAWMZ5n4hDHvoUZda/S7/UOX5yD6qckQ545ZFdB2EBmfzI6Z8ufZikLbGNxDF1GaqRz0JgVCCPtqjSZ6z5Q=="
DISCORD_BOT_APP_ID="1318609571475886191"
NODE_OPTIONS='--no-warnings --loader ts-node/esm'
SENTRY_DSN="encrypted:BC3/HgYgo0kK7egnDEVEPaQXT/MPnyqPeQ9seWlAZv5NBLpC330qkzR9zDGM46QYB3LVjE6KHCPG+XMw1cteYb//XrbTx/uiXXvRjl6JvnHX+pHEcthtT/FLB5Hf7JFeXBZIXrKkfm3Qk0uXOPQh7YT2RqNGRcYtAwjfpnCslqcKyx0M8dfiO8oJE68t3woEuJ010MDcyduD8S/xZbmIzPv9RvWwahtjKZoaADNAXKEdvYBZEXk="
SENTRY_DSN="encrypted:BC3/HgYgo0kK7egnDEVEPaQXT/MPnyqPeQ9seWlAZv5NBLpC330qkzR9zDGM46QYB3LVjE6KHCPG+XMw1cteYb//XrbTx/uiXXvRjl6JvnHX+pHEcthtT/FLB5Hf7JFeXBZIXrKkfm3Qk0uXOPQh7YT2RqNGRcYtAwjfpnCslqcKyx0M8dfiO8oJE68t3woEuJ010MDcyduD8S/xZbmIzPv9RvWwahtjKZoaADNAXKEdvYBZEXk="
NODE_ENV="development"
4 changes: 4 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
DOTENV_PUBLIC_KEY_PRODUCTION="031c733507101704b9bcd0a852a195c4728e64b09e75feca682a55ce482293b069"

# .env.production
# This is the production environment configuration file
# for HCB Explorer Discord bot, used at Hack Club Nest.
DISCORD_BOT_TOKEN="encrypted:BIdbL44fnvHirm9WBI1O4kbWeW2u04T8LmyxFkqBG2QXqfiT1IKygaMg8vqZeNzFh6SiEBKJ2M0VLzQWda/eHecEcbiwEb8WPBm+bKD5SDZJCat/f9sHlZqSzuXS+9c82S/1MSDDI3xXunfkVPVoRqJJFTFUURneKVMU0WvJ/XBAwovrDuYvyBNheqAa089uqelRxwUFV8j90hAzKj85Hm2R9sJo2vrrXQ=="
DUSCORD_BOT_APP_ID=1318460909567475722
NODE_OPTIONS='--no-warnings --loader ts-node/esm'
SENTRY_DSN="encrypted:BFB7eLx6B6fZLEvtHUPjs2zSqOdus655SwhQmOaO5WUl3z36OP6cfBk/iAbFnqbV+yQtn39l/D80dyZs4AjQNzqD1Tv2mg6jh1ZGHSJa5UUzf7dTSVVrTn0QwnY9oLhSIpDVWiMgFrxyR8dbDaoS6rmS76xcToXyLdfYRjp3XmBcQYkTCLsT70VXnNumsXl8YK7PvoM8Yz+BRcIDpBb4DiBPHa6+y7Fqifmsha01ceQV/pK3CbU="
18 changes: 18 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";


/** @type {import('eslint').Linter.Config[]} */
export default [
{files: ["**/*.{js,mjs,cjs,ts}"]},
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
{languageOptions: { globals: globals.node }},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
indent: ["error", 2],
}
}
];
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"license": "MPL-2.0",
"packageManager": "[email protected]",
"scripts": {
"start": "dotenvx run -f .env.production -- yarn node dist/index.js",
"dev:build": "yarn build && dotenvx run -f .env.development -- yarn node dist/index.js",
"dev": "dotenvx run -f .env.development -- ts-node-dev --loader ts-node/esm --respawn src/index.ts",
"dev:commands": "dotenvx run -f .env.development -- ts-node src/scripts/deploy-commands.ts",
"build": "tsc --build && yarn sentry:sourcemaps",
Expand All @@ -20,13 +22,18 @@
"hono": "^4.6.14"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@types/node": "^22.10.2",
"eslint": "^9.17.0",
"globals": "^15.14.0",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.1"
},
"repository": {
"type": "git",
"url": "https://github.com/recaptime-dev/hcb-explorer-discord.git"
}
}
}
31 changes: 31 additions & 0 deletions src/commands/hcb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ChatInputCommandInteraction, Client, CommandInteraction, Interaction, SlashCommandBuilder } from "discord.js";
import { fetchOrganization, handleOrgDataEmbed, notFoundEmbed } from "../lib/hcb";

export const hcb = {
data: new SlashCommandBuilder()
.setName("hcb")
.setDescription("Access the HCB API via Discord")
.addSubcommand(sub =>
sub.setName("org")
.setDescription("See a HCB organization's information, such as their website and description.")
.addStringOption(opt =>
opt.setName("name")
.setDescription("The name or ID of the organization.")
.setRequired(true)
)
),
async execute(interaction: CommandInteraction & ChatInputCommandInteraction, client: Client) {
const subcommand = interaction.options.getSubcommand();

if (subcommand === "org") {
const name = interaction.options.getString("name") || "hq";
const api = await fetchOrganization(name);

if (api.status == 404) {
return await notFoundEmbed(interaction)
}

await handleOrgDataEmbed(interaction, api);
}
}
}
73 changes: 73 additions & 0 deletions src/commands/utility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
SlashCommandBuilder,
CommandInteraction,
Client,
EmbedBuilder
} from "discord.js";
import projectConfig from "../config"

export const ping = {
data: new SlashCommandBuilder()
.setName("ping")
.setDescription("Replies with pong + latency"),
async execute(
interaction: CommandInteraction,
client: Client
) {
const startTime = Date.now();
const wsPing = client.ws.ping;

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

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

export const donateLinks = {
data: new SlashCommandBuilder()
.setName("donate")
.setDescription("Support the project by donating to RecapTime.dev"),
async execute(
interaction: CommandInteraction,
client: Client
) {
const donateOps = new EmbedBuilder({
title: "Donate to RecapTime.dev",
author: {
name: "Recap Time Squad",
icon_url: "https://avatars.githubusercontent.com/u/55875459?v=4",
url: "https://recaptime.dev"
},
description: "You can donate to RecapTime.dev to support the project through the following links:",
fields: [
{
name: "HCB (recommended)",
value: `https://hcb.hackclub.com/donations/start/${projectConfig.donate.hcb}`
},
{
name: "Open Collective",
value: `https://opencollective.com/${projectConfig.donate.opencollective}`
},
{
name: "Crypto and other options",
value: projectConfig.donate.more_details
}
],
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]
});
}
}
31 changes: 31 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pkgMetadata from "../package.json";

export default {
port: Number(process.env.PORT) || 3000,
appId: process.env.DISCORD_BOT_APP_ID,
botToken: process.env.DISCORD_BOT_TOKEN,
sentryDSN: process.env.SENTRY_DSN || "",
repo: process.env.REPO_URL || pkgMetadata.repository.url,
support: {
discord: process.env.DISCORD_INVITE_CODE || "FyBYQrJtUX",
slack: {
team_id: process.env.SLACK_TEAM_ID || "T0266FRGM",
channel_id: process.env.SLACK_CHANNEL_ID || "C07H1R2PW9W"
},
zulip: {
host: process.env.ZULIP_HOMESERVER_HOST || "recaptime-dev.zulipchat.com",
channel: process.env.ZULIP_CHANNEL || "hcb-ops"
}
},
donate: {
hcb: process.env.HCB_ORG_SLUG || "recaptime-dev",
opencollective: process.env.OPENCOLLECTIVE_SLUG || "recaptime-dev",
github_sposnors: process.env.GITHUB_USERNAME || "ajhalili2006",
more_details: process.env.DONATE_DETAILS_URL || "https://recaptime.dev/donate"
},
brand_icon_url: "https://hcb.hackclub.com/brand/hcb-icon-icon-dark.png",
opensource_org_ids: [
"org_G3ud13", // https://hcb.hackclub.com/recaptime-dev
"org_RRu9K4", // https://hcb.hackclub.com/lorebooks-wiki
]
}
90 changes: 90 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import "./lib/sentry";
import Sentry from './lib/sentry';

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

/** Check if we have the Discord bot token first */
if (!config.botToken) {
throw Error("Discord token missing, try running via dotenvx/doppler?")
}

/**
* Discord bot client initializer
*/
const client = new ExtendedClient({
intents: [
GatewayIntentBits.Guilds
],
presence: {
status: 'online',
activities: [
{
name: "🏦 hcb.hackclub.com",
type: ActivityType.Watching,
url: "https://hackclub.com/hcb",
state: "Monitoring Hack Club HQ finances and friends"
}
]
}
})

const app = new Hono()
client.commands = new Collection();

app.get("/", (c) => {
return c.redirect(config.repo)
})
app.get("/ping", (c) => {
return c.json({ ok: true, message: "Pong!" })
})
app.get("/invite", (c) => {
return c.redirect(`https://discord.com/oauth2/authorize?client_id=${config.appId}`)
})

client.once(Events.ClientReady, async readyClient => {
console.log('[discord]',`Up and running as ${readyClient.user.tag} (app ID: ${readyClient.user.id})`);
await honoServe({
fetch: app.fetch,
port: config.port
});
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.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."
});
return;
}

try {
await command.execute(interaction, client);
} 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"
}
],
})
}
};
})

client.login(config.botToken)
Empty file added src/lib/donateLinks.ts
Empty file.
16 changes: 16 additions & 0 deletions src/lib/extendedClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Client, ClientOptions, Collection } from 'discord.js';
import { SlashCommandBuilder } from '@discordjs/builders';

interface Command {
data: SlashCommandBuilder;
execute: Function;
}

export class ExtendedClient extends Client {
commands: Collection<string, Command>;

constructor(options: ClientOptions) {
super(options);
this.commands = new Collection();
}
}
Loading

0 comments on commit 97b3168

Please sign in to comment.