Hack Club Eligibility Bot™️

This commit is contained in:
Haroon 2024-09-04 00:12:02 +01:00
commit 4224c88c9b
Signed by: haroon
GPG key ID: 8B96C44DDF5756E4
6 changed files with 2706 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.env
node_modules

35
README.md Normal file
View file

@ -0,0 +1,35 @@
# Hack Club Eligiblity Bot
A bot that allows you to view the eligiblity of someone in Hack Club for certain swag (basically "are you a student?")
This uses the Hack Club Eligiblity API (duh).
## Usage
Run `/check-eligiblity` in any Slack channel, and optionally ping a user to check their eligiblity (otherwise it shows your own).
If you view your own eligiblity, you can also see the raw response from the API.
## Installation
1. Clone the repo.
```sh
$ git clone https://git.hackclub.app/haroon/hack-club-eligiblity.git
```
2. Enter the newly created directory.
```sh
$ cd hack-club-eligiblity
```
3. Add your bot token and signing secret to .env:
```sh
$ <editor> .env
# Add:
SLACK_BOT_TOKEN=<>
SLACK_SIGNING_SECRET=<>
```
4. Install dependencies:
```
$ npm i
```
5. Run!
```
$ npx tsx .
```

73
index.ts Normal file
View file

@ -0,0 +1,73 @@
import type { User } from "@slack/web-api/dist/response/UsersInfoResponse";
const { App, ExpressReceiver } = (await import("@slack/bolt"));
import "dotenv/config";
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
app.command("/check-eligiblity", async ctx => {
await ctx.ack();
const text = ctx.command.text.slice();
let match;
let userId = ctx.context.userId;
let matchedBy = "no input"
if (match = text.match(/\<\@(.+)\|(.+)>/)) {
userId = match[1];
matchedBy = "user mention"
} else if (text)
matchedBy = "invalid input"
const res = await fetch("https://verify.hackclub.dev/api/status", {
method: "POST",
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
"slack_id": userId
}),
redirect: "follow"
}).then(res => res.json())
if (res === `User ${userId} not found!`)
return await ctx.respond({
response_type: 'ephemeral',
text: `Either ${matchedBy !== "user mention" ? "you haven't" : `<@${userId}> hasn't`} verified, or ${matchedBy !== "user mention" ? "your" : "their"} verification hasn't been accepted.${matchedBy !== "user mention" ? "\nCheck out the <https://forms.hackclub.com/eligibility|eligiblity form> to verify." : ""}`,
unfurl_links: true
})
else {
return await ctx.respond({
response_type: 'ephemeral',
text: `${matchedBy !== "user mention" ? "You have verified your" : `<@${userId}> has verified their`} student status, and ${matchedBy !== "user mention" ? "are" : "is"} ${res.status}.`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `${matchedBy !== "user mention" ? "You have verified your" : `<@${userId}> has verified their`} student status, and ${matchedBy !== "user mention" ? "are" : "is"} *${res.status}*.`
}
},
...(matchedBy == "user mention" ? [] : [
{
type: 'section',
// @ts-ignore silly typings
text: {
type: 'mrkdwn',
text: `*Raw JSON output from the Eligiblity API:*\n` + "```\n" + JSON.stringify(res, null, 2) + "\n```"
}
}
])
]
})
}
})
;(async () => {
await app.start(60275);
console.log('⚡️ Bolt app is running!');
})();

2539
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

29
package.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "hack-club-eligiblity",
"version": "1.0.0",
"main": "index.ts",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://git.hackclub.app/haroon/hack-club-eligiblity.git"
},
"keywords": [
"hack",
"club",
"eligiblity",
"slack"
],
"author": "Haroon <haroon@hackclub.app>",
"license": "ISC",
"description": "",
"dependencies": {
"@slack/bolt": "^3.21.1",
"@slack/web-api": "^7.3.4",
"dotenv": "^16.4.5",
"typescript": "^5.5.4"
}
}

28
tsconfig.json Normal file
View file

@ -0,0 +1,28 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}