generated from nhcarrigan/template
Compare commits
No commits in common. "01587f406c5b7217934bbceafae2c98a5e394520" and "e940a9c7ab4f548ec23631a7ce382f64f4357352" have entirely different histories.
01587f406c
...
e940a9c7ab
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@nhcarrigan"
|
|
||||||
}
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
/node_modules/
|
|
||||||
/prod/
|
|
||||||
.env
|
|
@ -1 +0,0 @@
|
|||||||
"@nhcarrigan/prettier-config"
|
|
@ -1,3 +1,3 @@
|
|||||||
# Privacy Policy
|
# Privacy Policy
|
||||||
|
|
||||||
Our privacy policy can be found here: https://docs.nhcarrigan.com/#/discord-boost-monitor/privacy
|
Our privacy policy can be found here: https://docs.nhcarrigan.com/#/privacy
|
||||||
|
20
README.md
20
README.md
@ -1,12 +1,20 @@
|
|||||||
# Discord Boost Monitor
|
# New Repository Template
|
||||||
|
|
||||||
A bot to remove special roles from people when they stop boosting a server.
|
This template contains all of our basic files for a new GitHub repository. There is also a handy workflow that will create an issue on a new repository made from this template, with a checklist for the steps we usually take in setting up a new repository.
|
||||||
|
|
||||||
|
If you're starting a Node.JS project with TypeScript, we have a [specific template](https://github.com/naomi-lgbt/nodejs-typescript-template) for that purpose.
|
||||||
|
|
||||||
|
## Readme
|
||||||
|
|
||||||
|
Delete all of the above text (including this line), and uncomment the below text to use our standard readme template.
|
||||||
|
|
||||||
|
<!-- # Project Name
|
||||||
|
|
||||||
|
Project Description
|
||||||
|
|
||||||
## Live Version
|
## Live Version
|
||||||
|
|
||||||
This page is not yet deployed. Want to [run it yourself?](https://docs.nhcarrigan.com/#/discord-boost-monitor/usage)
|
This page is currently deployed. [View the live website.]
|
||||||
|
|
||||||
<!--This page is currently deployed. [View the live website.]()-->
|
|
||||||
|
|
||||||
## Feedback and Bugs
|
## Feedback and Bugs
|
||||||
|
|
||||||
@ -28,4 +36,4 @@ Copyright held by Naomi Carrigan.
|
|||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via `contact@nhcarrigan.com`.
|
We may be contacted through our [Chat Server](http://chat.nhcarrigan.com) or via email at `contact@nhcarrigan.com`. -->
|
||||||
|
49
package.json
49
package.json
@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "oogie-woogie-boostie-woostie",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"description": "A bot to remove booster perks when someone stops boosting.",
|
|
||||||
"main": "prod/index.js",
|
|
||||||
"scripts": {
|
|
||||||
"prebuild": "rm -rf ./prod",
|
|
||||||
"build": "tsc",
|
|
||||||
"lint": "eslint src --max-warnings 0",
|
|
||||||
"start": "node -r dotenv/config prod/index.js",
|
|
||||||
"test": "echo 'No tests yet!'"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "22",
|
|
||||||
"pnpm": "9"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/nhcarrigan/oogie-woogie-boostie-woostie.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"discord",
|
|
||||||
"bot",
|
|
||||||
"typescript",
|
|
||||||
"discord.js"
|
|
||||||
],
|
|
||||||
"author": "Naomi Carrigan",
|
|
||||||
"license": "SEE LICENSE IN https://docs.nhcarrigan.com/#/license",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/nhcarrigan/oogie-woogie-boostie-woostie/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/nhcarrigan/oogie-woogie-boostie-woostie#readme",
|
|
||||||
"devDependencies": {
|
|
||||||
"@nhcarrigan/eslint-config": "1.1.3",
|
|
||||||
"@nhcarrigan/prettier-config": "1.0.1",
|
|
||||||
"@nhcarrigan/typescript-config": "1.0.1",
|
|
||||||
"eslint": "8.57.0",
|
|
||||||
"prettier": "2.8.8",
|
|
||||||
"typescript": "5.4.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/integrations": "7.114.0",
|
|
||||||
"@sentry/node": "7.114.0",
|
|
||||||
"discord.js": "14.15.2",
|
|
||||||
"dotenv": "16.4.5",
|
|
||||||
"fastify": "5.0.0",
|
|
||||||
"winston": "3.13.0"
|
|
||||||
}
|
|
||||||
}
|
|
2830
pnpm-lock.yaml
generated
2830
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
DISCORD_TOKEN=""
|
|
||||||
SENTRY_DSN=""
|
|
||||||
DEBUG_HOOK=""
|
|
@ -1,12 +0,0 @@
|
|||||||
export const colourRoles = [
|
|
||||||
"883281643098484736",
|
|
||||||
"883283836887261204",
|
|
||||||
"883282544655753268",
|
|
||||||
"883283096378683402",
|
|
||||||
"883283115265642547",
|
|
||||||
"883283650794360852",
|
|
||||||
"883284117343584298",
|
|
||||||
"883284381937065984",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const boosterRole = "712431541531181177";
|
|
51
src/index.ts
51
src/index.ts
@ -1,51 +0,0 @@
|
|||||||
import { RewriteFrames } from "@sentry/integrations";
|
|
||||||
import * as Sentry from "@sentry/node";
|
|
||||||
import { Client, GatewayIntentBits, WebhookClient } from "discord.js";
|
|
||||||
|
|
||||||
import { manageRoles } from "./modules/manageRoles";
|
|
||||||
import { instantiateServer } from "./server/serve";
|
|
||||||
import { errorHandler } from "./utils/errorHandler";
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: process.env.SENTRY_DSN,
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
integrations: [
|
|
||||||
new RewriteFrames({
|
|
||||||
root: global.__dirname,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const bot = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.on("ready", async () => {
|
|
||||||
const hook = new WebhookClient({ url: process.env.DEBUG_HOOK as string });
|
|
||||||
await hook.send({
|
|
||||||
content: "Oogie boogie boostie woostie online!",
|
|
||||||
username: "Boost Monitor",
|
|
||||||
avatarURL: "https://cdn.nhcarrigan.com/avatars/nhcarrigan.png",
|
|
||||||
});
|
|
||||||
|
|
||||||
const guild = bot.guilds.cache.map((el) => el)[0];
|
|
||||||
const members = await guild.members.fetch();
|
|
||||||
await hook.send({
|
|
||||||
content: `Loaded ${members.size} members from ${guild.name}`,
|
|
||||||
username: "Boost Monitor",
|
|
||||||
avatarURL: "https://cdn.nhcarrigan.com/avatars/nhcarrigan.png",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.on("guildMemberUpdate", async (_oldMember, newMember) => {
|
|
||||||
await manageRoles(newMember);
|
|
||||||
});
|
|
||||||
|
|
||||||
await bot.login(process.env.DISCORD_TOKEN);
|
|
||||||
await instantiateServer(bot);
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler("index", err);
|
|
||||||
}
|
|
||||||
})();
|
|
@ -1,26 +0,0 @@
|
|||||||
import { GuildMember } from "discord.js";
|
|
||||||
|
|
||||||
import { boosterRole, colourRoles } from "../config/roles";
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches to the memberUpdate event, and checks if the member is no longer boosting
|
|
||||||
* the server (based on the premiumSubscriberRole). If true, then removes any of the configured
|
|
||||||
* booster-only roles.
|
|
||||||
*
|
|
||||||
* @param {GuildMember} newMember The state of the member after the update.
|
|
||||||
*/
|
|
||||||
export const manageRoles = async (newMember: GuildMember): Promise<void> => {
|
|
||||||
try {
|
|
||||||
if (!newMember.roles.cache.has(boosterRole)) {
|
|
||||||
for (const roleId of colourRoles) {
|
|
||||||
const target = newMember.roles.cache.find((role) => role.id === roleId);
|
|
||||||
if (target) {
|
|
||||||
await newMember.roles.remove(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler("manageRoles", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,85 +0,0 @@
|
|||||||
import { readFile } from "fs/promises";
|
|
||||||
|
|
||||||
import { type Client, WebhookClient } from "discord.js";
|
|
||||||
import fastify from "fastify";
|
|
||||||
|
|
||||||
import { errorHandler } from "../utils/errorHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts up a web server for health monitoring.
|
|
||||||
*
|
|
||||||
* @param {Client} bot The bot's Discord instance.
|
|
||||||
*/
|
|
||||||
export const instantiateServer = async (bot: Client) => {
|
|
||||||
try {
|
|
||||||
const server = fastify({
|
|
||||||
logger: false,
|
|
||||||
https: {
|
|
||||||
cert: await readFile(
|
|
||||||
"/etc/letsencrypt/live/oogie.nhcarrigan.com/cert.pem"
|
|
||||||
),
|
|
||||||
key: await readFile(
|
|
||||||
"/etc/letsencrypt/live/oogie.nhcarrigan.com/privkey.pem"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
server.get("/", (_req, res) => {
|
|
||||||
res.header("Content-Type", "text/html");
|
|
||||||
res.send(`
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Boost Monitor</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="description" content="A bot to remove special roles when someone stops boosting a server." />
|
|
||||||
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>Boost Monitor</h1>
|
|
||||||
<section>
|
|
||||||
<p>A bot to remove special roles when someone stops boosting a server.</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Links</h2>
|
|
||||||
<p>
|
|
||||||
<a href="https://codeberg.org/nhcarrigan/boost-monitor">
|
|
||||||
<i class="fa-solid fa-code"></i> Source Code
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="https://docs.nhcarrigan.com">
|
|
||||||
<i class="fa-solid fa-book"></i> Documentation
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="https://chat.nhcarrigan.com">
|
|
||||||
<i class="fa-solid fa-circle-info"></i> Support
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>`);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen({ port: 3443 }, (err) => {
|
|
||||||
if (err) {
|
|
||||||
void errorHandler("start server", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const hook = new WebhookClient({ url: process.env.DEBUG_HOOK as string });
|
|
||||||
void hook.send({
|
|
||||||
avatarURL:
|
|
||||||
bot.user?.displayAvatarURL() ??
|
|
||||||
"https://cdn.nhcarrigan.com/profile.png",
|
|
||||||
content: "Fastify server live on port 1443~!",
|
|
||||||
username: bot.user?.username ?? "Boost Monitor",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
await errorHandler("instantiate server", err);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,39 +0,0 @@
|
|||||||
import * as Sentry from "@sentry/node";
|
|
||||||
import { EmbedBuilder, WebhookClient } from "discord.js";
|
|
||||||
|
|
||||||
import { logHandler } from "./logHandler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard error handling module to pipe errors to Sentry and
|
|
||||||
* format the error for logging.
|
|
||||||
*
|
|
||||||
* @param {string} context A description of where the error occurred.
|
|
||||||
* @param {Error} err The error object.
|
|
||||||
*/
|
|
||||||
export const errorHandler = async (context: string, err: unknown) => {
|
|
||||||
const error = err as Error;
|
|
||||||
logHandler.log("error", `There was an error in the ${context}:`);
|
|
||||||
logHandler.log(
|
|
||||||
"error",
|
|
||||||
JSON.stringify({ errorMessage: error.message, errorStack: error.stack })
|
|
||||||
);
|
|
||||||
Sentry.captureException(error);
|
|
||||||
|
|
||||||
const hook = new WebhookClient({ url: process.env.DEBUG_HOOK as string });
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
embed.setTitle(`There was an error in the ${context}`);
|
|
||||||
embed.setDescription(error.message.slice(0, 2000));
|
|
||||||
embed.addFields([
|
|
||||||
{
|
|
||||||
name: "Stack",
|
|
||||||
value: `\`\`\`${error.stack?.slice(0, 1000) || "no stack"}\`\`\``,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
await hook.send({
|
|
||||||
embeds: [embed],
|
|
||||||
username: "Boost Monitor",
|
|
||||||
avatarURL: "https://cdn.nhcarrigan.com/avatars/nhcarrigan.png",
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
import { createLogger, format, transports, config } from "winston";
|
|
||||||
|
|
||||||
const { combine, timestamp, colorize, printf } = format;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard log handler, using winston to wrap and format
|
|
||||||
* messages. Call with `logHandler.log(level, message)`.
|
|
||||||
*
|
|
||||||
* @param {string} level - The log level to use.
|
|
||||||
* @param {string} message - The message to log.
|
|
||||||
*/
|
|
||||||
export const logHandler = createLogger({
|
|
||||||
levels: config.npm.levels,
|
|
||||||
level: "silly",
|
|
||||||
transports: [new transports.Console()],
|
|
||||||
format: combine(
|
|
||||||
timestamp({
|
|
||||||
format: "YYYY-MM-DD HH:mm:ss",
|
|
||||||
}),
|
|
||||||
colorize(),
|
|
||||||
printf((info) => `${info.level}: ${[info.timestamp]}: ${info.message}`)
|
|
||||||
),
|
|
||||||
exitOnError: false,
|
|
||||||
});
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@nhcarrigan/typescript-config",
|
|
||||||
"compilerOptions": {
|
|
||||||
"rootDir": "./src",
|
|
||||||
"outDir": "./prod"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user