diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 28f80b80..a99bd3f4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,5 +1,7 @@ name: Deploy -permissions: {} + +permissions: + contents: read on: push: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 535943e9..30d89316 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -1,5 +1,8 @@ name: Unit tests +permissions: + contents: read + on: pull_request: branches: [main, dev] diff --git a/.github/workflows/wiki-update.yml b/.github/workflows/wiki-update.yml index d341c079..e24a7092 100644 --- a/.github/workflows/wiki-update.yml +++ b/.github/workflows/wiki-update.yml @@ -1,5 +1,8 @@ name: Update Wiki Commands +permissions: + contents: write #gives both read & write access + on: push: branches: [main] @@ -31,10 +34,10 @@ jobs: - name: Update Wiki env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Clone wiki repo - git clone https://${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.wiki.git wiki + git clone https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git wiki # Copy new content cp src/data/commands.md ./wiki/Commands.md diff --git a/.gitignore b/.gitignore index 8aaabd8e..0605d8e0 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Application specific files & folders .env config.json /node_modules @@ -5,3 +6,7 @@ config.json /src/data/cigpics.json /src/data/pics.json /src/data/commands.md + +# IDE / code editor specific folders +/.vscode +/.zed diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100755 index b579f615..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch bot.", - "type": "node", - "request": "launch", - "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/index.js" - }, - { - "name": "Deploy slash commands.", - "type": "node", - "request": "launch", - "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/deploy-commands.js" - }, - { - "name": "Run unit tests.", - "type": "node", - "request": "launch", - "runtimeArgs": [ - "--inspect-brk", - "${workspaceRoot}/node_modules/jest/bin/jest.js", - "--runInBand" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "name": "Create SQL tables.", - "type": "node", - "request": "launch", - "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/create-tables.js" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d3cb2ac4..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "postman.settings.dotenv-detection-notification-visibility": false -} diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index 79860b3a..00000000 --- a/.zed/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -// Folder-specific settings -// -// For a full list of overridable settings, and general information on folder-specific settings, -// see the documentation: https://zed.dev/docs/configuring-zed#settings-files -{} diff --git a/README.md b/README.md index d235f94b..c3e4291c 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,13 @@
-[![Unit tests](https://github.com/vb2007/discordbot/actions/workflows/unit-tests.yml/badge.svg?branch=dev)](https://github.com/vb2007/discordbot/actions/workflows/unit-tests.yml) [![Update Wiki Commands](https://github.com/vb2007/discordbot/actions/workflows/wiki-update.yml/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/wiki-update.yml) [![CodeQL](https://github.com/vb2007/discordbot/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/github-code-scanning/codeql) +[![Deploy](https://github.com/vb2007/discordbot/actions/workflows/deploy.yml/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/deploy.yml) [![Release on GitHub](https://github.com/vb2007/discordbot/actions/workflows/gh-release.yml/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/gh-release.yml) [![Update Wiki Commands](https://github.com/vb2007/discordbot/actions/workflows/wiki-update.yml/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/wiki-update.yml) + +
+ +
+ +[![Unit tests](https://github.com/vb2007/discordbot/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/unit-tests.yml) [![CodeQL](https://github.com/vb2007/discordbot/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/vb2007/discordbot/actions/workflows/github-code-scanning/codeql)
diff --git a/package-lock.json b/package-lock.json index 3cf82b2c..816ec269 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "discordbot", - "version": "4.2.0", + "version": "4.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "discordbot", - "version": "4.2.0", + "version": "4.2.1", "license": "AGPL-3.0-only", "dependencies": { "ajv": "^8.17.1", @@ -56,7 +56,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1441,9 +1440,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", - "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1500,7 +1499,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -1553,9 +1551,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001756", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", - "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -1886,9 +1884,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.38.34", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.34.tgz", - "integrity": "sha512-muq7xKGznj5MSFCzuIm/2TO7DpttuomUTemVM82fRqgnMl70YRzEyY24jlbiV6R9tzOTq6A6UnZ0bsfZeKD38Q==", + "version": "0.38.35", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.35.tgz", + "integrity": "sha512-pI6FKJmkyIhToiDK5f8iok7acugSJDFnr3D2a0m+r91EMSFWCzAAEgUro9Km0AUYQPAUluS6iJaXVKt6+wBF7w==", "license": "MIT", "workspaces": [ "scripts/actions/documentation" @@ -1998,9 +1996,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", "dev": true, "license": "ISC" }, @@ -3750,9 +3748,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz", + "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==", "dev": true, "license": "MIT", "bin": { diff --git a/src/commands/economy/store.js b/src/commands/economy/store.js new file mode 100644 index 00000000..c7495274 --- /dev/null +++ b/src/commands/economy/store.js @@ -0,0 +1,41 @@ +import { SlashCommandBuilder } from "discord.js"; +import { embedReplyPrimaryColorWithFields } from "../../helpers/embeds/embed-reply.js"; +import { checkIfNotInGuild } from "../../helpers/command-validation/general.js"; +import { replyAndLog } from "../../helpers/reply.js"; +import { query } from "../../helpers/db.js"; + +const commandName = "store"; + +export default { + data: new SlashCommandBuilder() + .setName(commandName) + .setDescription( + "Returns a store page with various purchaseable items that can help with your economy status." + ) + .setNSFW(false) + .setDMPermission(false), + async execute(interaction) { + const guildCheck = checkIfNotInGuild(commandName, interaction); + if (guildCheck) { + return await replyAndLog(interaction, guildCheck); + } + + const storeQuery = await query( + `SELECT name, price, description FROM economyStore ORDER BY price ASC` + ); + const fields = storeQuery.map((item) => ({ + name: `${item.name} - \`$${item.price}\``, + value: item.description, + inline: false, + })); + + const embedReply = embedReplyPrimaryColorWithFields( + "Store", + "Use the `/buy`(**item name**) command to purchase any of these items.", + fields, + interaction + ); + + return await replyAndLog(interaction, embedReply); + }, +}; diff --git a/src/create-tables.js b/src/create-tables.js index cf1412fb..6d7c155a 100644 --- a/src/create-tables.js +++ b/src/create-tables.js @@ -24,45 +24,69 @@ const readSQLFiles = (dir) => { return sqlQueries; }; -const updateCommandData = async () => { +//parses CSV line into array of values +const parseCSVLine = (line) => { + const regex = /(?:^|,)(?:"([^"]*(?:""[^"]*)*)"|([^,"]*))/g; + const values = []; + let match; + + while ((match = regex.exec(line))) { + const value = match[1] || match[2]; + values.push(value ? value.replace(/""/g, '"').trim() : ""); + } + + return values; +}; + +//universal function to parse and update CSV data into database tables +const parseAndUpdateCSV = async (csvFileName, tableName, columnMapping) => { try { - const csvPath = path.join(__dirname, "data", "commandData.csv"); + const csvPath = path.join(__dirname, "data", csvFileName); const csvContent = fs.readFileSync(csvPath, "utf8"); - const commands = csvContent - .split("\n") + //parse CSV and extract headers + const lines = csvContent.split("\n").filter((line) => line.trim()); + const headers = parseCSVLine(lines[0]); + + const rows = lines .slice(1) - .filter((line) => line.trim()) .map((line) => { - //csv parsing - const regex = /(?:^|,)(?:"([^"]*(?:""[^"]*)*)"|([^,"]*))/g; - const values = []; - let match; + const values = parseCSVLine(line); - while ((match = regex.exec(line))) { - const value = match[1] || match[2]; - values.push(value ? value.replace(/""/g, '"').trim() : ""); - } + if (values.length < headers.length) return null; - if (values.length < 4) return null; + //create object with header-value pairs + const row = {}; + headers.forEach((header, index) => { + row[header] = values[index]; + }); - const [id, name, category, description] = values; - return { name, category, description }; + return row; }) - .filter((cmd) => cmd !== null); + .filter((row) => row !== null); + + //prepare SQL query + const columns = columnMapping.columns; + const placeholders = columns.map(() => "?").join(", "); + const updateClause = columns + .filter((col) => !columnMapping.skipUpdate?.includes(col)) + .map((col) => `${col} = VALUES(${col})`) + .join(", "); - for (const cmd of commands) { - console.log(`Processing command: ${cmd.name}`); + const insertQuery = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updateClause}`; - await query( - "INSERT INTO commandData (name, category, description) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE category = VALUES(category), description = VALUES(description)", - [cmd.name, cmd.category, cmd.description] - ); + for (const row of rows) { + const identifier = row[columnMapping.identifier] || row.name || row.id; + console.log(`Processing ${tableName}: ${identifier}`); + + const values = columns.map((col) => row[col]); + + await query(insertQuery, values); } - console.log("Command data has been updated successfully."); + console.log(`${tableName} data has been updated successfully.`); } catch (error) { - console.error("Error updating command data:", error); + console.error(`Error updating ${tableName} data:`, error); console.error("Error details:", error.stack); } }; @@ -106,7 +130,17 @@ const createTables = async () => { const init = async () => { await createTables(); - await updateCommandData(); + + await parseAndUpdateCSV("commandData.csv", "commandData", { + columns: ["name", "category", "description"], + identifier: "name", + }); + + await parseAndUpdateCSV("economyStore.csv", "economyStore", { + columns: ["price", "name", "description"], + identifier: "name", + }); + process.exit(0); }; diff --git a/src/data/commandData.csv b/src/data/commandData.csv index 116ddc6c..cd512289 100644 --- a/src/data/commandData.csv +++ b/src/data/commandData.csv @@ -39,3 +39,4 @@ 38,invite,utility,Gives you back a link for inviting the bot to any server where you have permissions to do so. 39,word-leaderboard,fun,Counts a specified word in the current server and sends back a leaderboard with the users who used that word the most through all their previous messages. 40,darwin-random,fun,"Sends back a random, streamable video from Darwin's database." +41,store,economy,Displays a store filled with various items / power-ups you can buy to help you progress in this terrible economy. diff --git a/src/data/economyStore.csv b/src/data/economyStore.csv new file mode 100644 index 00000000..4ea357e4 --- /dev/null +++ b/src/data/economyStore.csv @@ -0,0 +1,9 @@ +"id","price","name","description" +1,5,Bread,"flour, yeast, salt, water. fresh." +2,150,gun,"Makes you able to rob from a user's bank account, if they haven't been bankrobbed already after their last deposit." +3,80,fentanyl,"Gives you a 100% success rate on your next rob command, unless your target has a police badge." +4,5000,clipped-coins,"Gets you expelled from 109 countries, but makes you gain a 3x multiplier on any gabling command you use first after activation." +5,15000,dildo,very long +6,2500,fried-chicken,"Best paired with watermelon & Kool-aid. Your black mind runs faster from it, thus giving you a 15% win rate on your next roulette game, if you bet on green." +7,100,police-badge,"Takes your opponent's breath instantly when it's under the influence of fent, thus making the robbery attempt unsuccessful." +8,150,kippah,"Makes your nose huge, but gives you a 3x multiplier on the next work commands." diff --git a/src/index.js b/src/index.js index b44fb321..1d568d4f 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,10 @@ import config from "../config.json" with { type: "json" }; import "dotenv/config"; const token = process.env.TOKEN; +//for displaying version is bot's status +import packageJson from "../package.json" with { type: "json" }; +const version = packageJson.version; + import { getConnection } from "./helpers/db.js"; import { runDarwinProcess } from "./helpers/darwin/darwinProcess.js"; import { validateConfig } from "./scripts/verify-config-syntax.js"; @@ -218,8 +222,8 @@ const setActivity = () => { client.user.setActivity({ status: "online", - type: ActivityType.Playing, - name: "with stolen user data.", + type: ActivityType.Custom, + name: `Running v${version}`, }); // console.log("Re-announced bot's activity."); diff --git a/src/sql/commandData/table.sql b/src/sql/commandData/table.sql index 3fd413f2..00afc760 100644 --- a/src/sql/commandData/table.sql +++ b/src/sql/commandData/table.sql @@ -7,4 +7,4 @@ CREATE TABLE IF NOT EXISTS `commandData` ( `description` text DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_name` (`name`) -); \ No newline at end of file +); diff --git a/src/sql/darwinCache/table.sql b/src/sql/darwinCache/table.sql index 86ec5e5e..167765fb 100644 --- a/src/sql/darwinCache/table.sql +++ b/src/sql/darwinCache/table.sql @@ -1,13 +1,13 @@ -- discordbot.darwinCache definition -CREATE TABLE `darwinCache` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `directVideoUrl` varchar(255) NOT NULL, - `forumPostUrl` varchar(255) NOT NULL DEFAULT '', - `videoId` varchar(100) NOT NULL, - `videoTitle` varchar(180) NOT NULL DEFAULT '', - `processedAt` timestamp NULL DEFAULT current_timestamp(), - PRIMARY KEY (`id`), - UNIQUE KEY `videoId` (`videoId`), - UNIQUE KEY `directVideoUrl` (`directVideoUrl`) +CREATE TABLE IF NOT EXISTS `darwinCache` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `directVideoUrl` varchar(255) NOT NULL, + `forumPostUrl` varchar(255) DEFAULT NULL, + `videoId` varchar(100) NOT NULL, + `videoTitle` varchar(180) DEFAULT NULL, + `processedAt` timestamp NOT NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `videoId` (`videoId`), + UNIQUE KEY `directVideoUrl` (`directVideoUrl`) ); diff --git a/src/sql/economyStore/table.sql b/src/sql/economyStore/table.sql new file mode 100644 index 00000000..7c690103 --- /dev/null +++ b/src/sql/economyStore/table.sql @@ -0,0 +1,10 @@ +-- discordbot.economyStore definition + +CREATE TABLE IF NOT EXISTS `economyStore` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `price` int(11) NOT NULL, + `name` varchar(100) NOT NULL, + `description` varchar(100) DEFAULT 'No description provided.', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;