mirror of
https://github.com/keanuplayz/TravBot-v3.git
synced 2024-08-15 02:33:12 +00:00
Made radical changes and setup foundation for SQLite database
This commit is contained in:
parent
69a8452574
commit
16e42be58d
22 changed files with 715 additions and 639 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@ test*
|
|||
!test/
|
||||
*.bat
|
||||
desktop.ini
|
||||
*.db
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
- [Structure](#structure)
|
||||
- [Version Numbers](#version-numbers)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Utility Functions](#utility-functions)
|
||||
- [Testing](#testing)
|
||||
|
||||
|
@ -34,6 +35,17 @@ Because versions are assigned to batches of changes rather than single changes (
|
|||
|
||||
*Note: This system doesn't retroactively apply to TravBot-v2, which is why this version naming system won't make sense for v2's changelog.*
|
||||
|
||||
# Environment Variables
|
||||
|
||||
Certain variables are set via `.env` at the project root. These are for system configuration and should never change dynamically within the program, essentially read-only variables.
|
||||
- `TOKEN`: Your bot's token
|
||||
- `PREFIX`: Your bot's prefix
|
||||
- `OWNER`: The ID of the owner
|
||||
- `ADMINS`: A comma-separated (with a space) list of bot admin IDs
|
||||
- `SUPPORT`: A comma-separated (with a space) list of bot support IDs
|
||||
- `WOLFRAM_API_KEY`: Used for `commands/utility/calc`
|
||||
- `DEV`: Enables dev mode as long as it isn't a falsy value (`DEV=1` works for example)
|
||||
|
||||
# Utility Functions
|
||||
|
||||
## [src/lib](../src/lib.ts) - General utility functions
|
||||
|
|
|
@ -52,7 +52,7 @@ Rather than have an `events` folder which contains dynamically loaded events, yo
|
|||
```ts
|
||||
import {client} from "..";
|
||||
|
||||
client.on("message", (message) => {
|
||||
client.on("messageCreate", (message) => {
|
||||
//...
|
||||
});
|
||||
```
|
||||
|
|
1002
package-lock.json
generated
1002
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -4,23 +4,20 @@
|
|||
"description": "TravBot Discord bot.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "node -r dotenv/config .",
|
||||
"build": "rimraf dist && tsc --project tsconfig.prod.json && npm prune --production",
|
||||
"start": "node .",
|
||||
"once": "tsc && npm start",
|
||||
"dev": "tsc-watch --onSuccess \"npm run dev-instance\"",
|
||||
"dev-fast": "tsc-watch --onSuccess \"node . dev\"",
|
||||
"dev-instance": "rimraf dist && tsc && node . dev",
|
||||
"dev": "tsc-watch --onSuccess \"npm start\"",
|
||||
"test": "jest",
|
||||
"format": "prettier --write **/*",
|
||||
"postinstall": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^7.4.5",
|
||||
"canvas": "^2.8.0",
|
||||
"chalk": "^4.1.2",
|
||||
"discord.js": "^13.3.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"figlet": "^1.5.2",
|
||||
"glob": "^7.2.0",
|
||||
"inquirer": "^8.2.0",
|
||||
"moment": "^2.29.1",
|
||||
"ms": "^2.1.3",
|
||||
"node-wolfram-alpha": "^1.2.5",
|
||||
|
@ -30,9 +27,8 @@
|
|||
"weather-js": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.4.1",
|
||||
"@types/figlet": "^1.5.4",
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/inquirer": "^8.1.3",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/mathjs": "^9.4.1",
|
||||
"@types/ms": "^0.7.31",
|
||||
|
|
|
@ -8,7 +8,7 @@ module.exports = {
|
|||
jsxSingleQuote: false,
|
||||
trailingComma: "none",
|
||||
bracketSpacing: false,
|
||||
jsxBracketSameLine: false,
|
||||
bracketSameLine: false,
|
||||
arrowParens: "always",
|
||||
endOfLine: "auto" // Apparently, the GitHub repository still uses CRLF. I don't know how to force it to use LF, and until someone figures that out, I'm changing this to auto because I don't want more than one line ending commit.
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ export const BetCommand = new NamedCommand({
|
|||
|
||||
// handle invalid target
|
||||
if (target.id == author.id) return send("You can't bet Mons with yourself!");
|
||||
else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!");
|
||||
else if (target.bot && !process.env.DEV) return send("You can't bet Mons with a bot!");
|
||||
|
||||
return send("How much are you betting?");
|
||||
} else return;
|
||||
|
@ -34,7 +34,7 @@ export const BetCommand = new NamedCommand({
|
|||
|
||||
// handle invalid target
|
||||
if (target.id == author.id) return send("You can't bet Mons with yourself!");
|
||||
else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!");
|
||||
else if (target.bot && !process.env.DEV) return send("You can't bet Mons with a bot!");
|
||||
|
||||
// handle invalid amount
|
||||
if (amount <= 0) return send("You must bet at least one Mon!");
|
||||
|
@ -68,7 +68,7 @@ export const BetCommand = new NamedCommand({
|
|||
|
||||
// handle invalid target
|
||||
if (target.id == author.id) return send("You can't bet Mons with yourself!");
|
||||
else if (target.bot && !IS_DEV_MODE) return send("You can't bet Mons with a bot!");
|
||||
else if (target.bot && !!process.env.DEV) return send("You can't bet Mons with a bot!");
|
||||
|
||||
// handle invalid amount
|
||||
if (amount <= 0) return send("You must bet at least one Mon!");
|
||||
|
|
|
@ -143,7 +143,7 @@ export const PayCommand = new NamedCommand({
|
|||
embeds: [getMoneyEmbed(author, true)]
|
||||
});
|
||||
else if (target.id === author.id) return send("You can't send Mons to yourself!");
|
||||
else if (target.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!");
|
||||
else if (target.bot && !process.env.DEV) return send("You can't send Mons to a bot!");
|
||||
|
||||
sender.money -= amount;
|
||||
receiver.money += amount;
|
||||
|
@ -179,7 +179,7 @@ export const PayCommand = new NamedCommand({
|
|||
const user = await getUserByNickname(args.join(" "), guild);
|
||||
if (typeof user === "string") return send(user);
|
||||
else if (user.id === author.id) return send("You can't send Mons to yourself!");
|
||||
else if (user.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!");
|
||||
else if (user.bot && !process.env.DEV) return send("You can't send Mons to a bot!");
|
||||
|
||||
const confirmed = await confirm(
|
||||
await send({
|
||||
|
|
|
@ -42,7 +42,7 @@ export const AwardCommand = new NamedCommand({
|
|||
run: "You need to specify a user!",
|
||||
user: new Command({
|
||||
async run({send, author, args}) {
|
||||
if (author.id === "394808963356688394" || IS_DEV_MODE) {
|
||||
if (author.id === "394808963356688394" || process.env.DEV) {
|
||||
const target = args[0] as User;
|
||||
const user = Storage.getUser(target.id);
|
||||
user.money++;
|
||||
|
@ -54,7 +54,7 @@ export const AwardCommand = new NamedCommand({
|
|||
},
|
||||
number: new Command({
|
||||
async run({send, author, args}) {
|
||||
if (author.id === "394808963356688394" || IS_DEV_MODE) {
|
||||
if (author.id === "394808963356688394" || process.env.DEV) {
|
||||
const target = args[0] as User;
|
||||
const amount = Math.floor(args[1]);
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje
|
|||
}
|
||||
|
||||
export function isAuthorized(guild: Guild | null, channel: TextBasedChannels): boolean {
|
||||
if (IS_DEV_MODE) {
|
||||
if (process.env.DEV) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Command, NamedCommand, getPermissionLevel, getPermissionName, CHANNEL_TYPE, RestCommand} from "onion-lasers";
|
||||
import {Config, Storage} from "../../structures";
|
||||
import {Config, Storage, getPrefix} from "../../structures";
|
||||
import {Permissions, TextChannel, User, Role, Channel, Util} from "discord.js";
|
||||
import {logs} from "../../modules/globals";
|
||||
import {logs} from "../../modules/logger";
|
||||
|
||||
function getLogBuffer(type: string) {
|
||||
return {
|
||||
|
@ -38,7 +38,7 @@ export default new NamedCommand({
|
|||
Storage.getGuild(guild!.id).prefix = null;
|
||||
Storage.save();
|
||||
send(
|
||||
`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`
|
||||
`The custom prefix for this guild has been removed. My prefix is now back to \`${getPrefix()}\`.`
|
||||
);
|
||||
},
|
||||
any: new Command({
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import {NamedCommand, RestCommand} from "onion-lasers";
|
||||
import {WolframClient} from "node-wolfram-alpha";
|
||||
import {MessageEmbed} from "discord.js";
|
||||
import {Config} from "../../structures";
|
||||
|
||||
export default new NamedCommand({
|
||||
description: "Calculates a specified math expression.",
|
||||
run: "Please provide a calculation.",
|
||||
any: new RestCommand({
|
||||
async run({send, combined}) {
|
||||
if (Config.wolfram === null) return send("There's no Wolfram token in the config.");
|
||||
if (!process.env.WOLFRAM_API_KEY) return send("There's no Wolfram API key in `.env`.");
|
||||
|
||||
const wClient = new WolframClient(Config.wolfram);
|
||||
const wClient = new WolframClient(process.env.WOLFRAM_API_KEY);
|
||||
let resp;
|
||||
try {
|
||||
resp = await wClient.query(combined);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {MessageEmbed, version as djsversion, Guild, User, GuildMember, TextChannel, VoiceChannel} from "discord.js";
|
||||
import {MessageEmbed, version, Guild, User, GuildMember, TextChannel, VoiceChannel} from "discord.js";
|
||||
import ms from "ms";
|
||||
import os from "os";
|
||||
import {Command, NamedCommand, getUserByNickname, CHANNEL_TYPE, getGuildByName, RestCommand} from "onion-lasers";
|
||||
|
@ -68,8 +68,12 @@ export default new NamedCommand({
|
|||
"Do MMMM YYYY HH:mm:ss"
|
||||
)}`,
|
||||
`**❯ Node.JS:** ${process.version}`,
|
||||
`**❯ Version:** v${process.env.npm_package_version}`,
|
||||
`**❯ Discord.JS:** v${djsversion}`,
|
||||
`**❯ Version:** ${
|
||||
process.env.npm_package_version
|
||||
? `v${process.env.npm_package_version}`
|
||||
: "*Unable to fetch version, make sure to start the project via `npm start`, not `node`!*"
|
||||
}`,
|
||||
`**❯ Discord.JS:** v${version}`,
|
||||
"\u200b"
|
||||
].join("\n")
|
||||
)
|
||||
|
|
19
src/index.ts
19
src/index.ts
|
@ -1,6 +1,5 @@
|
|||
import "./modules/globals";
|
||||
import "./modules/logger";
|
||||
import {Client, Permissions, Intents} from "discord.js";
|
||||
import path from "path";
|
||||
|
||||
// This is here in order to make it much less of a headache to access the client from other files.
|
||||
// This of course won't actually do anything until the setup process is complete and it logs in.
|
||||
|
@ -17,18 +16,16 @@ export const client = new Client({
|
|||
]
|
||||
});
|
||||
|
||||
import {join} from "path";
|
||||
import {launch} from "onion-lasers";
|
||||
import setup from "./modules/setup";
|
||||
import {Config, getPrefix} from "./structures";
|
||||
import {getPrefix} from "./structures";
|
||||
import {toTitleCase} from "./lib";
|
||||
|
||||
// Send the login request to Discord's API and then load modules while waiting for it.
|
||||
setup.init().then(() => {
|
||||
client.login(Config.token).catch(setup.again);
|
||||
});
|
||||
client.login(process.env.TOKEN).catch(console.error);
|
||||
|
||||
// Setup the command handler.
|
||||
launch(client, path.join(__dirname, "commands"), {
|
||||
launch(client, join(__dirname, "commands"), {
|
||||
getPrefix,
|
||||
categoryTransformer: toTitleCase,
|
||||
permissionLevels: [
|
||||
|
@ -60,17 +57,17 @@ launch(client, path.join(__dirname, "commands"), {
|
|||
{
|
||||
// BOT_SUPPORT //
|
||||
name: "Bot Support",
|
||||
check: (user) => Config.support.includes(user.id)
|
||||
check: (user) => !!process.env.SUPPORT && process.env.SUPPORT.split(", ").includes(user.id)
|
||||
},
|
||||
{
|
||||
// BOT_ADMIN //
|
||||
name: "Bot Admin",
|
||||
check: (user) => Config.admins.includes(user.id)
|
||||
check: (user) => !!process.env.ADMINS && process.env.ADMINS.split(", ").includes(user.id)
|
||||
},
|
||||
{
|
||||
// BOT_OWNER //
|
||||
name: "Bot Owner",
|
||||
check: (user) => Config.owner === user.id
|
||||
check: (user) => process.env.OWNER === user.id
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
145
src/modules/database.ts
Normal file
145
src/modules/database.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
import Database from "better-sqlite3";
|
||||
import {existsSync} from "fs";
|
||||
import {join} from "path";
|
||||
|
||||
// This section will serve as the documentation for the database, because in order to guarantee
|
||||
// that a database created now will have the same structure as a database that has been migrated
|
||||
// through different versions, a new database starts at version one and goes through the same
|
||||
// migration process. Creating separate statements for migrations and creating a new database will
|
||||
// allow for some dangerous out of sync definitions. For example, version 9 via migration might
|
||||
// have a column that forgot to be dropped while version 9 via creation won't include that column,
|
||||
// so when someone tries to use an INSERT statement, it'll throw an error because of discrepancies.
|
||||
|
||||
// -=[ Current Schema ]=-
|
||||
// System: Version (INT UNIQUE)
|
||||
// Users: ID, Money (INT), LastReceived (TIME), LastMonday (TIME), TimezoneOffset (INT NULLABLE), DaylightSavingsRegion (INT), EcoBetInsurance (INT)
|
||||
// Guilds: ID, Prefix (TEXT NULLABLE), WelcomeType (INT), WelcomeChannel (TEXT NULLABLE), WelcomeMessage (TEXT NULLABLE), StreamingChannel (TEXT NULLABLE), HasMessageEmbeds (BOOL)
|
||||
// Members: UserID, GuildID, StreamCategory (TEXT NULLABLE)
|
||||
// Webhooks: ID, Token (TEXT)
|
||||
// TodoLists: UserID, Timestamp (TIME), Entry (TEXT)
|
||||
// StreamingRoles: GuildID, RoleID, Category (TEXT)
|
||||
// DefaultChannelNames: GuildID, ChannelID, Name (TEXT)
|
||||
// AutoRoles: GuildID, RoleID
|
||||
|
||||
// -=[ Notes ]=-
|
||||
// - Unless otherwise directed above (NULLABLE), assume the "NOT NULL" constraint.
|
||||
// - IDs use the "UNIQUE ON CONFLICT REPLACE" constraint to enable implicit UPSERT statements.
|
||||
// - This way, you don't need to do INSERT INTO ... ON CONFLICT(...) DO UPDATE SET ...
|
||||
// - For the sake of simplicity, any Discord ID will be stored and retrieved as a string.
|
||||
// - Any datetime stuff (marked as TIME) will be stored as a UNIX timestamp in milliseconds (INT).
|
||||
// - Booleans (marked as BOOL) will be stored as an integer, either 0 or 1 (though it just checks for 0).
|
||||
|
||||
// Calling migrations[2]() migrates the database from version 2 to version 3.
|
||||
// NOTE: Once a migration is written, DO NOT change that migration or it'll break all future migrations.
|
||||
const migrations: (() => void)[] = [
|
||||
() => {
|
||||
const hasLegacyData = existsSync(join("data", "config.json")) && existsSync(join("data", "storage.json"));
|
||||
|
||||
// Generate initial state
|
||||
// Stuff like CREATE TABLE IF NOT EXISTS should be handled by the migration system.
|
||||
generateSQLMigration([
|
||||
`CREATE TABLE System (
|
||||
Version INT NOT NULL UNIQUE
|
||||
)`,
|
||||
"INSERT INTO System VALUES (1)",
|
||||
`CREATE TABLE Users (
|
||||
ID TEXT NOT NULL UNIQUE ON CONFLICT REPLACE,
|
||||
Money INT NOT NULL DEFAULT 0,
|
||||
LastReceived INT NOT NULL DEFAULT -1,
|
||||
LastMonday INT NOT NULL DEFAULT -1,
|
||||
TimezoneOffset INT,
|
||||
DaylightSavingsRegion INT NOT NULL DEFAULT 0,
|
||||
EcoBetInsurance INT NOT NULL DEFAULT 0
|
||||
)`,
|
||||
`CREATE TABLE Guilds (
|
||||
ID TEXT NOT NULL UNIQUE ON CONFLICT REPLACE,
|
||||
Prefix TEXT,
|
||||
WelcomeType INT NOT NULL DEFAULT 0,
|
||||
WelcomeChannel TEXT,
|
||||
WelcomeMessage TEXT,
|
||||
StreamingChannel TEXT,
|
||||
HasMessageEmbeds INT NOT NULL CHECK(HasMessageEmbeds BETWEEN 0 AND 1) DEFAULT 1
|
||||
)`,
|
||||
`CREATE TABLE Members (
|
||||
UserID TEXT NOT NULL,
|
||||
GuildID TEXT NOT NULL,
|
||||
StreamCategory TEXT,
|
||||
UNIQUE (UserID, GuildID) ON CONFLICT REPLACE
|
||||
)`,
|
||||
`CREATE TABLE Webhooks (
|
||||
ID TEXT NOT NULL UNIQUE ON CONFLICT REPLACE,
|
||||
Token TEXT NOT NULL
|
||||
)`,
|
||||
`CREATE TABLE TodoLists (
|
||||
UserID TEXT NOT NULL,
|
||||
Timestamp INT NOT NULL,
|
||||
Entry TEXT NOT NULL
|
||||
)`,
|
||||
`CREATE TABLE StreamingRoles (
|
||||
GuildID TEXT NOT NULL,
|
||||
RoleID TEXT NOT NULL UNIQUE ON CONFLICT REPLACE,
|
||||
Category TEXT NOT NULL
|
||||
)`,
|
||||
`CREATE TABLE DefaultChannelNames (
|
||||
GuildID TEXT NOT NULL,
|
||||
ChannelID TEXT NOT NULL UNIQUE ON CONFLICT REPLACE,
|
||||
Name TEXT NOT NULL
|
||||
)`,
|
||||
`CREATE TABLE AutoRoles (
|
||||
GuildID TEXT NOT NULL,
|
||||
RoleID TEXT NOT NULL UNIQUE ON CONFLICT REPLACE
|
||||
)`
|
||||
])();
|
||||
|
||||
// Load initial data if present
|
||||
if (hasLegacyData) {
|
||||
generateSQLMigration([])();
|
||||
}
|
||||
}
|
||||
// "UPDATE System SET Version=2" when the time comes
|
||||
];
|
||||
|
||||
const isExistingDatabase = existsSync(join("data", "main.db"));
|
||||
export const db = new Database(join("data", "main.db"));
|
||||
let version = -1;
|
||||
|
||||
// Get existing version if applicable and throw error if corrupt data.
|
||||
// The data is considered corrupt if it exists and:
|
||||
// - The System table doesn't exist (throws an error)
|
||||
// - There isn't exactly one entry in System for Version
|
||||
if (isExistingDatabase) {
|
||||
try {
|
||||
const {Version, Amount} = db.prepare("SELECT Version, Count(Version) AS Amount FROM System").get() as {
|
||||
Version: number | null;
|
||||
Amount: number;
|
||||
};
|
||||
|
||||
if (!Version) {
|
||||
console.error("No version entry in the System table.");
|
||||
} else if (Amount === 1) {
|
||||
version = Version;
|
||||
} else {
|
||||
console.error("More than one version entry in the System table.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error("Invalid database, take a look at it manually.");
|
||||
}
|
||||
} else {
|
||||
version = 0;
|
||||
}
|
||||
|
||||
// Then loop through all the versions
|
||||
if (version !== -1) {
|
||||
for (let v = version; v < migrations.length; v++) {
|
||||
migrations[v]();
|
||||
}
|
||||
}
|
||||
|
||||
function generateSQLMigration(statements: string[]): () => void {
|
||||
return () => {
|
||||
for (const statement of statements) {
|
||||
db.prepare(statement).run();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -5,7 +5,7 @@ import {client} from "../index";
|
|||
// - "oil" will remain the same though, it's better that way (anything even remotely "oil"-related calls the image)
|
||||
// - Also uwu and owo penalties
|
||||
|
||||
client.on("message", (message) => {
|
||||
client.on("messageCreate", (message) => {
|
||||
if (message.content.toLowerCase().includes("remember to drink water")) {
|
||||
message.react("🚱");
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import chalk from "chalk";
|
||||
|
||||
declare global {
|
||||
var IS_DEV_MODE: boolean;
|
||||
var PERMISSIONS: typeof PermissionsEnum;
|
||||
var BOT_VERSION: string;
|
||||
|
||||
interface Console {
|
||||
ready: (...data: any[]) => void;
|
||||
|
@ -20,9 +18,7 @@ enum PermissionsEnum {
|
|||
BOT_OWNER
|
||||
}
|
||||
|
||||
global.IS_DEV_MODE = process.argv[2] === "dev";
|
||||
global.PERMISSIONS = PermissionsEnum;
|
||||
global.BOT_VERSION = "3.2.3";
|
||||
|
||||
const oldConsole = console;
|
||||
|
||||
|
@ -84,7 +80,8 @@ console = {
|
|||
// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> <path>/::(<object>.)<function>(<args>) = <value>
|
||||
// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests.
|
||||
debug(...args: any[]) {
|
||||
if (IS_DEV_MODE) oldConsole.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args);
|
||||
if (process.env.DEV)
|
||||
oldConsole.debug(chalk.white.bgGray(formatTimestamp()), chalk.white.bgBlue("DEBUG"), ...args);
|
||||
const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`;
|
||||
logs.verbose += text;
|
||||
},
|
||||
|
@ -96,5 +93,3 @@ console = {
|
|||
logs.verbose += text;
|
||||
}
|
||||
};
|
||||
|
||||
console.log("Loading globals...");
|
|
@ -3,7 +3,7 @@ import {MessageEmbed} from "discord.js";
|
|||
import {getPrefix} from "../structures";
|
||||
import {getMessageByID} from "onion-lasers";
|
||||
|
||||
client.on("message", (message) => {
|
||||
client.on("messageCreate", (message) => {
|
||||
(async () => {
|
||||
// Only execute if the message is from a user and isn't a command.
|
||||
if (message.content.startsWith(getPrefix(message.guild)) || message.author.bot) return;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {client} from "../index";
|
||||
import {Config, Storage} from "../structures";
|
||||
import {Storage, getPrefix} from "../structures";
|
||||
|
||||
client.once("ready", () => {
|
||||
if (client.user) {
|
||||
|
@ -8,7 +8,7 @@ client.once("ready", () => {
|
|||
);
|
||||
client.user.setActivity({
|
||||
type: "LISTENING",
|
||||
name: `${Config.prefix}help`
|
||||
name: `${getPrefix()}help`
|
||||
});
|
||||
|
||||
// Run this setup block once to restore eco bet money in case the bot went down. (And I guess search the client for those users to let them know too.)
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
import {existsSync as exists} from "fs";
|
||||
import inquirer from "inquirer";
|
||||
import Storage from "./storage";
|
||||
import {Config} from "../structures";
|
||||
|
||||
// This file is called (or at least should be called) automatically as long as a config file doesn't exist yet.
|
||||
// And that file won't be written until the data is successfully initialized.
|
||||
const prompts = [
|
||||
{
|
||||
type: "password",
|
||||
name: "token",
|
||||
message: "What's your bot's token?",
|
||||
mask: true
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "prefix",
|
||||
message: "What do you want your bot's prefix to be?",
|
||||
default: "$"
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "owner",
|
||||
message: "Enter the owner's user ID here."
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "admins",
|
||||
message: "Enter a list of bot admins (by their IDs) separated by spaces."
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "support",
|
||||
message: "Enter a list of bot troubleshooters (by their IDs) separated by spaces."
|
||||
}
|
||||
];
|
||||
|
||||
export default {
|
||||
async init() {
|
||||
while (!exists("data/config.json")) {
|
||||
const answers = await inquirer.prompt(prompts);
|
||||
Storage.open("data");
|
||||
Config.token = answers.token as string;
|
||||
Config.prefix = answers.prefix as string;
|
||||
Config.owner = answers.owner as string;
|
||||
const admins = answers.admins as string;
|
||||
Config.admins = admins !== "" ? admins.split(" ") : [];
|
||||
const support = answers.support as string;
|
||||
Config.support = support !== "" ? support.split(" ") : [];
|
||||
Config.save(false);
|
||||
}
|
||||
},
|
||||
/** Prompt the user to set their token again. */
|
||||
async again() {
|
||||
console.error("It seems that the token you provided is invalid.");
|
||||
|
||||
// Deactivate the console //
|
||||
const oldConsole = console;
|
||||
console = {
|
||||
...oldConsole,
|
||||
log() {},
|
||||
warn() {},
|
||||
error() {},
|
||||
debug() {},
|
||||
ready() {}
|
||||
};
|
||||
|
||||
const answers = await inquirer.prompt(prompts.slice(0, 1));
|
||||
Config.token = answers.token as string;
|
||||
Config.save(false);
|
||||
process.exit();
|
||||
}
|
||||
};
|
|
@ -13,7 +13,9 @@ const Storage = {
|
|||
try {
|
||||
data = JSON.parse(file);
|
||||
} catch (error) {
|
||||
if (process.argv[2] !== "dev") {
|
||||
console.error(error, file);
|
||||
|
||||
if (!process.env.DEV) {
|
||||
console.warn("[storage.read]", `Malformed JSON data (header: ${header}), backing it up.`, file);
|
||||
fs.writeFile(`${path}.backup`, file, (error) => {
|
||||
if (error) console.error("[storage.read]", error);
|
||||
|
@ -30,7 +32,7 @@ const Storage = {
|
|||
this.open("data");
|
||||
const path = `data/${header}.json`;
|
||||
|
||||
if (IS_DEV_MODE || header === "config") {
|
||||
if (process.env.DEV || header === "config") {
|
||||
const result = JSON.stringify(data, null, "\t");
|
||||
|
||||
if (asynchronous)
|
||||
|
|
|
@ -8,25 +8,11 @@ import {Guild as DiscordGuild, Snowflake} from "discord.js";
|
|||
// And maybe use Collections/Maps instead of objects?
|
||||
|
||||
class ConfigStructure extends GenericStructure {
|
||||
public token: string;
|
||||
public prefix: string;
|
||||
public owner: string;
|
||||
public admins: string[];
|
||||
public support: string[];
|
||||
public lavalink: boolean | null;
|
||||
public wolfram: string | null;
|
||||
public systemLogsChannel: string | null;
|
||||
public webhooks: {[id: string]: string}; // id-token pairs
|
||||
|
||||
constructor(data: GenericJSON) {
|
||||
super("config");
|
||||
this.token = select(data.token, "<ENTER YOUR TOKEN HERE>", String);
|
||||
this.prefix = select(data.prefix, "$", String);
|
||||
this.owner = select(data.owner, "", String);
|
||||
this.admins = select(data.admins, [], String, true);
|
||||
this.support = select(data.support, [], String, true);
|
||||
this.lavalink = select(data.lavalink, null, Boolean);
|
||||
this.wolfram = select(data.wolfram, null, String);
|
||||
this.systemLogsChannel = select(data.systemLogsChannel, null, String);
|
||||
this.webhooks = {};
|
||||
|
||||
|
@ -211,7 +197,7 @@ export let Storage = new StorageStructure(FileManager.read("storage"));
|
|||
|
||||
// This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache.
|
||||
// However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues.
|
||||
if (IS_DEV_MODE) {
|
||||
if (process.env.DEV) {
|
||||
watch("data", (_event, filename) => {
|
||||
const header = filename.substring(0, filename.indexOf(".json"));
|
||||
|
||||
|
@ -229,19 +215,17 @@ if (IS_DEV_MODE) {
|
|||
/**
|
||||
* Get the current prefix of the guild or the bot's prefix if none is found.
|
||||
*/
|
||||
export function getPrefix(guild: DiscordGuild | null): string {
|
||||
let prefix = Config.prefix;
|
||||
|
||||
export function getPrefix(guild?: DiscordGuild | null): string {
|
||||
if (guild) {
|
||||
const possibleGuildPrefix = Storage.getGuild(guild.id).prefix;
|
||||
|
||||
// Here, lossy comparison works in our favor because you wouldn't want an empty string to trigger the prefix.
|
||||
if (possibleGuildPrefix) {
|
||||
prefix = possibleGuildPrefix;
|
||||
return possibleGuildPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
return prefix;
|
||||
return process.env.PREFIX || "$";
|
||||
}
|
||||
|
||||
export interface EmoteRegistryDumpEntry {
|
||||
|
|
Loading…
Reference in a new issue