Initial Commit - Based on TheShadowEevee/Konpeki-Discord-Bot

This commit is contained in:
Michael 2024-12-23 13:41:57 -06:00
commit 0e027dc99b
Signed by: TheShadowEevee
GPG key ID: 7A8AA92B3BAFAB75
19 changed files with 3633 additions and 0 deletions

49
.eslintrc.json Normal file
View file

@ -0,0 +1,49 @@
{
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2021
},
"rules": {
"arrow-spacing": ["warn", { "before": true, "after": true }],
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
"comma-dangle": ["error", "always-multiline"],
"comma-spacing": "error",
"comma-style": "error",
"curly": ["error", "multi-line", "consistent"],
"dot-location": ["error", "property"],
"handle-callback-err": "off",
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": "error",
"max-nested-callbacks": ["error", { "max": 4 }],
"max-statements-per-line": ["error", { "max": 2 }],
"no-console": "off",
"no-empty-function": "error",
"no-floating-decimal": "error",
"no-inline-comments": "error",
"no-lonely-if": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
"no-trailing-spaces": ["error"],
"no-var": "error",
"object-curly-spacing": ["error", "always"],
"prefer-const": "error",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"space-before-blocks": "error",
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
}

13
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: TheShadowEevee # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: TheShadowEevee # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

71
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '16 9 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
config.json
presence.json
node_modules
data

7
LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright © 2022 TheShadowEevee and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

75
README.md Normal file
View file

