From e010811d3ae067ab2152255a1a08941d0a92752e Mon Sep 17 00:00:00 2001 From: marek Date: Wed, 23 Sep 2026 18:31:15 +0200 Subject: [PATCH] init --- bun.lock | 70 ++++ buttons/reakcnerole.js | 152 +++++++++ commands/rr.js | 733 +++++++++++++++++++++++++++++++++++++++++ data/db.sqlite | Bin 0 -> 4096 bytes data/db.sqlite-shm | Bin 0 -> 32768 bytes data/db.sqlite-wal | Bin 0 -> 201912 bytes index.js | 100 ++++++ modals/reakcnerole.js | 54 +++ package.json | 5 + utils/kvdb.js | 330 +++++++++++++++++++ 10 files changed, 1444 insertions(+) create mode 100644 bun.lock create mode 100644 buttons/reakcnerole.js create mode 100644 commands/rr.js create mode 100644 data/db.sqlite create mode 100644 data/db.sqlite-shm create mode 100644 data/db.sqlite-wal create mode 100644 index.js create mode 100644 modals/reakcnerole.js create mode 100644 package.json create mode 100644 utils/kvdb.js diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..d2d9d8a --- /dev/null +++ b/bun.lock @@ -0,0 +1,70 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "oceanic.js": "^1.15.0", + }, + }, + }, + "packages": { + "@discordjs/voice": ["@discordjs/voice@0.19.2", "", { "dependencies": { "@snazzah/davey": "^0.1.9", "@types/ws": "^8.18.1", "discord-api-types": "^0.38.41", "prism-media": "^1.3.5", "tslib": "^2.8.1", "ws": "^8.19.0" } }, "sha512-3yJ255e4ag3wfZu/DSxeOZK1UtnqNxnspmLaQetGT0pDkThNZoHs+Zg6dgZZ19JEVomXygvfHn9lNpICZuYtEA=="], + + "@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" } }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="], + + "@snazzah/davey": ["@snazzah/davey@0.1.12", "", { "optionalDependencies": { "@snazzah/davey-android-arm-eabi": "0.1.12", "@snazzah/davey-android-arm64": "0.1.12", "@snazzah/davey-darwin-arm64": "0.1.12", "@snazzah/davey-darwin-x64": "0.1.12", "@snazzah/davey-freebsd-x64": "0.1.12", "@snazzah/davey-linux-arm-gnueabihf": "0.1.12", "@snazzah/davey-linux-arm64-gnu": "0.1.12", "@snazzah/davey-linux-arm64-musl": "0.1.12", "@snazzah/davey-linux-x64-gnu": "0.1.12", "@snazzah/davey-linux-x64-musl": "0.1.12", "@snazzah/davey-wasm32-wasi": "0.1.12", "@snazzah/davey-win32-arm64-msvc": "0.1.12", "@snazzah/davey-win32-ia32-msvc": "0.1.12", "@snazzah/davey-win32-x64-msvc": "0.1.12" } }, "sha512-V+NlX5931RwVamZhhEfZekMdcvXDKdMAmHW1AuGaykVQsNyBOq3bpmGpoKRBDCYgFWKIufJ0Dcg3m4cYhvUy6g=="], + + "@snazzah/davey-android-arm-eabi": ["@snazzah/davey-android-arm-eabi@0.1.12", "", { "os": "android", "cpu": "arm" }, "sha512-6VC/an+Sx5dI5skb+90rYcIB1jhm48Rl0nDaw0UNT4bz1rMjpVfmmZqeocYXMq96IdbBMlE6OTKGcBm2C3gkQg=="], + + "@snazzah/davey-android-arm64": ["@snazzah/davey-android-arm64@0.1.12", "", { "os": "android", "cpu": "arm64" }, "sha512-0Bwd03/JsTFhlPhF4q/LW0RxSzntFpQdhz+TBdFljYSg8IEyA38saPJeTNjpIgDfhAumPzvhCdfS6O5qT8yXDw=="], + + "@snazzah/davey-darwin-arm64": ["@snazzah/davey-darwin-arm64@0.1.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lKMV6ITi9BQLt0fx/pAT7M8xcojVK7bryVJGdaW3bq8gABFslS3ti/KzrWabQvhpEV71FZe5mV0UcKHFVaTsZw=="], + + "@snazzah/davey-darwin-x64": ["@snazzah/davey-darwin-x64@0.1.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-vXXc/eW/e3TQeb7VsdtrPqs3/22j0aSqiP1ZXmZtDjQRBwgSxwItWYa6sh5MELP2EHB2igNlGzB6Hc0XlbGi4g=="], + + "@snazzah/davey-freebsd-x64": ["@snazzah/davey-freebsd-x64@0.1.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-G1gas5HrC4Xp3mRY0+OeAqXS6fG2tRgBEc8gQh69Hw4YK9RV9mzQKcmoKMkBM72U1+2C+2u57dolnKKzwozByQ=="], + + "@snazzah/davey-linux-arm-gnueabihf": ["@snazzah/davey-linux-arm-gnueabihf@0.1.12", "", { "os": "linux", "cpu": "arm" }, "sha512-97Fujh82r2Ll7dPZeNuoZ3yKfsqycf3c93OWXOo/ThNL/18Onl2Ht4SIvpX6VHhfeS9bDpHJ1lHCaz1b/i5ocw=="], + + "@snazzah/davey-linux-arm64-gnu": ["@snazzah/davey-linux-arm64-gnu@0.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-FWyAOv52cHKDM4BOsmImKKogHFvqNFoXmZcicNJbX3XpVl8Mas88ZoXQ+IA5V+qc9pNtAl1MbWwEZ9JrqAQtbg=="], + + "@snazzah/davey-linux-arm64-musl": ["@snazzah/davey-linux-arm64-musl@0.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-ptRbLSQxtV6EjXppS5z7qaPDI0NRKhrkJYsTlAjEghmOvlAObozSCYYnMO6nbkt6Ab3+lWqyahClzcRNcD2ouw=="], + + "@snazzah/davey-linux-x64-gnu": ["@snazzah/davey-linux-x64-gnu@0.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-w86fZvhJn0ErOoAQHt2UbQ95V/cgwvfvQ4GlTPQLCzt58nn+rLlXLgPn90qYlSQrZxFW38rKXwqVOMbm9p+pwQ=="], + + "@snazzah/davey-linux-x64-musl": ["@snazzah/davey-linux-x64-musl@0.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-LLNnO+hfG41ymeI+O1YHo5/0h3aKaetUNLdkBwpdJsjoyKMXZaeCnB+aHNkkupJCMPmWS0g6iPMCHUOqZSBcTg=="], + + "@snazzah/davey-wasm32-wasi": ["@snazzah/davey-wasm32-wasi@0.1.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.5" }, "cpu": "none" }, "sha512-MPKFuqYVkDFXheR7qmtEY4FWxQ/ADfgsCojQWHi13sibUqCTR9q2F1LqNn2i9IVh3sh1sxeg87fdFMCH63pl7g=="], + + "@snazzah/davey-win32-arm64-msvc": ["@snazzah/davey-win32-arm64-msvc@0.1.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-Uf4OYHyfbXpzyaOqIV8/h6kv166Qni5+Bxmc1E/ov4uhhKO8qXbOky8zbVOzu0U4cv5ll3s4IQ8jDAJkx/K75Q=="], + + "@snazzah/davey-win32-ia32-msvc": ["@snazzah/davey-win32-ia32-msvc@0.1.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-nRVbKTsb2ldcPI8D4BDA7P/UeiMEMvR+wYuUMp7H1pRD/3dF2hKo+MzUU+Pn78EhuYA3CWHItiAcS/GsPld67A=="], + + "@snazzah/davey-win32-x64-msvc": ["@snazzah/davey-win32-x64-msvc@0.1.12", "", { "os": "win32", "cpu": "x64" }, "sha512-AgUA3itPDVkxQq7RIkgE1thCiWePwWjyfOZefBBxGIlMWpRUOSwF4vY9kNLnrScyJcFULdR+Zm8PwiwV8/RKnw=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/node": ["@types/node@22.20.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "discord-api-types": ["discord-api-types@0.38.55", "", {}, "sha512-ytuaRTzdnHUCXJ6KjL9MrItQX0xKncBKEeYI1Bst4+ud47eejH3cG6gaesYakjpPcUhh68XYS2YwGq3mmujFsA=="], + + "oceanic.js": ["oceanic.js@1.15.0", "", { "dependencies": { "tslib": "^2.8.1", "ws": "^8.21.3" }, "optionalDependencies": { "@discordjs/voice": "^0.19.2" }, "peerDependencies": { "erlpack": "^0.1.4", "pako": "^2.1.0", "zlib-sync": "^0.1.10", "zstd-napi": "^0.0.12" }, "optionalPeers": ["erlpack", "pako", "zlib-sync", "zstd-napi"] }, "sha512-RV1ezHnMzxDxGCkzKpbhHxYOWo6ZHOjTFYOXhxCyIU1mI3Xj01NT+djwJkOiUCekd8k9ciFnpKRs7ckXuLbzwg=="], + + "prism-media": ["prism-media@1.3.5", "", { "peerDependencies": { "@discordjs/opus": ">=0.8.0 <1.0.0", "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0", "node-opus": "^0.3.3", "opusscript": "^0.0.8" }, "optionalPeers": ["@discordjs/opus", "ffmpeg-static", "node-opus", "opusscript"] }, "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], + } +} diff --git a/buttons/reakcnerole.js b/buttons/reakcnerole.js new file mode 100644 index 0000000..12be720 --- /dev/null +++ b/buttons/reakcnerole.js @@ -0,0 +1,152 @@ +import { ApplicationCommandOptionTypes, ApplicationCommandTypes, ButtonStyles, ComponentTypes, MessageFlags, Permissions, SeparatorSpacingSize } from "oceanic.js"; + +export default { + data: { + name: "reakcnerole", + }, + + handle: async (interaction, database) => { + await interaction.defer(MessageFlags.EPHEMERAL) + + const db = database.table(`rr`) + + const splitID = interaction.data.customID.split('.') + + const kolekcia = splitID[1] + const rolaID = splitID[2] + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const rolaExistuje = await db.has(`${interaction.guildID}.${kolekcia}.roles`, (r) => r.id === rolaID) + + if (!rolaExistuje) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ rola v kolekcii ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const rolaDB = await db.get(`${interaction.guildID}.${kolekcia}.roles`, (r) => r.id === rolaID) + + const rola = interaction.guild.roles.find(r => r.id === rolaID) + if (!rola) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ rola neexistuje` + } + ] + } + ] + }) + + const botPermissions = interaction.guild.clientMember.permissions + + if (!botPermissions.has(Permissions.MANAGE_ROLES)) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ nemam prava na zmenu roli` + } + ] + } + ] + }) + + const memberMaRolu = interaction.member.roles.includes(rolaID) + + if (memberMaRolu) { + await interaction.member.removeRole(rolaID, `reakcne role`) + + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ rola ${rola.name} ti bola odobrana` + } + ] + } + ] + }) + } + + const jednaRola = await db.get(`${interaction.guildID}.${kolekcia}.options.jednaRola`) + + const roly = interaction.member.roles + const rolyDB = new Set((await db.get(`${interaction.guildID}.${kolekcia}.roles`)).map(i => i.id)) + + const memberMaRoluZKolekcie = roly.some(i => rolyDB.has(i)) + + if (jednaRola && memberMaRoluZKolekcie) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ uz mas nejaku rolu z kolekcie` + } + ] + } + ] + }) + + await interaction.member.addRole(rolaID, `reakcne role`) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ rola ${rola.name} ti bola pridana` + } + ] + } + ] + }) + } +} \ No newline at end of file diff --git a/commands/rr.js b/commands/rr.js new file mode 100644 index 0000000..07dc01d --- /dev/null +++ b/commands/rr.js @@ -0,0 +1,733 @@ +import { ApplicationCommandOptionTypes, ApplicationCommandTypes, ButtonStyles, ComponentTypes, MessageFlags, Permissions, SeparatorSpacingSize, TextInputStyles } from "oceanic.js"; + +export default { + data: { + type: ApplicationCommandTypes.CHAT_INPUT, // CHAT_INPUT = slash commands - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandTypes.html + name: "rr", + description: "nastav reakcne role", + options: [ + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "zoznam", + description: "zobrazit zoznam kolekcii" + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "zoznam_roli", + description: "zobrazit zoznam roli v kolekcii", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia, z ktorej sa zobrazi zoznam roli", + required: true + }, + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "vytvorit", + description: "vytvorit novu kolekciu", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "nazov kolekcie na vytvorenie", + required: true + }, + { + type: ApplicationCommandOptionTypes.BOOLEAN, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "jedna_rola", + description: "moznost vybrat iba jednej role z kolekcie" + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "pridat_rolu", + description: "pridat rolu do kolekcie", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia, do ktorej bude rola pridana", + required: true + }, + { + type: ApplicationCommandOptionTypes.ROLE, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "rola", + description: "rola na pridanie do kolekcie", + required: true + }, + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "nazov", + description: "nazov role v kolekcii" + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "odobrat_rolu", + description: "odobrat rolu z kolekcie", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia, z ktorej bude rola odobrana", + required: true + }, + { + type: ApplicationCommandOptionTypes.ROLE, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "rola", + description: "rola na odobranie z kolekcie", + required: true + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "zmazat", + description: "zmazat kolekciu", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia na zmazanie", + required: true + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "poslat", + description: "poslat kolekciu do kanala", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia na odoslanie", + required: true + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "upravit", + description: "upravit kolekciu", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia na upravenie", + required: true + }, + { + type: ApplicationCommandOptionTypes.BOOLEAN, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "jedna_rola", + description: "moznost vybrat iba jednej role z kolekcie" + }, + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "novy_nazov", + description: "novy nazov kolekcie" + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "upravit_rolu", + description: "upravit rolu v kolekcii", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia, z ktorej je rola na upravenie", + required: true + }, + { + type: ApplicationCommandOptionTypes.ROLE, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "rola", + description: "rola na upravenie", + required: true + }, + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "nazov", + description: "nazov role v kolekcii" + } + ] + }, + { + type: ApplicationCommandOptionTypes.SUB_COMMAND, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "popis", + description: "upravit popis kolekcie", + options: [ + { + type: ApplicationCommandOptionTypes.STRING, // A string input - full list: https://docs.oceanic.ws/latest/enums/Constants.ApplicationCommandOptionTypes.html + name: "kolekcia", + description: "kolekcia, ktorej popis bude upraveny", + required: true + } + ] + } + ], + dmPermission: false, // false = usable in guilds only, true = both guild & direct message + defaultMemberPermissions: "8" // The bitfield of the default permissions required to use this command (8 = Administrator) + }, + + handle: async (interaction, database) => { + const db = database.table(`rr`) + + const action = interaction.data.options.getSubCommand()[0] + + if (action !== `popis`) await interaction.defer(MessageFlags.EPHEMERAL); + + switch (action) { + case "vytvorit": { + const kolekcia = interaction.data.options.getString("kolekcia") + const jednaRola = interaction.data.options.getString("jedna_rola") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} uz existuje` + } + ] + } + ] + }) + + await db.set(`${interaction.guildID}.${kolekcia}`, { options: { jednaRola }, popis: null, roles: [] }) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ kolekcia ${kolekcia} vytvorena` + } + ] + } + ] + }) + + break + } case "pridat_rolu": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const rola = interaction.data.options.getRole("rola") + const rolaExistuje = await db.has(`${interaction.guildID}.${kolekcia}.roles`, (r) => r.id === rola.id) + + const nazov = interaction.data.options.getString("nazov") + + if (rolaExistuje) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ rola ${rola.name} uz v kolekcii ${kolekcia} existuje` + } + ] + } + ] + }) + + await db.push(`${interaction.guildID}.${kolekcia}.roles`, { id: rola.id, nazov }) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ rola ${rola.name} pridana do kolekcie ${kolekcia}` + } + ] + } + ] + }) + break + } case "odobrat_rolu": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const rola = interaction.data.options.getRole("rola") + const rolaExistuje = await db.has(`${interaction.guildID}.${kolekcia}.roles`, (r) => r.id === rola.id) + + if (!rolaExistuje) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ rola ${rola.name} v kolekcii ${kolekcia} neexistuje` + } + ] + } + ] + }) + + await db.delete(`${interaction.guildID}.${kolekcia}.roles`, (r) => r.id === rola.id) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ rola ${rola.name} odobrana z kolekcie ${kolekcia}` + } + ] + } + ] + }) + + break + } case "zmazat": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + await db.delete(`${interaction.guildID}.${kolekcia}`) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ kolekcia ${kolekcia} zmazana` + } + ] + } + ] + }) + + break + } case "poslat": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const botPermissions = interaction.channel.permissionsOf(interaction.guild.clientMember) + + if (!botPermissions.has(Permissions.SEND_MESSAGES)) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ nemam prava na poslanie spravy` + } + ] + } + ] + }) + + const popis = await db.get(`${interaction.guildID}.${kolekcia}.popis`) + + const roleDB = await db.get(`${interaction.guildID}.${kolekcia}.roles`) + + const komponenty = [] + + komponenty.push({ + type: ComponentTypes.TEXT_DISPLAY, + content: `# ${kolekcia}` + }) + + if (popis) + komponenty.push({ + type: ComponentTypes.TEXT_DISPLAY, + content: popis + }) + + komponenty.push({ + type: ComponentTypes.SEPARATOR, + spacing: SeparatorSpacingSize.LARGE, + divider: true + }) + + const komponentyRoli = [] + let i = 0 + + for (const rolaDB of roleDB) { + const rola = interaction.guild.roles.find(r => r.id === rolaDB.id) + if (!rola) continue + + if (i % 5 === 0) + komponentyRoli.push({ + type: ComponentTypes.ACTION_ROW, + components: [] + }) + + komponentyRoli[Math.floor(i / 5)].components.push( + { + type: ComponentTypes.BUTTON, + style: ButtonStyles.PRIMARY, + label: rolaDB.nazov ?? rola.name, + customID: `reakcnerole.${kolekcia}.${rolaDB.id}` + } + ) + + i++ + } + + if (!komponentyRoli.length) + komponentyRoli.push({ + type: ComponentTypes.TEXT_DISPLAY, + content: `zatial ziadne role` + }) + + await interaction.channel.createMessage({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [...komponenty, ...komponentyRoli] + } + ] + }) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ sprava poslana` + } + ] + } + ] + }) + + break + } case "upravit": { + const kolekcia = interaction.data.options.getString("kolekcia") + const jednaRola = interaction.data.options.getBoolean("jedna_rola") + const novyNazov = interaction.data.options.getString("novy_nazov") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + if (jednaRola !== undefined) + await db.set(`${interaction.guildID}.${kolekcia}.options`, { jednaRola }) + + if (novyNazov) + await db.rename(`${interaction.guildID}.${kolekcia}`, `${interaction.guildID}.${novyNazov}`) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ kolekcia ${novyNazov ?? kolekcia} upravena` + } + ] + } + ] + }) + + break + } case "upravit_rolu": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const rola = interaction.data.options.getRole("rola") + const rolaExistuje = await db.has(`${interaction.guildID}.${kolekcia}.roles`, (r) => r.id === rola.id) + + const nazov = interaction.data.options.getString("nazov") + + if (!rolaExistuje) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ rola ${rola.name} v kolekcii ${kolekcia} neexistuje` + } + ] + } + ] + }) + + if (nazov) await db.update(`${interaction.guildID}.${kolekcia}.roles`, (r => r.id === rola.id), { nazov }) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ rola ${rola.name} upravena v kolekcii ${kolekcia}` + } + ] + } + ] + }) + + break + } case "popis": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + await interaction.createModal({ + customID: `reakcnerole.${kolekcia}.popis`, + title: `${kolekcia} - upravit popis`, + components: [ + { + type: ComponentTypes.LABEL, + label: `popis`, + component: { + type: ComponentTypes.TEXT_INPUT, + style: TextInputStyles.PARAGRAPH, + customID: `popis`, + required: false + } + } + ] + }) + + break + } case "zoznam": { + const kolekcie = await db.get(`${interaction.guildID}`) + + let textZoznam = `# zoznam kolekcií` + + for (const kolekcia of Object.keys(kolekcie)) { + textZoznam += `\n- ${kolekcia}` + } + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: textZoznam + } + ] + } + ] + }) + + break + } case "zoznam_roli": { + const kolekcia = interaction.data.options.getString("kolekcia") + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const role = await db.get(`${interaction.guildID}.${kolekcia}.roles`) + + let textZoznam = `# zoznam rolí (${kolekcia})` + + for (const rola of role) { + textZoznam += `\n- ${rola.nazov} <@&${rola.id}>` + } + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: textZoznam + } + ] + } + ] + }) + break + } default: { + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ neplatna akcia` + } + ] + } + ] + }) + } + } + } +} \ No newline at end of file diff --git a/data/db.sqlite b/data/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..09e3110e7c163d9073f90dd93af4fd4108f2292c GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYC*>f)}J+fNV2HHI9bB nXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDP#6LL6MF}4 literal 0 HcmV?d00001 diff --git a/data/db.sqlite-shm b/data/db.sqlite-shm new file mode 100644 index 0000000000000000000000000000000000000000..8ffbaf5542a5779e1ab33ac71d390c99393f9e86 GIT binary patch literal 32768 zcmeI)w@pMr6a~$M3b;cXE~Xga82o1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZpe=!} z<~)hEesP$<9}%eLtq6htDNs!s5(58IpqBm&W+=lM$!Nwho{3CmD$|+CY?%f&pL!Ou zSmtvrXCD8(D?#=8R+~bbx42!0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oPur`~W|; BEMouw literal 0 HcmV?d00001 diff --git a/data/db.sqlite-wal b/data/db.sqlite-wal new file mode 100644 index 0000000000000000000000000000000000000000..c0b5a68a19b22b482c39613445b4b061c1686674 GIT binary patch literal 201912 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 literal 0 HcmV?d00001 diff --git a/index.js b/index.js new file mode 100644 index 0000000..808efee --- /dev/null +++ b/index.js @@ -0,0 +1,100 @@ +import { ApplicationCommandTypes, Client, ComponentTypes, InteractionTypes } from "oceanic.js"; +import { readdir } from "node:fs/promises"; +import { KVDB } from "./utils/kvdb"; + +const db = new KVDB("data/db.sqlite"); + +const client = new Client({ + token: process.env.TOKEN +}); + +const commandFiles = await readdir("./commands", { recursive: true }); +const commandsData = [] +const commands = new Map() + +for (const commandFile of commandFiles) { + const command = (await import(`./commands/${commandFile}`)).default + commandsData.push(command.data) + commands.set(command.data.name, command) +} + +const buttonFiles = await readdir("./buttons", { recursive: true }); +const buttonsData = [] +const buttons = new Map() + +for (const buttonFile of buttonFiles) { + const button = (await import(`./buttons/${buttonFile}`)).default + buttonsData.push(button.data) + buttons.set(button.data.name, button) +} + +const modalFiles = await readdir("./modals", { recursive: true }); +const modalsData = [] +const modals = new Map() + +for (const modalFile of modalFiles) { + const modal = (await import(`./modals/${modalFile}`)).default + modalsData.push(modal.data) + modals.set(modal.data.name, modal) +} + +client.on("ready", async() => { + await client.application.bulkEditGlobalCommands(commandsData); + + console.log("Ready as", client.user.tag); +}); + +// if you do not add a listener for the error event, any errors will cause an UncaughtError to be thrown, +// and your process may be killed as a result. +client.on("error", (err) => { + console.error("bro vazne..", err); +}); + +client.on("interactionCreate", async(interaction) => { + switch(interaction.type) { + // https://docs.oceanic.ws/latest/classes/CommandInteraction.html + case InteractionTypes.APPLICATION_COMMAND: + // data = https://docs.oceanic.ws/latest/interfaces/Types_Interactions.ApplicationCommandInteractionData.html + switch(interaction.data.type) { + // Chat Input commands are what you use in the chat, i.e. slash commands + case ApplicationCommandTypes.CHAT_INPUT: + const command = commands.get(interaction.data.name) + + if (!command) + return await interaction.reply("invalid command") + + await command.handle(interaction, db) + + break + } + + break + + case InteractionTypes.MESSAGE_COMPONENT: + switch(interaction.data.componentType) { + case ComponentTypes.BUTTON: + const button = buttons.get(interaction.data.customID.split(".")[0]) + + if (!button) + return await interaction.reply("something went wrong") + + await button.handle(interaction, db) + + break + } + + break + + case InteractionTypes.MODAL_SUBMIT: + const modal = modals.get(interaction.data.customID.split(".")[0]) + + if (!modal) + return await interaction.reply("something went wrong") + + await modal.handle(interaction, db) + + break + } +}); + +await client.connect(); \ No newline at end of file diff --git a/modals/reakcnerole.js b/modals/reakcnerole.js new file mode 100644 index 0000000..415bf28 --- /dev/null +++ b/modals/reakcnerole.js @@ -0,0 +1,54 @@ +import { ApplicationCommandOptionTypes, ApplicationCommandTypes, ButtonStyles, ComponentTypes, MessageFlags, Permissions, SeparatorSpacingSize } from "oceanic.js"; + +export default { + data: { + name: "reakcnerole", + }, + + handle: async (interaction, database) => { + await interaction.defer(MessageFlags.EPHEMERAL) + + const db = database.table(`rr`) + + const splitID = interaction.data.customID.split('.') + + const kolekcia = splitID[1] + + const exists = await db.has(`${interaction.guildID}.${kolekcia}`) + + if (!exists) + return await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `❌ kolekcia ${kolekcia} neexistuje` + } + ] + } + ] + }) + + const popis = interaction.data.components.getTextInput(`popis`) + + await db.set(`${interaction.guildID}.${kolekcia}.popis`, popis) + + await interaction.createFollowup({ + flags: MessageFlags.IS_COMPONENTS_V2, + components: [ + { + type: ComponentTypes.CONTAINER, + components: [ + { + type: ComponentTypes.TEXT_DISPLAY, + content: `✅ popis kolekcie ${kolekcia} zmeneny` + } + ] + } + ] + }) + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..be61961 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "oceanic.js": "^1.15.0" + } +} \ No newline at end of file diff --git a/utils/kvdb.js b/utils/kvdb.js new file mode 100644 index 0000000..3a7080c --- /dev/null +++ b/utils/kvdb.js @@ -0,0 +1,330 @@ +/* + vibecoded :sob: +*/ + +import { Database } from "bun:sqlite"; + +export class KVDB { + /** + * @param {string} [filename="db.sqlite"] - The SQLite database file (e.g., "db.sqlite" or ":memory:") + * @param {string} [table="main_kv"] - The table/context name (defaults to "main_kv") + */ + constructor(filename = "db.sqlite", table = "main_kv") { + this.db = new Database(filename); + this.db.run("PRAGMA journal_mode = WAL;"); + + // Sanitize table name to prevent SQL injection in DDL + this.tableName = table.replace(/[^a-zA-Z0-9_]/g, ""); + + this.db.run(` + CREATE TABLE IF NOT EXISTS "${this.tableName}" ( + key TEXT PRIMARY KEY, + value TEXT + ) + `); + } + + /** + * Switch database table/context without recreating database connections + * @param {string} newTableName + * @returns {KVDB} + */ + table(newTableName) { + return new KVDB(this.db.filename, newTableName); + } + + /** + * Set a key or nested dot-notation property. + * Examples: + * await db.set("user", { name: "Alex" }) + * await db.set("user.age", 30) + * @param {string} keyPath + * @param {any} value + * @returns {Promise} + */ + async set(keyPath, value) { + const parts = keyPath.split("."); + const rootKey = parts[0]; + + if (parts.length === 1) { + this.db.run( + `INSERT INTO "${this.tableName}" (key, value) VALUES (?, ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value`, + [rootKey, JSON.stringify(value)] + ); + return; + } + + const jsonPath = `$.${parts.slice(1).join(".")}`; + + this.db.run( + `INSERT INTO "${this.tableName}" (key, value) VALUES (?, '{}') + ON CONFLICT(key) DO NOTHING`, + [rootKey] + ); + + this.db.run( + `UPDATE "${this.tableName}" + SET value = json_set(value, ?, json(?)) + WHERE key = ?`, + [jsonPath, JSON.stringify(value), rootKey] + ); + } + + /** + * Get a key/nested property, OR find an element inside an array using a predicate function. + * Examples: + * await db.get("user.roles") -> returns whole array + * await db.get("user.roles", (r) => r.id === "admin") -> returns matching role object + * @param {string} keyPath + * @param {Function} [predicate] - Optional matching function for array items + * @returns {Promise} + */ + async get(keyPath, predicate) { + const parts = keyPath.split("."); + const rootKey = parts[0]; + let data = null; + + if (parts.length === 1) { + const row = this.db + .query(`SELECT value FROM "${this.tableName}" WHERE key = ?`) + .get(rootKey); + data = row ? JSON.parse(row.value) : null; + } else { + const jsonPath = `$.${parts.slice(1).join(".")}`; + const row = this.db + .query( + `SELECT json_extract(value, ?) AS result FROM "${this.tableName}" WHERE key = ?` + ) + .get(jsonPath, rootKey); + + if (row && row.result !== null) { + if (typeof row.result === "string") { + try { + data = JSON.parse(row.result); + } catch { + data = row.result; + } + } else { + data = row.result; + } + } + } + + if (typeof predicate === "function") { + if (!Array.isArray(data)) return null; + return data.find(predicate) ?? null; + } + + return data; + } + + /** + * Check if a key/property exists, OR check if an array contains a specific item / predicate match. + * Examples: + * await db.has("user.roles") -> true/false + * await db.has("user.roles", (r) => r.id === "admin") -> true/false + * await db.has("user.roles", "admin_role_id") -> true/false + * @param {string} keyPath + * @param {any | Function} [itemOrPredicate] + * @returns {Promise} + */ + async has(keyPath, itemOrPredicate) { + if (itemOrPredicate !== undefined) { + const arr = await this.get(keyPath); + if (!Array.isArray(arr)) return false; + + if (typeof itemOrPredicate === "function") { + return arr.some(itemOrPredicate); + } + + if (typeof itemOrPredicate === "object" && itemOrPredicate !== null) { + const targetString = JSON.stringify(itemOrPredicate); + return arr.some((el) => JSON.stringify(el) === targetString); + } + + return arr.includes(itemOrPredicate); + } + + const parts = keyPath.split("."); + const rootKey = parts[0]; + + if (parts.length === 1) { + const row = this.db + .query(`SELECT 1 FROM "${this.tableName}" WHERE key = ?`) + .get(rootKey); + return Boolean(row); + } + + const jsonPath = `$.${parts.slice(1).join(".")}`; + const row = this.db + .query( + `SELECT json_type(value, ?) AS type FROM "${this.tableName}" WHERE key = ?` + ) + .get(jsonPath, rootKey); + + return Boolean(row && row.type !== null); + } + + /** + * Delete a key, property, OR remove item(s) from an array using a predicate or value. + * Examples: + * await db.delete("user.roles") -> deletes the roles key/property entirely + * await db.delete("user.roles", (r) => r.id === "admin") -> pulls matching item from array + * @param {string} keyPath + * @param {any | Function} [itemOrPredicate] + * @returns {Promise} + */ + async delete(keyPath, itemOrPredicate) { + if (itemOrPredicate !== undefined) { + const arr = await this.get(keyPath); + if (!Array.isArray(arr)) return; + + let filtered; + if (typeof itemOrPredicate === "function") { + filtered = arr.filter((item) => !itemOrPredicate(item)); + } else if (typeof itemOrPredicate === "object" && itemOrPredicate !== null) { + const targetString = JSON.stringify(itemOrPredicate); + filtered = arr.filter((item) => JSON.stringify(item) !== targetString); + } else { + filtered = arr.filter((item) => item !== itemOrPredicate); + } + + await this.set(keyPath, filtered); + return; + } + + const parts = keyPath.split("."); + const rootKey = parts[0]; + + if (parts.length === 1) { + this.db.run(`DELETE FROM "${this.tableName}" WHERE key = ?`, [rootKey]); + return; + } + + const jsonPath = `$.${parts.slice(1).join(".")}`; + this.db.run( + `UPDATE "${this.tableName}" + SET value = json_remove(value, ?) + WHERE key = ?`, + [jsonPath, rootKey] + ); + } + + /** + * Appends an element to an array at the target key or nested path. + * @param {string} keyPath + * @param {any} value + * @returns {Promise} + */ + async push(keyPath, value) { + const parts = keyPath.split("."); + const rootKey = parts[0]; + const jsonPath = parts.length === 1 ? "$" : `$.${parts.slice(1).join(".")}`; + + if (parts.length === 1) { + this.db.run( + `INSERT INTO "${this.tableName}" (key, value) VALUES (?, '[]') + ON CONFLICT(key) DO NOTHING`, + [rootKey] + ); + } else { + this.db.run( + `INSERT INTO "${this.tableName}" (key, value) VALUES (?, '{}') + ON CONFLICT(key) DO NOTHING`, + [rootKey] + ); + this.db.run( + `UPDATE "${this.tableName}" + SET value = json_set(value, ?, json('[]')) + WHERE key = ? AND json_type(value, ?) IS NULL`, + [jsonPath, rootKey, jsonPath] + ); + } + + const appendPath = `${jsonPath}[#]`; + this.db.run( + `UPDATE "${this.tableName}" + SET value = json_insert(value, ?, json(?)) + WHERE key = ?`, + [appendPath, JSON.stringify(value), rootKey] + ); + } + + /** + * Updates matching item(s) in an array using a predicate and an update callback/object. + * @param {string} keyPath + * @param {Function} predicate + * @param {Object | Function} updateFnOrObject + * @returns {Promise} + */ + async update(keyPath, predicate, updateFnOrObject) { + const arr = await this.get(keyPath); + + if (!Array.isArray(arr)) return false; + + let updatedAny = false; + + const newArr = arr.map((item) => { + if (!predicate(item)) return item; + + updatedAny = true; + + if (typeof updateFnOrObject === "function") { + return updateFnOrObject(item); + } + + if (typeof item === "object" && item !== null && typeof updateFnOrObject === "object") { + return { ...item, ...updateFnOrObject }; + } + + return updateFnOrObject; + }); + + if (updatedAny) { + await this.set(keyPath, newArr); + } + + return updatedAny; + } + + /** + * Renames a key or nested property path without changing its value. + * Examples: + * await db.rename("user.roles", "user.rolesOld") + * await db.rename("user.roles", "rolesOld") + * await db.rename("user", "userOld") + * @param {string} oldKeyPath + * @param {string} newKeyPath + * @returns {Promise} True if target key existed and was renamed + */ + async rename(oldKeyPath, newKeyPath) { + if (!(await this.has(oldKeyPath))) return false; + + const oldParts = oldKeyPath.split("."); + let targetNewPath = newKeyPath; + + if (oldParts.length > 1 && !newKeyPath.startsWith(oldParts.slice(0, -1).join(".") + ".")) { + const parentPath = oldParts.slice(0, -1).join("."); + targetNewPath = `${parentPath}.${newKeyPath}`; + } + + const value = await this.get(oldKeyPath); + + const oldRoot = oldParts[0]; + const newParts = targetNewPath.split("."); + const newRoot = newParts[0]; + + if (oldParts.length === 1 && newParts.length === 1) { + this.db.run( + `UPDATE "${this.tableName}" SET key = ? WHERE key = ?`, + [newRoot, oldRoot] + ); + } else { + await this.set(targetNewPath, value); + await this.delete(oldKeyPath); + } + + return true; + } +} \ No newline at end of file