Compare commits
13 Commits
1fd0cb7aef
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 161c8c1e80 | |||
| 8c0d10f28e | |||
| 44d8bd582b | |||
| 29144c8af4 | |||
| a7168aeddb | |||
| 8abe3ede90 | |||
| 2237aae4f8 | |||
| cbf1fec04d | |||
| 808bba3695 | |||
| 8132fca9b6 | |||
| 0ccac0087e | |||
| aa1d60b19e | |||
| e010811d3a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -136,3 +136,5 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
bun.lock
|
||||||
|
data/
|
||||||
|
|||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# use the official Bun image
|
||||||
|
# see all versions at https://hub.docker.com/r/oven/bun/tags
|
||||||
|
FROM oven/bun:1 AS base
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN bun install
|
||||||
|
|
||||||
|
# run the app
|
||||||
|
USER bun
|
||||||
|
ENTRYPOINT [ "bun", "run", "index.js" ]
|
||||||
152
buttons/reakcnerole.js
Normal file
152
buttons/reakcnerole.js
Normal file
@@ -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 = await 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`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
741
commands/rr.js
Normal file
741
commands/rr.js
Normal file
@@ -0,0 +1,741 @@
|
|||||||
|
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 = await 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í`
|
||||||
|
|
||||||
|
if (Object.keys(kolekcie)?.length) {
|
||||||
|
for (const kolekcia of Object.keys(kolekcie)) {
|
||||||
|
textZoznam += `\n- ${kolekcia}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textZoznam += `\nziadne kolekcie`
|
||||||
|
}
|
||||||
|
|
||||||
|
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})`
|
||||||
|
|
||||||
|
if (role?.length) {
|
||||||
|
for (const rola of role) {
|
||||||
|
textZoznam += `\n- ${rola.nazov ? `${rola.nazov} `: ``}<@&${rola.id}>`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textZoznam += `\nziadne role`
|
||||||
|
}
|
||||||
|
|
||||||
|
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`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
docker-compose.yml
Normal file
4
docker-compose.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
services:
|
||||||
|
itckar-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
108
index.js
Normal file
108
index.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { ApplicationCommandTypes, Client, ComponentTypes, InteractionTypes } from "oceanic.js";
|
||||||
|
import { readdir } from "node:fs/promises";
|
||||||
|
import { KVDB } from "./utils/kvdb";
|
||||||
|
|
||||||
|
const db = new KVDB({
|
||||||
|
user: process.env.POSTGRESQL_USERNAME,
|
||||||
|
password: process.env.POSTGRESQL_PASSWORD,
|
||||||
|
host: process.env.POSTGRESQL_HOST,
|
||||||
|
port: process.env.POSTGRESQL_PORT,
|
||||||
|
database: process.env.POSTGRESQL_DATABASE
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.init();
|
||||||
|
|
||||||
|
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();
|
||||||
54
modals/reakcnerole.js
Normal file
54
modals/reakcnerole.js
Normal file
@@ -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 = await 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`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package.json
Normal file
6
package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"oceanic.js": "^1.15.0",
|
||||||
|
"pg": "^8.23.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
320
utils/kvdb.js
Normal file
320
utils/kvdb.js
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
vibecoded :sob:
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Pool } from "pg";
|
||||||
|
|
||||||
|
export class KVDB {
|
||||||
|
/**
|
||||||
|
* @param {Pool | object} poolOrConfig - A pg.Pool instance or pg connection config object
|
||||||
|
* @param {string} [table="main_kv"] - The table/context name (defaults to "main_kv")
|
||||||
|
*/
|
||||||
|
constructor(poolOrConfig, table = "main_kv") {
|
||||||
|
this.pool = poolOrConfig instanceof Pool ? poolOrConfig : new Pool(poolOrConfig);
|
||||||
|
|
||||||
|
// Sanitize table name to prevent SQL injection in DDL
|
||||||
|
this.tableName = table.replace(/[^a-zA-Z0-9_]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the database table. Call this once after instantiating the class.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
await this.pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value JSONB
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch database table/context sharing the same connection pool
|
||||||
|
* @param {string} newTableName
|
||||||
|
* @returns {Promise<KVDB>}
|
||||||
|
*/
|
||||||
|
async table(newTableName) {
|
||||||
|
const newDb = new KVDB(this.pool, newTableName);
|
||||||
|
await newDb.init();
|
||||||
|
return newDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<void>}
|
||||||
|
*/
|
||||||
|
async set(keyPath, value) {
|
||||||
|
const parts = keyPath.split(".");
|
||||||
|
const rootKey = parts[0];
|
||||||
|
const jsonValue = JSON.stringify(value);
|
||||||
|
|
||||||
|
if (parts.length === 1) {
|
||||||
|
await this.pool.query(
|
||||||
|
`INSERT INTO "${this.tableName}" (key, value) VALUES ($1, $2::jsonb)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value`,
|
||||||
|
[rootKey, jsonValue]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathArr = parts.slice(1);
|
||||||
|
|
||||||
|
await this.pool.query(
|
||||||
|
`INSERT INTO "${this.tableName}" (key, value) VALUES ($1, '{}'::jsonb)
|
||||||
|
ON CONFLICT(key) DO NOTHING`,
|
||||||
|
[rootKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.pool.query(
|
||||||
|
`UPDATE "${this.tableName}"
|
||||||
|
SET value = jsonb_set(value, $1::text[], $2::jsonb, true)
|
||||||
|
WHERE key = $3`,
|
||||||
|
[pathArr, jsonValue, rootKey]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a key/nested property, OR find an element inside an array using a predicate function.
|
||||||
|
* @param {string} keyPath
|
||||||
|
* @param {Function} [predicate] - Optional matching function for array items
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async get(keyPath, predicate) {
|
||||||
|
const parts = keyPath.split(".");
|
||||||
|
const rootKey = parts[0];
|
||||||
|
let data = null;
|
||||||
|
|
||||||
|
if (parts.length === 1) {
|
||||||
|
const { rows } = await this.pool.query(
|
||||||
|
`SELECT value FROM "${this.tableName}" WHERE key = $1`,
|
||||||
|
[rootKey]
|
||||||
|
);
|
||||||
|
// pg parses JSONB automatically
|
||||||
|
data = rows.length > 0 ? rows[0].value : null;
|
||||||
|
} else {
|
||||||
|
const pathArr = parts.slice(1);
|
||||||
|
const { rows } = await this.pool.query(
|
||||||
|
`SELECT value #> $1::text[] AS result FROM "${this.tableName}" WHERE key = $2`,
|
||||||
|
[pathArr, rootKey]
|
||||||
|
);
|
||||||
|
data = (rows.length > 0 && rows[0].result !== undefined) ? rows[0].result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
* @param {string} keyPath
|
||||||
|
* @param {any | Function} [itemOrPredicate]
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
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 { rows } = await this.pool.query(
|
||||||
|
`SELECT 1 FROM "${this.tableName}" WHERE key = $1`,
|
||||||
|
[rootKey]
|
||||||
|
);
|
||||||
|
return rows.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathArr = parts.slice(1);
|
||||||
|
const { rows } = await this.pool.query(
|
||||||
|
`SELECT 1 FROM "${this.tableName}"
|
||||||
|
WHERE key = $1 AND value #> $2::text[] IS NOT NULL`,
|
||||||
|
[rootKey, pathArr]
|
||||||
|
);
|
||||||
|
|
||||||
|
return rows.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a key, property, OR remove item(s) from an array using a predicate or value.
|
||||||
|
* @param {string} keyPath
|
||||||
|
* @param {any | Function} [itemOrPredicate]
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
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) {
|
||||||
|
await this.pool.query(`DELETE FROM "${this.tableName}" WHERE key = $1`, [rootKey]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathArr = parts.slice(1);
|
||||||
|
await this.pool.query(
|
||||||
|
`UPDATE "${this.tableName}"
|
||||||
|
SET value = value #- $1::text[]
|
||||||
|
WHERE key = $2`,
|
||||||
|
[pathArr, rootKey]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an element to an array at the target key or nested path.
|
||||||
|
* @param {string} keyPath
|
||||||
|
* @param {any} value
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async push(keyPath, value) {
|
||||||
|
const parts = keyPath.split(".");
|
||||||
|
const rootKey = parts[0];
|
||||||
|
const wrappedValue = JSON.stringify([value]);
|
||||||
|
|
||||||
|
if (parts.length === 1) {
|
||||||
|
await this.pool.query(
|
||||||
|
`INSERT INTO "${this.tableName}" (key, value) VALUES ($1, $2::jsonb)
|
||||||
|
ON CONFLICT(key) DO UPDATE
|
||||||
|
SET value = CASE
|
||||||
|
WHEN jsonb_typeof("${this.tableName}".value) = 'array'
|
||||||
|
THEN "${this.tableName}".value || $2::jsonb
|
||||||
|
ELSE $2::jsonb
|
||||||
|
END`,
|
||||||
|
[rootKey, wrappedValue]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const pathArr = parts.slice(1);
|
||||||
|
|
||||||
|
await this.pool.query(
|
||||||
|
`INSERT INTO "${this.tableName}" (key, value) VALUES ($1, '{}'::jsonb)
|
||||||
|
ON CONFLICT(key) DO NOTHING`,
|
||||||
|
[rootKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.pool.query(
|
||||||
|
`UPDATE "${this.tableName}"
|
||||||
|
SET value = jsonb_set(
|
||||||
|
value,
|
||||||
|
$1::text[],
|
||||||
|
COALESCE(value #> $1::text[], '[]'::jsonb) || $2::jsonb,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
WHERE key = $3`,
|
||||||
|
[pathArr, wrappedValue, 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<boolean>}
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* @param {string} oldKeyPath
|
||||||
|
* @param {string} newKeyPath
|
||||||
|
* @returns {Promise<boolean>} 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) {
|
||||||
|
await this.pool.query(
|
||||||
|
`UPDATE "${this.tableName}" SET key = $1 WHERE key = $2`,
|
||||||
|
[newRoot, oldRoot]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.set(targetNewPath, value);
|
||||||
|
await this.delete(oldKeyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user