diff --git a/config/config.example.jsonc b/config/config.example.jsonc index c1eeb864..7efa26a9 100644 --- a/config/config.example.jsonc +++ b/config/config.example.jsonc @@ -4,8 +4,21 @@ "mainColor": "f6c42f", // The hex color of the embeds by default "lang": "main", // If you want to set english please set "main" + /* + Pick a database driver, postgres will take priority over mysql (if both are enabled, which please don't do). + If neither option is enabled, SQLite will be used. + *PostgreSQL will be using default schema* + */ + "postgre": { + "enabled": false, + "host": "postgresql.example.com", // The host of the PostgreSQL database + "user": "postgres", // The user of the PostgreSQL database + "password": "password", // The password of the PostgreSQL database + "database": "postgres", // The name of the PostgreSQL database + "table": "json" // The name of the table where the tickets will be saved + }, "mysql": { - "enabled": false, // If you want to use MySQL set to true, if not set to false to use SQLite + "enabled": false, "host": "mysql.example.com", // The host of the MySQL database "user": "mysql", // The user of the MySQL database "password": "password", // The password of the MySQL database diff --git a/events/ready.js b/events/ready.js index 12bcb4b3..1c7dcc43 100644 --- a/events/ready.js +++ b/events/ready.js @@ -134,8 +134,9 @@ module.exports = { if (client.config.status.type && client.config.status.text) { // If the user just want to set the status but not the activity + const url = client.config.status.url; client.user.setPresence({ - activities: [{ name: client.config.status.text, type: type, url: client.config.status.url }], + activities: [{ name: client.config.status.text, type: type, url: (url && url.trim() !== "") ? url : undefined }], status: client.config.status.status, }); } diff --git a/index.js b/index.js index 3d6babc7..e8c2be2b 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,6 @@ const { Client, Collection, GatewayIntentBits } = require("discord.js"); const { token } = require("./config/token.json"); const { QuickDB, MySQLDriver } = require("quick.db"); const jsonc = require("jsonc"); -const { exec } = require("child_process"); process.on("unhandledRejection", (reason, promise, a) => { console.log(reason, promise, a); @@ -39,15 +38,20 @@ process.stdout.write(` Connecting to Discord... `); -fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res)=> { - if(Math.floor(res.status / 100) !== 2) return console.warn("[Version Check] Failed to pull latest version from server"); +// Update Detector +fetch("https://api.github.com/repos/Sayrix/Ticket-Bot/tags").then((res) => { + if (Math.floor(res.status / 100) !== 2) return console.warn("[Version Check] Failed to pull latest version from server"); res.json().then((json) => { // Assumign the format stays consistent (i.e. x.x.x) - const latest = json[0].name.split(".").map(k=>parseInt(k)); - const current = require("./package.json").version.split(".").map(k=>parseInt(k)); - if(latest[0] > current[0] || - (latest[0] === current[0] && latest[1] > current[1]) || - (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2])) + const latest = json[0].name.split(".").map((k) => parseInt(k)); + const current = require("./package.json") + .version.split(".") + .map((k) => parseInt(k)); + if ( + latest[0] > current[0] || + (latest[0] === current[0] && latest[1] > current[1]) || + (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2]) + ) console.warn(`[Version Check] New version available: ${json[0].name}; Current Version: ${current.join(".")}`); else console.log("[Version Check] Up to date"); }); @@ -68,9 +72,37 @@ client.config = jsonc.parse(fs.readFileSync(path.join(__dirname, "config/config. let db = null; -if (client.config.mysql?.enabled) { +if (client.config.postgre?.enabled) { + // PostgreSQL Support. (async () => { try { + // eslint-disable-next-line node/no-missing-require + require.resolve("pg"); + } catch (e) { + console.error("pg driver is not installed!\n\nPlease run \"npm i pg\" in the console!"); + throw e.code; + } + const PostgresDriver = require("./utils/pgsqlDriver"); + const pgsql = new PostgresDriver({ + host: client.config.postgre?.host, + user: client.config.postgre?.user, + password: client.config.postgre?.password, + database: client.config.postgre?.database + }); + + await pgsql.connect(); + + db = new QuickDB({ + driver: pgsql, + table: client.config.postgre?.table ?? "json" + }); + client.db = db; + })(); +} else if (client.config.mysql?.enabled) { + // MySQL Support + (async () => { + try { + // eslint-disable-next-line node/no-missing-require require.resolve("mysql2"); } catch (e) { console.error("mysql2 is not installed!\n\nPlease run \"npm i mysql2\" in the console!"); @@ -94,6 +126,7 @@ if (client.config.mysql?.enabled) { client.db = db; })(); } else { + // SQLite Support db = new QuickDB(); client.db = db; } diff --git a/package.json b/package.json index b781bae6..2c81615d 100644 --- a/package.json +++ b/package.json @@ -26,24 +26,25 @@ }, "homepage": "https://github.com/Sayrix/ticket-bot#readme", "dependencies": { - "axios": "^1.3.4", - "better-sqlite3": "^8.2.0", - "discord.js": "^14.8.0", + "axios": "^1.4.0", + "better-sqlite3": "^8.4.0", + "discord.js": "^14.11.0", "fs-extra": "^11.1.1", "jsonc": "^2.0.0", - "mysql2": "^3.2.0", - "quick.db": "^9.1.3", + "mysql2": "^3.3.5", + "pg": "^8.11.0", + "quick.db": "^9.1.6", "readline": "^1.3.0", "ticket-bot-transcript-uploader": "^1.3.0", "websocket": "^1.0.34" }, "devDependencies": { - "eslint": "^8.37.0", + "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.7" + "prettier": "^2.8.8" } } diff --git a/utils/logs.js b/utils/logs.js index db274233..63d85fc3 100644 --- a/utils/logs.js +++ b/utils/logs.js @@ -25,12 +25,8 @@ module.exports = { .catch((e) => console.error("The channel to log events is not found!\n", e)); if (!channel) return console.error("The channel to log events is not found!"); - let webhooks = await channel.fetchWebhooks(); - if (webhooks.size === 0) { - await channel.createWebhook({ name: "Ticket Bot Logs" }); - webhooks = await channel.fetchWebhooks(); - } - const webhook = webhooks.find((wh) => wh.token); + const webhook = (await channel.fetchWebhooks()).find((wh) => wh.token) ?? + await channel.channel.createWebhook({ name: "Ticket Bot Logs" }); if (logsType === "ticketCreate") { const embed = new Discord.EmbedBuilder() diff --git a/utils/pgsqlDriver.js b/utils/pgsqlDriver.js new file mode 100644 index 00000000..8a776e8f --- /dev/null +++ b/utils/pgsqlDriver.js @@ -0,0 +1,89 @@ +/** + * Official pre-released quick.db Postgresql driver. Remove this file when the driver is officially released ~ zhiyan114 + * Why add this? PostgreSQL is a better database than MySQL. Alternative would be to use Prisma, + * but I don't want to esentially re-write the database logic and cause confusions. + * Source: https://github.com/plexidev/quick.db/blob/dev/src/drivers/PostgresDriver.ts + * LICENSE: https://github.com/plexidev/quick.db/blob/dev/LICENSE.md + */ + +// eslint-disable-next-line node/no-missing-require +const { Client } = require("pg"); + +module.exports = class PostgresDriver { + instance; + config; + conn; + + constructor(config) { + this.config = config; + } + + static createSingleton(config) { + if (!this.instance) this.instance = new PostgresDriver(config); + return this.instance; + } + + async connect() { + this.conn = new Client(this.config); + await this.conn.connect(); + } + + async disconnect() { + this.checkConnection(); + await this.conn.end(); + } + + checkConnection() { + if (!this.conn) { + throw new Error("No connection to postgres database"); + } + } + + async prepare(table) { + this.checkConnection(); + await this.conn.query(`CREATE TABLE IF NOT EXISTS ${table} (id VARCHAR(255), value TEXT)`); + } + + async getAllRows(table) { + this.checkConnection(); + const queryResult = await this.conn.query(`SELECT * FROM ${table}`); + return queryResult.rows.map((row) => ({ + id: row.id, + value: JSON.parse(row.value) + })); + } + + async getRowByKey(table, key) { + this.checkConnection(); + const queryResult = await this.conn.query(`SELECT value FROM ${table} WHERE id = $1`, [key]); + + if (queryResult.rowCount < 1) return [null, false]; + return [JSON.parse(queryResult.rows[0].value), true]; + } + + async setRowByKey(table, key, value, update) { + this.checkConnection(); + + const stringifiedValue = JSON.stringify(value); + + if (update) { + await this.conn.query(`UPDATE ${table} SET value = $1 WHERE id = $2`, [stringifiedValue, key]); + } else { + await this.conn.query(`INSERT INTO ${table} (id, value) VALUES ($1, $2)`, [key, stringifiedValue]); + } + + return value; + } + + async deleteAllRows(table) { + this.checkConnection(); + const queryResult = await this.conn.query(`DELETE FROM ${table}`); + return queryResult.rowCount; + } + + async deleteRowByKey(table, key) { + this.checkConnection(); + const queryResult = await this.conn.query(`DELETE FROM ${table} WHERE id = $1`, [key]); + return queryResult.rowCount; + } +};