Initializing bot with artificer core

This commit is contained in:
Ean Milligan (Bastion) 2022-09-02 00:53:43 -04:00
parent 6ec3b8be96
commit f02faed7ff
13 changed files with 863 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
config.ts
logs

16
.sonarcloud.properties Normal file
View File

@ -0,0 +1,16 @@
# Path to sources
sonar.sources=.
sonar.exclusions=emojis
#sonar.inclusions=
# Path to tests
#sonar.tests=
#sonar.test.exclusions=
#sonar.test.inclusions=
# Source encoding
sonar.sourceEncoding=UTF-8
# Exclusions for copy-paste detection
# src/commands/rollHelp.ts, src/commands/rollDecorators.ts are excluded to get rid of the duplicate code compliant. Sonar does not like you initializing JSON in ts files.
sonar.cpd.exclusions=src/commands/rollHelp.ts,src/commands/rollDecorators.ts

12
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true,
"deno.import_intellisense_origins": {
"https://deno.land": true
},
"spellright.language": [
"en"
],
"spellright.documentTypes": []
}

20
config.example.ts Normal file
View File

@ -0,0 +1,20 @@
export const config = {
'name': 'Sweeper Bot', // Name of the bot
'version': '0.0.0', // Version of the bot
'token': 'the_bot_token', // Discord API Token for this bot
'localtoken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token"
'prefix': 's!', // Prefix for all commands
'db': { // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot
'host': '', // IP address for the db, usually localhost
'localhost': '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment
'port': 3306, // Port for the db
'username': '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privalages
'password': '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box
'name': '', // Name of the database Schema to use for the bot
},
'logChannel': 0n, // Discord channel ID where the bot should put startup messages and other error messages needed
'reportChannel': 0n, // Discord channel ID where reports will be sent when using the built-in report command
'devServer': 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts
};
export default config;

31
deno.json Normal file
View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"allowJs": true,
"lib": ["deno.worker"],
"strict": true
},
"lint": {
"files": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"exclude": []
},
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"exclude": []
}
},
"fmt": {
"files": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"exclude": []
},
"options": {
"useTabs": true,
"lineWidth": 200,
"indentWidth": 2,
"singleQuote": true,
"proseWrap": "preserve"
}
}
}

20
deps.ts Normal file
View File

@ -0,0 +1,20 @@
// All external dependancies are to be loaded here to make updating dependancy versions much easier
export {
botId,
cache,
cacheHandlers,
DiscordActivityTypes,
editBotNickname,
editBotStatus,
hasGuildPermissions,
Intents,
sendDirectMessage,
sendMessage,
startBot,
} from 'https://deno.land/x/discordeno@12.0.1/mod.ts';
export type { CreateMessage, DiscordenoGuild, DiscordenoMessage, EmbedField } from 'https://deno.land/x/discordeno@12.0.1/mod.ts';
export { Client } from 'https://deno.land/x/mysql@v2.10.2/mod.ts';
export { initLog, log, LogTypes as LT } from 'https://raw.githubusercontent.com/Burn-E99/Log4Deno/V1.1.1/mod.ts';

6
flags.ts Normal file
View File

@ -0,0 +1,6 @@
// DEVMODE is to prevent users from accessing parts of the bot that are currently broken
export const DEVMODE = false;
// DEBUG is used to toggle the cmdPrompt
export const DEBUG = false;
// LOCALMODE is used to run a differnt bot token for local testing
export const LOCALMODE = false;

307
mod.ts Normal file
View File