@ -0,0 +1,75 @@
[![GitHub](https://img.shields.io/github/license/TheShadowEevee/Konpeki-Discord-Bot)](https://github.com/TheShadowEevee/Konpeki-Discord-Bot/blob/master/LICENSE) [![GitHub repo size](https://img.shields.io/github/repo-size/TheShadowEevee/Konpeki-Discord-Bot)](https://github.com/TheShadowEevee/Konpeki-Discord-Bot) [![GitHub package.json dependency version (prod)](https://img.shields.io/github/package-json/dependency-version/TheShadowEevee/Konpeki-Discord-Bot/discord.js)](https://discord.js.org/) [![CodeFactor](https://www.codefactor.io/repository/github/theshadoweevee/konpeki-discord-bot/badge)](https://www.codefactor.io/repository/github/theshadoweevee/konpeki-discord-bot) [![Discord](https://img.shields.io/discord/1052844258173931540?label=discord)](https://discord.gg/Zt8zruXexJ)
# Konpeki Discord Bot
Konpeki is a Discord Bot with varied functions. Written with discord.js, the Konpeki bot is designed to be easy to use and setup, as well as easy to change to work how you want it to. The name Konpeki comes from the Japenese word for Azure; there's no other reason for the name.
For general support, or to try out a live, public version of the bot, feel free to join the bot's [Discord Server](https://discord.gg/Zt8zruXexJ)!
The live version of the bot is `Konpeki Shiho#2603`, run by TheShadowEevee.
## Adding to your server
You can add the public bot, Konpeki Shiho, to your server [here](https://discord.com/oauth2/authorize?client_id=812862868721631272&permissions=275146345472&scope=bot%20applications.commands)!
- Please note that currently Konpeki Shiho is *not* a verified bot due to lack of server membership. You can help get the bot verified by adding it to your server! Konpeki Shiho runs and updates directly off of the code from this repository, while development is done primarily against the private bot Little Shiho.
If you are running your own version of the Konpeki bot, change the Client ID in the link to your bot's Client ID found in the Discord Developer portal. See [Running Locally](#Running-Locally) for more.
## Running Locally
These instructions will get you setup with a basic setup for development.
If you are running the bot as-is, it is recommended to use the [pm2](https://pm2.io/) process manager or a similar tool to keep the bot running unattended.
Before you begin, install these prerequisites:
- [Node.js](https://nodejs.org/en/download/)
- [Git](https://git-scm.com/downloads/)
#### ***Creating the bot on Discord***
The first step to setup any Discord bot is creating one in the [Discord Developer Portal](https://discord.com/developers/).
- Go to the [Discord Developer Portal](https://discord.com/developers/)
- Click New Application, name it, and press Create.
- The Application ID on the page you are brought to is your Client ID. You will need this later.
- Click the Bot tab on the left, then New Bot.
- If you get an error saying too many users have this username, rename your application to something more unique.
- Copy your bot's token somewhere *safe*.
- Do not share this! This will give anyone access to your bot. Reset it immediatly if it becomes public.
- If you lose the token, you will have to reset it and get a new one.
#### ***Configuring the bot***
Once you have created a bot, you need to configure it.
- Clone the repositiory and move into the folder.
```bash
git clone https://github.com/TheShadowEevee/Konpeki-Discord-Bot
cd Konpeki-Discord-Bot
```
- Copy `config.json.template` and name the new file `config.json`.
- Fill in the `config.json` file with your bot's token, client id, and your username.
#### ***Running the bot***
Once your bot is created, run the below commands to clone and run the bot.
```bash
# Install NPM dependencies
npm install
# Setup slash commands
node deploy-commands.js
# Run the bot
node index.js
```
#### ***Updating the bot***
Update the bot periodically to ensure your up-to-date.
If you're updating from Github:
- Run `git pull` from the bot's folder
- This will get the lastest updates from Github
- Run `node delete-commands.js && node deploy-commands.js`
- This will recreate all your slash commands to ensure they are up-to-date
If you're updating based on changes you made:
- You only need to do this if you have created or deleted a file from the `commands` folder
- Run `node delete-commands.js && node deploy-commands.js`
- This will recreate all your slash commands to ensure they are up-to-date

15
TODO.md Normal file
View file

@ -0,0 +1,15 @@
TODO List for feature completeness
P0:
- Check for Media in a message
- Regex Parse Git Commits
- Github top priority, Gitea/Forgejo second, any others third
- Check for reactions on a message
- Start / End Dates
P1:
- Check if in a thread
- Allow reaction to be chosen in the command rather than hard coded
P2:
- Calculate Days, Highest Streak

27
commands/commit-count.js Normal file
View file

@ -0,0 +1,27 @@
/*
* Commit Overflow Counting - Slash Command Definition File
* commit-count.js - Gets an initial commit count for Commit Overflow
*/
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('commit-count')
.setDescription('Counts Commits'),
async execute(interaction) {
const exampleEmbed = new EmbedBuilder()
.setColor(interaction.member.displayHexColor)
.setTitle('Commits')
.setDescription('By Day (Method)')
.addFields(
{ name: 'Valid Commits', value: 'December 20th (Media)\nDecember 21st (Commit URL)\nDecember 23rd (Reaction)' },
{ name: 'Missed Days', value: 'December 22nd' },
{ name: 'Organizers:', value: 'Please react with :gh_green_square: to verify missing days!' },
);
await interaction.reply({ embeds: [exampleEmbed], ephemeral: true });
},
};

6
config.json.template Normal file
View file

@ -0,0 +1,6 @@
{
"token": "Bot Token Here - DO NOT SHARE YOUR TOKEN OR YOUR CONFIG FILE",
"clientId": "0123456789012345678",
"botName": "Konpeki Bot",
"botOwner": "discordusername"
}

30
delete-commands.js Normal file
View file

@ -0,0 +1,30 @@
/*
* Konpeki Discord Bot - Slash Command Configuration File
* delete-commands.js - Clears all slash commands from Discord to remove any old commands
*
* Code modified based on example from the discord.js guides
* Run `node deploy-commands.js` after this to recreate your slash commands
*/
const { REST, Routes } = require('discord.js');
const { clientId, token } = require('./config.json');
// Construct and prepare an instance of the REST module
const rest = new REST({ version: '10' }).setToken(token);
// and deploy your commands!
(async () => {
try {
console.log('Started deleting all application (/) commands.');
// The put method is used to fully refresh all commands
rest.put(Routes.applicationCommands(clientId), { body: [] })
.then(() => console.log('Successfully deleted all application (/) commands.'))
.catch(console.error);
}
catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
}
})();

111
deploy-commands.js Normal file
View file

@ -0,0 +1,111 @@
/*
* Konpeki Discord Bot - Slash Command Configuration File
* deploy-commands.js - Gathers all current .js files in the commands folder and publishes a list to Discord
*
* Code modified based on example from the discord.js guides
*/
const { REST, Routes } = require('discord.js');
const { clientId, token } = require('./config.json');
const fs = require('node:fs');
const commands = [];
const commandsHelp = [];
// Grab all the command files from the commands directory you created earlier
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
commands.push(command.data.toJSON());
commandsHelp.push(command.data);
}
// Construct and prepare an instance of the REST module
const rest = new REST({ version: '10' }).setToken(token);
// Generate help-text.json
try {
console.log(`Generating ${commands.length} help text entries.`);
let helpJSONString = '{\n';
for (let i = 0; i < commandsHelp.length; i++) {
helpJSONString += ` "${commandsHelp[i].name}": {\n`;
helpJSONString += ` "description": "${commandsHelp[i].description}",\n`;
helpJSONString += ' "options": {\n';
for (let j = 0; j < commandsHelp[i].options.length; j++) {
helpJSONString += ` "${commandsHelp[i].options[j].name}": {\n`;
helpJSONString += ` "description": "${commandsHelp[i].options[j].description}",\n`;
helpJSONString += ` "required": "${commandsHelp[i].options[j].required}",\n`;
helpJSONString += ' "choices": {\n';
if (typeof commandsHelp[i].options[j].choices !== 'undefined') {
for (let k = 0; k < commandsHelp[i].options[j].choices.length; k++) {
helpJSONString += ` "${commandsHelp[i].options[j].choices[k].name}": {\n`;
helpJSONString += ` "value": "${commandsHelp[i].options[j].choices[k].value}",\n`;
helpJSONString += ' },\n';
}
}
helpJSONString += ' },\n';
helpJSONString += ' },\n';
}
helpJSONString += ' },\n';
helpJSONString += ' },\n';
}
helpJSONString += '}';
// Lazy way out of removing trailing commas
// See https://stackoverflow.com/a/34347475
const helpJSON = JSON.stringify(JSON.parse(helpJSONString.replace(/,(?!\s*?[{["'\w])/g, '')), null, 4);
// Create data folder if it doesn't exist
if (!fs.existsSync('data')) {
fs.mkdirSync('data');
}
// Write file to disk
fs.writeFile('./data/help-text.json', helpJSON, err => {
if (err) {
console.log(`Unable to write help-text.json: ${err}`);
console.log('Stopping...');
return;
}
});
console.log(`Successfully generated ${commandsHelp.length} help text entries.`);
console.log();
// Update slash commands on Discord's side
(async () => {
try {
console.log(`Started refreshing ${commands.length} application (/) commands.`);
// The put method is used to fully refresh all commands
const data = await rest.put(
Routes.applicationCommands(clientId),
{ body: commands },
);
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
}
catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
}
})();
}
catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
}

127
index.js Normal file
View file

@ -0,0 +1,127 @@
/*
* Konpeki Discord Bot - Main Bot File
* All base level bot setup is done here
*/
// Require filesystem libraries
const fs = require('node:fs');
const path = require('node:path');
// Add pm2 metrics - this should NEVER track ANYTHING identifiable. This is purely for basic metrics and bot performance tracking
const metrics = require('./utils/pm2-metrics.js');
// Use a custom logging script
const logger = require('./utils/logging.js');
// Listen for (Semi-)Permenant Interactions
const interactionListener = require('./utils/interaction-trigger.js');
// Require the necessary discord.js classes
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
const { token, botOwner } = require('./config.json');
const { activity, status } = require('./presence.json');
// Create a new client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// Setup the commands collection
client.commands = new Collection();
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
// Set a new item in the Collection with the key as the command name and the value as the exported module
if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
}
else {
logger.log(logger.logLevels.WARN, `The command at ${filePath} is missing a required "data" or "execute" property.`);
}
}
// When the client is ready, run this code (only once)
// We use 'c' for the event parameter to keep it separate from the already defined 'client'
client.once(Events.ClientReady, c => {
logger.log(logger.logLevels.INFO, `${logger.colorText('Ready!', logger.textColor.Green)} Logged in as ${logger.colorText(c.user.tag, logger.textColor.Blue)}`);
client.user.setPresence({ activities: [{ name: activity }], status: status });
// Track websocket heartbeat with PM2 Histogram
let latency = 0;
setInterval(function() {
latency = c.ws.ping;
metrics.websocketHeartbeatHist.update(latency);
}, 1000);
// Report current server count with PM2 (Servers counted anonymously)
metrics.serverCount.set(c.guilds.cache.size);
});
// Client "on" Events
// Someone used an interaction
client.on(Events.InteractionCreate, async interaction => {
if (interaction.isChatInputCommand()) {
const command = interaction.client.commands.get(interaction.commandName);
if (!command) {
logger.log(logger.logLevels.ERROR, `No command matching ${interaction.commandName} was found.`);
await interaction.reply({ content: `This command no longer exists! Please contact ${botOwner} to report that this is happening!`, ephemeral: true });
// Report error to PM2 dashboard
metrics.interactionErrors.inc();
metrics.io.notifyError(new Error('Interaction doesn\'t exist'), {
custom: {
interactionCommand: interaction.commandName,
},
});
return;
}
try {
await command.execute(interaction);
}
catch (error) {
logger.log(logger.logLevels.ERROR, error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
// Report error to PM2 dashboard
metrics.interactionErrors.inc();
metrics.io.notifyError(new Error('Error executing interaction'), {
custom: {
interactionCommand: interaction.commandName,
error: error,
},
});
}
// Successful Execution, report as a PM2 metric
// If the bot gets a lot of use, consider removing this for performance
metrics.interactionSuccess();
}
else if (interaction.isButton()) {
interactionListener.buttonInteraction(interaction);
}
else if (interaction.isStringSelectMenu()) {
// respond to the select menu
}
});
// Joined a server
client.on(Events.GuildCreate, guild => {
// Report current server count with PM2 (Servers counted anonymously)
metrics.serverCount.set(guild.client.guilds.cache.size);
});
// Removed from a server
client.on(Events.GuildDelete, guild => {
// Report current server count with PM2 (Servers counted anonymously)
metrics.serverCount.set(guild.client.guilds.cache.size);
});
// Log in to Discord with your client's token
client.login(token);

1703
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

33
package.json Normal file
View file

@ -0,0 +1,33 @@
{
"dependencies": {
"@pm2/io": "^5.0.0",
"discord.js": "^14.15.3",
"dotenv": "^16.0.3"
},
"name": "konpeki-discordbot",
"version": "1.0.0",
"description": "A Discord bot with varied functionalities",
"main": "index.js",
"devDependencies": {
"eslint": "^8.29.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/TheShadowEevee/Konpeki-Discord-Bot.git"
},
"keywords": [
"RTD",
"Discord",
"Bot",
"Konpeki"
],
"author": "TheShadowEevee",
"license": "MIT",
"bugs": {
"url": "https://github.com/TheShadowEevee/Konpeki-Discord-Bot/issues"
},
"homepage": "https://github.com/TheShadowEevee/Konpeki-Discord-Bot#readme"
}

1206
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

4
presence.json.template Normal file
View file

@ -0,0 +1,4 @@
{
"activity": "Put your status here!",
"status": "online"
}

View file

@ -0,0 +1,39 @@
const { Events } = require('discord.js');
const buttonInteraction = function(interaction) {
const splitInteraction = interaction.customId.split('-');
(async () => {
if (splitInteraction[0] === 'role') {
const client = interaction.client;
const guild = await client.guilds.fetch(interaction.guildId);
const member = interaction.member;
const role = await guild.roles.fetch(splitInteraction[1]);
if (member.roles.cache.find(r => r.id === splitInteraction[1])) {
try {
member.roles.remove(splitInteraction[1]);
await interaction.reply({ content: `Removed role ${role} from ${interaction.user}!`, ephemeral: true });
}
catch {
await interaction.reply({ content: 'An error has occurred and the role was not removed. Likely I don\'t have the needed permissions!', ephemeral: true });
}
}
else {
try {
member.roles.add(splitInteraction[1]);
await interaction.reply({ content: `Added role ${role} to ${interaction.user}!`, ephemeral: true });
}
catch {
await interaction.reply({ content: 'An error has occurred and the role was not added. Likely I don\'t have the needed permissions!', ephemeral: true });
}
}
}
})();
return;
};
module.exports = {
name: Events.InteractionCreate,
buttonInteraction,
};

72
utils/logging.js Normal file
View file

@ -0,0 +1,72 @@
/*
* Konpeki Discord Bot - Utility Definition File
* logging.js - A custom logging script to apply information to output
*
* Though console.error() and console.warn() exist, they don't exactly fit what is wanted here.
*/
// Enum list of severity levels
const logLevels = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
};
// Enum list of text colors
const textColor = {
White: '\x1b[97m',
Gray: '\x1b[37m',
Yellow: '\x1b[33m',
Red: '\x1b[91m',
Blue: '\x1b[96m',
Green: '\x1b[92m',
Reset: '\x1b[0m',
};
// Get the current Date and Time
const date_time = new Date(new Date().toUTCString());
const datetime = '[' + ('0' + (date_time.getMonth() + 1)).slice(-2) + '/' + ('0' + date_time.getDate()).slice(-2) + '/' + date_time.getFullYear() + ' ' + ('0' + (date_time.getHours() + 1)).slice(-2) + ':' + ('0' + (date_time.getMinutes() + 1)).slice(-2) + ':' + ('0' + (date_time.getSeconds() + 1)).slice(-2) + ' UTC]';
// Add color to the text passed through - If a text manip util is made, move this there
const colorText = function(message, color) {
return color + message + textColor.Reset;
};
// Print log message to console based on severity level
const log = function(logLevel, message) {
// Used in the event the message is an error: see case 3
const lines = message.toString().split('\n');
switch (logLevel) {
case 0:
console.log(datetime + ' ' + colorText('[DEBUG]', textColor.Gray) + ' ' + message);
break;
case 1:
console.log(datetime + ' ' + colorText('[INFO]', textColor.White) + ' ' + message);
break;
case 2:
console.log(datetime + ' ' + colorText('[WARN]', textColor.Yellow) + ' ' + message);
break;
case 3:
// Errors tend to have multiple lines, handle them specially to include the first two.
console.log(datetime + ' ' + colorText('[ERROR]', textColor.Red) + ' ' + lines[0]);
// If the error only has 1 line, don't try to read a second one
if (lines[1] != null) {
console.log(datetime + ' ' + colorText('[ERROR]', textColor.Red) + ' | ' + lines[1].trim());
}
break;
default:
console.log(datetime + ' ' + colorText('[INFO]', textColor.White) + ' ' + message);
break;
}
};
module.exports = {
logLevels,
textColor,
colorText,
log,
};

40
utils/pm2-metrics.js Normal file
View file

@ -0,0 +1,40 @@
/*
* Konpeki Discord Bot - Utility Definition File
* pm2-metrics.js - A place to keep all pm2 metrics and error definitions
*/
const io = require('@pm2/io');
const interactionErrors = io.counter({
name: 'Interaction Errors (Since last restart)',
});
const interactionSuccessCounter = io.counter({
name: 'Interaction Successful Runs (Since last restart)',
});
const interactionSuccessMeter = io.meter({
name: 'Successful Interactions per Second',
});
const websocketHeartbeatHist = io.histogram({
name: 'Avg. Websocket Heartbeat (ms) / 5 min.',
measurement: 'mean',
});
const serverCount = io.metric({
name : 'Server Count',
});
const interactionSuccess = function() {
interactionSuccessCounter.inc();
interactionSuccessMeter.mark();
};
module.exports = {
io,
interactionErrors,
interactionSuccess,
websocketHeartbeatHist,
serverCount,
};