generated from nhcarrigan/template
feat: initial prototype
All checks were successful
Node.js CI / Lint and Test (push) Successful in 31s
All checks were successful
Node.js CI / Lint and Test (push) Successful in 31s
This commit is contained in:
parent
2ba0ae631b
commit
f3ab9e541f
38
.gitea/workflows/ci.yml
Normal file
38
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,38 @@
|
||||
name: Node.js CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint and Test
|
||||
|
||||
steps:
|
||||
- name: Checkout Source Files
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js v22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint Source Files
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Verify Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm run test
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
prod
|
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"eslint.validate": ["typescript"]
|
||||
}
|
5
eslint.config.js
Normal file
5
eslint.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
import NaomisConfig from "@nhcarrigan/eslint-config";
|
||||
|
||||
export default [
|
||||
...NaomisConfig
|
||||
]
|
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "standup-bot",
|
||||
"version": "0.0.0",
|
||||
"description": "Discord bot to post standup reminders for the Commit Your Code cohort.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "rm -rf prod && tsc",
|
||||
"lint": "eslint src --max-warnings 0",
|
||||
"start": "op run --env-file=prod.env --no-masking -- node prod/index.js",
|
||||
"test": "echo \"No tests yet!\" && exit 0"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Naomi Carrigan",
|
||||
"license": "See license in LICENSE.md",
|
||||
"devDependencies": {
|
||||
"@nhcarrigan/eslint-config": "5.1.0",
|
||||
"@nhcarrigan/typescript-config": "4.0.0",
|
||||
"@types/node": "22.13.1",
|
||||
"@types/node-schedule": "2.1.7",
|
||||
"@vitest/coverage-istanbul": "3.0.5",
|
||||
"eslint": "9.20.0",
|
||||
"typescript": "5.7.3",
|
||||
"vitest": "3.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nhcarrigan/logger": "1.0.0",
|
||||
"discord.js": "14.18.0",
|
||||
"node-schedule": "2.1.1"
|
||||
}
|
||||
}
|
4764
pnpm-lock.yaml
generated
Normal file
4764
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
prod.env
Normal file
2
prod.env
Normal file
@ -0,0 +1,2 @@
|
||||
DISCORD_TOKEN="op://Environment Variables - Naomi/Standup Bot/discord_token"
|
||||
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"
|
24
src/config/channels.ts
Normal file
24
src/config/channels.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
/**
|
||||
* The channels to post standup reminders in.
|
||||
*/
|
||||
export const channels = [
|
||||
"1326291362483671202",
|
||||
"1329625299406684263",
|
||||
"1326291172896931921",
|
||||
"1326291132535013409",
|
||||
"1326291241045856286",
|
||||
"1326291186863833169",
|
||||
"1326291226332496026",
|
||||
"1326291379621597304",
|
||||
"1326291254421749770",
|
||||
"1326291160632655983",
|
||||
"1326291146267430912",
|
||||
"1326291198666866788",
|
||||
"1326291214026408009",
|
||||
];
|
38
src/index.ts
Normal file
38
src/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { Client, Events, GatewayIntentBits } from "discord.js";
|
||||
import { scheduleJob } from "node-schedule";
|
||||
import { standup } from "./modules/standup.js";
|
||||
import { logger } from "./utils/logger.js";
|
||||
|
||||
process.on("unhandledRejection", (error) => {
|
||||
if (error instanceof Error) {
|
||||
void logger.error("Unhandled Rejection", error);
|
||||
return;
|
||||
}
|
||||
void logger.error("unhandled rejection", new Error(String(error)));
|
||||
});
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
if (error instanceof Error) {
|
||||
void logger.error("Uncaught Exception", error);
|
||||
return;
|
||||
}
|
||||
void logger.error("uncaught exception", new Error(String(error)));
|
||||
});
|
||||
|
||||
const client = new Client({
|
||||
intents: [ GatewayIntentBits.Guilds ],
|
||||
});
|
||||
|
||||
client.on(Events.ClientReady, () => {
|
||||
void logger.log("debug", "Bot is ready.");
|
||||
scheduleJob("reminders", "0 6 * * 1-5", async() => {
|
||||
await standup(client);
|
||||
});
|
||||
});
|
||||
|
||||
await client.login(process.env.DISCORD_TOKEN);
|
48
src/modules/standup.ts
Normal file
48
src/modules/standup.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { channels } from "../config/channels.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import type { Client } from "discord.js";
|
||||
|
||||
/**
|
||||
* Posts a daily standup reminder in configured channels.
|
||||
* @param client - The Discord client.
|
||||
*/
|
||||
export const standup = async(client: Client): Promise<void> => {
|
||||
try {
|
||||
const mapped = await Promise.all(
|
||||
channels.map(async(channel) => {
|
||||
const fetched = await client.channels.fetch(channel).catch(() => {
|
||||
return null;
|
||||
});
|
||||
if (!fetched) {
|
||||
return null;
|
||||
}
|
||||
if (!fetched.isTextBased() || !("send" in fetched)) {
|
||||
return null;
|
||||
}
|
||||
return fetched;
|
||||
}),
|
||||
);
|
||||
const filtered = mapped.filter((channel) => {
|
||||
return channel !== null;
|
||||
});
|
||||
await Promise.all(
|
||||
filtered.map(async(channel) => {
|
||||
await channel.send(
|
||||
// eslint-disable-next-line stylistic/max-len -- This message is too long to fit on one line.
|
||||
"Good morning @everyone~! It's time for standup! Please share your goals for today.",
|
||||
);
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
await logger.error("standup module", error);
|
||||
return;
|
||||
}
|
||||
await logger.error("standup module", new Error(String(error)));
|
||||
}
|
||||
};
|
12
src/utils/logger.ts
Normal file
12
src/utils/logger.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Logger } from "@nhcarrigan/logger";
|
||||
|
||||
export const logger = new Logger(
|
||||
"Standup Bot",
|
||||
process.env.LOG_TOKEN ?? "",
|
||||
);
|
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@nhcarrigan/typescript-config",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./prod"
|
||||
},
|
||||
"exclude": ["test/**/*.ts", "vitest.config.ts"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user