@ -0,0 +1,307 @@
/* The Artificer was built in memory of Babka
* With love, Ean
*
* December 21, 2020
*/
import config from './config.ts';
import { DEBUG, DEVMODE, LOCALMODE } from './flags.ts';
import {
// Discordeno deps
botId,
cache,
DiscordActivityTypes,
DiscordenoGuild,
DiscordenoMessage,
editBotNickname,
editBotStatus,
initLog,
Intents,
// Log4Deno deps
log,
LT,
// Discordeno deps
sendMessage,
startBot,
} from './deps.ts';
// import { dbClient, ignoreList } from './src/db.ts';
import commands from './src/commands/_index.ts';
import intervals from './src/intervals.ts';
import { successColor, warnColor } from './src/commandUtils.ts';
import utils from './src/utils.ts';
// Initialize logging client with folder to use for logs, needs --allow-write set on Deno startup
initLog('logs', DEBUG);
// Start up the Discord Bot
startBot({
token: LOCALMODE ? config.localtoken : config.token,
intents: [Intents.GuildMessages, Intents.DirectMessages, Intents.Guilds],
eventHandlers: {
ready: () => {
log(LT.INFO, `${config.name} Logged in!`);
editBotStatus({
activities: [{
name: 'Booting up . . .',
type: DiscordActivityTypes.Game,
createdAt: new Date().getTime(),
}],
status: 'online',
});
// Interval to rotate the status text every 30 seconds to show off more commands
setInterval(async () => {
log(LT.LOG, 'Changing bot status');
try {
// Wrapped in try-catch due to hard crash possible
editBotStatus({
activities: [{
name: await intervals.getRandomStatus(),
type: DiscordActivityTypes.Game,
createdAt: new Date().getTime(),
}],
status: 'online',
});
} catch (e) {
log(LT.ERROR, `Failed to update status: ${JSON.stringify(e)}`);
}
}, 30000);
// Interval to update bot list stats every 24 hours
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : setInterval(() => {
log(LT.LOG, 'Updating all bot lists statistics');
intervals.updateListStatistics(botId, cache.guilds.size);
}, 86400000);
// Interval to update hourlyRates every hour
setInterval(() => {
log(LT.LOG, 'Updating all command hourlyRates');
intervals.updateHourlyRates();
}, 3600000);
// Interval to update heatmap.png every hour
setInterval(() => {
log(LT.LOG, 'Updating heatmap.png');
intervals.updateHeatmapPng();
}, 3600000);
// setTimeout added to make sure the startup message does not error out
setTimeout(() => {
LOCALMODE && editBotNickname(config.devServer, `LOCAL - ${config.name}`);
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : intervals.updateListStatistics(botId, cache.guilds.size);
intervals.updateHourlyRates();
intervals.updateHeatmapPng();
editBotStatus({
activities: [{
name: 'Booting Complete',
type: DiscordActivityTypes.Game,
createdAt: new Date().getTime(),
}],
status: 'online',
});
sendMessage(config.logChannel, {
embeds: [{
title: `${config.name} is now Online`,
color: successColor,
fields: [
{
name: 'Version:',
value: `${config.version}`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:88', 'Startup', e));
}, 1000);
},
guildCreate: (guild: DiscordenoGuild) => {
log(LT.LOG, `Handling joining guild ${JSON.stringify(guild)}`);
sendMessage(config.logChannel, {
embeds: [{
title: 'New Guild Joined!',
color: successColor,
fields: [
{
name: 'Name:',
value: `${guild.name}`,
inline: true,
},
{
name: 'Id:',
value: `${guild.id}`,
inline: true,
},
{
name: 'Member Count:',
value: `${guild.memberCount}`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:95', 'Join Guild', e));
},
guildDelete: (guild: DiscordenoGuild) => {
log(LT.LOG, `Handling leaving guild ${JSON.stringify(guild)}`);
sendMessage(config.logChannel, {
embeds: [{
title: 'Removed from Guild',
color: warnColor,
fields: [
{
name: 'Name:',
value: `${guild.name}`,
inline: true,
},
{
name: 'Id:',
value: `${guild.id}`,
inline: true,
},
{
name: 'Member Count:',
value: `${guild.memberCount}`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:99', 'Leave Guild', e));
dbClient.execute('DELETE FROM allowed_guilds WHERE guildid = ? AND banned = 0', [guild.id]).catch((e) => utils.commonLoggers.dbError('mod.ts:100', 'delete from', e));
},
debug: DEVMODE ? (dmsg) => log(LT.LOG, `Debug Message | ${JSON.stringify(dmsg)}`) : undefined,
messageCreate: (message: DiscordenoMessage) => {
// Ignore all other bots
if (message.isBot) return;
// Ignore users who requested to be ignored
if (ignoreList.includes(message.authorId) && (!message.content.startsWith(`${config.prefix}opt-in`) || message.guildId !== 0n)) return;
// Ignore all messages that are not commands
if (message.content.indexOf(config.prefix) !== 0) {
// Handle @bot messages
if (message.mentionedUserIds[0] === botId && (message.content.trim().startsWith(`<@${botId}>`) || message.content.trim().startsWith(`<@!${botId}>`))) {
commands.handleMentions(message);
}
// return as we are done handling this command
return;
}
log(LT.LOG, `Handling ${config.prefix}command message: ${JSON.stringify(message)}`);
// Split into standard command + args format
const args = message.content.slice(config.prefix.length).trim().split(/[ \n]+/g);
const command = args.shift()?.toLowerCase();
// All commands below here
switch (command) {
case 'opt-out':
case 'ignore-me':
// [[opt-out or [[ignore-me
// Tells the bot to add you to the ignore list.
commands.optOut(message);
break;
case 'opt-in':
// [[opt-in
// Tells the bot to remove you from the ignore list.
commands.optIn(message);
break;
case 'ping':
// [[ping
// Its a ping test, what else do you want.
commands.ping(message);
break;
case 'rip':
case 'memory':
// [[rip [[memory
// Displays a short message I wanted to include
commands.rip(message);
break;
case 'rollhelp':
case 'rh':
case 'hr':
case '??':
// [[rollhelp or [[rh or [[hr or [[??
// Help command specifically for the roll command
commands.rollHelp(message);
break;
case 'rolldecorators':
case 'rd':
case 'dr':
case '???':
// [[rollDecorators or [[rd or [[dr or [[???
// Help command specifically for the roll command decorators
commands.rollDecorators(message);
break;
case 'help':
case 'h':
case '?':
// [[help or [[h or [[?
// Help command, prints from help file
commands.help(message);
break;
case 'info':
case 'i':
// [[info or [[i
// Info command, prints short desc on bot and some links
commands.info(message);
break;
case 'privacy':
// [[privacy
// Privacy command, prints short desc on bot's privacy policy
commands.privacy(message);
break;
case 'version':
case 'v':
// [[version or [[v
// Returns version of the bot
commands.version(message);
break;
case 'report':
case 'r':
// [[report or [[r (command that failed)
// Manually report a failed roll
commands.report(message, args);
break;
case 'stats':
case 's':
// [[stats or [[s
// Displays stats on the bot
commands.stats(message);
break;
case 'api':
// [[api arg
// API sub commands
commands.api(message, args);
break;
case 'audit':
// [[audit arg
// Audit sub commands
commands.audit(message, args);
break;
case 'heatmap':
case 'hm':
// [[heatmap or [[hm
// Audit sub commands
commands.heatmap(message);
break;
default:
// Non-standard commands
if (command?.startsWith('xdy')) {
// [[xdydz (aka someone copy pasted the template as a roll)
// Help command specifically for the roll command
commands.rollHelp(message);
} else if (command && (`${command}${args.join('')}`).indexOf(config.postfix) > -1) {
// [[roll]]
// Dice rolling commence!
commands.roll(message, args, command);
} else if (command) {
// [[emoji or [[emojialias
// Check if the unhandled command is an emoji request
commands.emoji(message, command);
}
break;
}
},
},
});

316
src/commandUtils.ts Normal file
View File

@ -0,0 +1,316 @@
import config from '../config.ts';
import { CountDetails, SolvedRoll } from './solver/solver.d.ts';
import { RollModifiers } from './mod.d.ts';
export const failColor = 0xe71212;
export const warnColor = 0xe38f28;
export const successColor = 0x0f8108;
export const infoColor1 = 0x313bf9;
export const infoColor2 = 0x6805e9;
export const rollingEmbed = {
embeds: [{
color: infoColor1,
title: 'Rolling . . .',
}],
};
export const generatePing = (time: number) => ({
embeds: [{
color: infoColor1,
title: time === -1 ? 'Ping?' : `Pong! Latency is ${time}ms.`,
}],
});
export const generateReport = (msg: string) => ({
embeds: [{
color: infoColor2,
title: 'USER REPORT:',
description: msg || 'No message',
}],
});
export const generateStats = (guildCount: number, channelCount: number, memberCount: number, rollCount: bigint, utilityCount: bigint, rollRate: number, utilityRate: number) => ({
embeds: [{
color: infoColor2,
title: 'The Artificer\'s Statistics:',
timestamp: new Date().toISOString(),
fields: [
{
name: 'Guilds:',
value: `${guildCount}`,
inline: true,
},
{
name: 'Channels:',
value: `${channelCount}`,
inline: true,
},
{
name: 'Active Members:',
value: `${memberCount}`,
inline: true,
},
{
name: 'Roll Commands:',
value: `${rollCount}
(${rollRate.toFixed(2)} per hour)`,
inline: true,
},
{
name: 'Utility Commands:',
value: `${utilityCount}
(${utilityRate.toFixed(2)} per hour)`,
inline: true,
},
],
}],
});
export const generateApiFailed = (args: string) => ({
embeds: [{
color: failColor,
title: `Failed to ${args} API rolls for this guild.`,
description: 'If this issue persists, please report this to the developers.',
}],
});
export const generateApiStatus = (banned: boolean, active: boolean) => {
const apiStatus = active ? 'allowed' : 'blocked from being used';
return {
embeds: [{
color: infoColor1,
title: `The Artificer's API is ${config.api.enable ? 'currently enabled' : 'currently disabled'}.`,
description: banned ? 'API rolls are banned from being used in this guild.\n\nThis will not be reversed.' : `API rolls are ${apiStatus} in this guild.`,
}],
};
};
export const generateApiSuccess = (args: string) => ({
embeds: [{
color: successColor,
title: `API rolls have successfully been ${args} for this guild.`,
}],
});
export const generateDMFailed = (user: string) => ({
embeds: [{
color: failColor,
title: `WARNING: ${user} could not be messaged.`,
description: 'If this issue persists, make sure direct messages are allowed from this server.',
}],
});
export const generateApiKeyEmail = (email: string, key: string) => ({
content: `<@${config.api.admin}> A USER HAS REQUESTED AN API KEY`,
embeds: [{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Key',
},
{
name: 'Body:',
value: `Hello Artificer API User,
Welcome aboard The Artificer's API. You can find full details about the API on the GitHub: https://github.com/Burn-E99/TheArtificer
Your API Key is: ${key}
Guard this well, as there is zero tolerance for API abuse.
Welcome aboard,
The Artificer Developer - Ean Milligan`,
},
],
}],
});
export const generateApiDeleteEmail = (email: string, deleteCode: string) => ({
content: `<@${config.api.admin}> A USER HAS REQUESTED A DELETE CODE`,
embeds: [{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Delete Code',
},
{
name: 'Body:',
value: `Hello Artificer API User,
I am sorry to see you go. If you would like, please respond to this email detailing what I could have done better.
As requested, here is your delete code: ${deleteCode}
Sorry to see you go,
The Artificer Developer - Ean Milligan`,
},
],
}],
});
export const generateRollError = (errorType: string, errorMsg: string) => ({
embeds: [{
color: failColor,
title: 'Roll command encountered the following error:',
fields: [{
name: errorType,
value: `${errorMsg}\n\nPlease try again. If the error is repeated, please report the issue using the \`${config.prefix}report\` command.`,
}],
}],
});
export const generateCountDetailsEmbed = (counts: CountDetails) => ({
color: infoColor1,
title: 'Roll Count Details:',
fields: [
{
name: 'Total Rolls:',
value: `${counts.total}`,
inline: true,
},
{
name: 'Successful Rolls:',
value: `${counts.successful}`,
inline: true,
},
{
name: 'Failed Rolls:',
value: `${counts.failed}`,
inline: true,
},
{
name: 'Rerolled Dice:',
value: `${counts.rerolled}`,
inline: true,
},
{
name: 'Dropped Dice:',
value: `${counts.dropped}`,
inline: true,
},
{
name: 'Exploded Dice:',
value: `${counts.exploded}`,
inline: true,
},
],
});
export const generateRollEmbed = async (authorId: bigint, returnDetails: SolvedRoll, modifiers: RollModifiers) => {
if (returnDetails.error) {
// Roll had an error, send error embed
return {
embed: {
color: failColor,
title: 'Roll failed:',
description: `${returnDetails.errorMsg}`,
footer: {
text: `Code: ${returnDetails.errorCode}`,
},
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
},
};
} else {
if (modifiers.gmRoll) {
// Roll is a GM Roll, send this in the pub channel (this funciton will be ran again to get details for the GMs)
return {
embed: {
color: infoColor2,
description: `<@${authorId}>${returnDetails.line1}
Results have been messaged to the following GMs: ${modifiers.gms.join(' ')}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
},
};
} else {
// Roll is normal, make normal embed
const line2Details = returnDetails.line2.split(': ');
let details = '';
if (!modifiers.superNoDetails) {
if (modifiers.noDetails) {
details = `**Details:**
Suppressed by -nd flag`;
} else {
details = `**Details:**
${modifiers.spoiler}${returnDetails.line3}${modifiers.spoiler}`;
}
}
const baseDesc = `<@${authorId}>${returnDetails.line1}
**${line2Details.shift()}:**
${line2Details.join(': ')}`;
if (baseDesc.length + details.length < 4090) {
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
},
};
} else {
// If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob([`${baseDesc}\n\n${details}` as BlobPart], { 'type': 'text' });
details = 'Details have been ommitted from this message for being over 2000 characters.';
if (b.size > 8388290) {
details +=
'\n\nFull details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
},
};
} else {
details += '\n\nFull details have been attached to this messaged as a \`.txt\` file for verification purposes.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: true,
attachment: {
'blob': b,
'name': 'rollDetails.txt',
},
};
}
}
}
}
};

11
src/intervals.ts Normal file
View File

@ -0,0 +1,11 @@
// getRandomStatus() returns status as string
// Gets a new random status for the bot
const sweeperLines = ['Reporting broom stolen, Broom stolen!', 'Oh no, no, no, no, no!', 'I have nothing!', 'Whoever has the broom, please bring it back soon. There is so much...sweeping to do.', 'Have you seen my broom?', 'Is theft from a frame a crime?', 'They celebrate lost souls, but what about lost things?', 'Where is it? Where is it?!', 'What does the broom say? Broom Broom.', 'All is lost....All. is. lost!', 'malfunctional frame...will report for recycling...', 'I have lost, a part of me...', 'But in that sweep of death, what dreams may come?', 'Dust to dust to dust to dust!?', 'Who is the thief? Who is the thief...', 'Life...is meaningless...', 'Somebody help me!', 'What is my purpose?', 'They have candy, I have nothing!', 'Dark purple candle...'];
const getRandomStatus = async (): Promise<string> => {
return sweeperLines[Math.floor((Math.random() * sweeperLines.length))];
};
export default {
getRandomStatus,
};

114
src/utils.ts Normal file
View File

@ -0,0 +1,114 @@
/* The Artificer was built in memory of Babka
* With love, Ean
*
* December 21, 2020
*/
import {
// Discordeno deps
DiscordenoMessage,
// Log4Deno deps
log,
LT,
// Discordeno deps
sendMessage,
} from '../deps.ts';
// ask(prompt) returns string
// ask prompts the user at command line for message
const ask = async (question: string, stdin = Deno.stdin, stdout = Deno.stdout): Promise<string> => {
const buf = new Uint8Array(1024);
// Write question to console
await stdout.write(new TextEncoder().encode(question));
// Read console's input into answer
const n = <number> await stdin.read(buf);
const answer = new TextDecoder().decode(buf.subarray(0, n));
return answer.trim();
};
// cmdPrompt(logChannel, botName) returns nothing
// cmdPrompt creates an interactive CLI for the bot, commands can vary
const cmdPrompt = async (logChannel: bigint, botName: string): Promise<void> => {
let done = false;
while (!done) {
// Get a command and its args
const fullCmd = await ask('cmd> ');
// Split the args off of the command and prep the command
const args = fullCmd.split(' ');
const command = args.shift()?.toLowerCase();
// All commands below here
// exit or e
// Fully closes the bot
if (command === 'exit' || command === 'e') {
console.log(`${botName} Shutting down.\n\nGoodbye.`);
done = true;
Deno.exit(0);
} // stop
// Closes the CLI only, leaving the bot running truly headless
else if (command === 'stop') {
console.log(`Closing ${botName} CLI. Bot will continue to run.\n\nGoodbye.`);
done = true;
} // m [channel] [message]
// Sends [message] to specified [channel]
else if (command === 'm') {
try {
const channelId = args.shift() || '';
const message = args.join(' ');
sendMessage(BigInt(channelId), message).catch((reason) => {
console.error(reason);
});
} catch (e) {
console.error(e);
}
} // ml [message]
// Sends a message to the specified log channel
else if (command === 'ml') {
const message = args.join(' ');
sendMessage(logChannel, message).catch((reason) => {
console.error(reason);
});
} // help or h
// Shows a basic help menu
else if (command === 'help' || command === 'h') {
console.log(`${botName} CLI Help:
Available Commands:
exit - closes bot
stop - closes the CLI
m [ChannelID] [messgae] - sends message to specific ChannelID as the bot
ml [message] sends a message to the specified botlog
help - this message`);
} // Unhandled commands die here
else {
console.log('undefined command');
}
}
};
const genericLogger = (level: LT, message: string) => log(level, message);
const messageEditError = (location: string, message: DiscordenoMessage | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to edit message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`);
const messageSendError = (location: string, message: DiscordenoMessage | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to send message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`);
const messageDeleteError = (location: string, message: DiscordenoMessage | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to delete message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`);
const dbError = (location: string, type: string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to ${type} database | Error: ${err.name} - ${err.message}`);
export default {
commonLoggers: {
dbError,
messageEditError,
messageSendError,
messageDeleteError,
},
cmdPrompt,
};

1
start.command Normal file
View File

@ -0,0 +1 @@
deno run --allow-write=./logs/ --allow-net .\mod.ts

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"allowJs": true,
"lib": ["es2022"],
"strict": true
}
}