diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index fe24bdd..7a74c27 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -2,7 +2,7 @@ name: Build and Push Docker Image on: push: - branches: [ main ] + branches: [ main, stoat ] jobs: build: @@ -21,14 +21,14 @@ jobs: - name: Build Docker Image run: | # Build the image with the commit hash tag - docker build -t git.cesium.one/kira/cockinator . + docker build -t git.cesium.one/kira/cockinator:stoat . - name: Push Docker Images env: BRANCH_NAME: ${{ github.ref_name }} SHORT_HASH: ${{ github.sha }} run: | - docker push git.cesium.one/kira/cockinator + docker push git.cesium.one/kira/cockinator:stoat - name: Log out from registry if: always() diff --git a/README.md b/README.md index 4d5b743..03339e7 100644 --- a/README.md +++ b/README.md @@ -2,41 +2,39 @@ it finds twitter links and turns them into girlcock links for better embed +rebuilt for [Stoat](https://stoat.chat), missing everything but the core functionality + ## easiest way -docker image published at `git.cesium.one/kira/cockinator:latest` only needs `DISCORD` passed as a variable with your token +docker image published at `git.cesium.one/kira/cockinator:stoat` only needs `TOKEN` passed as a variable with your token ### examples -`docker run -e "DISCORD=TOKEN_HERE" git.cesium.one/kira/cockinator:latest` +`docker run -e "TOKEN=TOKEN_HERE" git.cesium.one/kira/cockinator:stoat` ```yml services: bot: restart: unless-stopped - image: git.cesium.one/kira/cockinator + image: git.cesium.one/kira/cockinator:stoat environment: - - DISCORD=TOKEN_HERE + - TOKEN=TOKEN_HERE ``` ## easy way -this also works in not docker with regular node.js, and pass it the `DISCORD` variable however you like +this also works in not docker with regular node.js (v22.15+), and pass it the `TOKEN` variable however you like -`npm i && DISCORD=TOKEN_HERE node index.js` +`npm i && TOKEN=TOKEN_HERE node index.js` ## other ways you can build the image yourself with `docker build . -t cockinator` to then run with `docker run cockinator` -you can also build the image with docker-compose, by replacing `image: git.cesium.one/kira/cockinator` with `build: .` +you can also build the image with docker-compose, by replacing `image: git.cesium.one/kira/cockinator:stoat` with `build: .` ## the config file -for the full functionality of the bot, you can copy `config.json.default` to `config.json` and fill it in. +we only need the config to customize the initial status of the bot, so if you wanna do that, you can copy `config.json.default` to `config.json` and fill it in it'll need to be in the main directory, and for docker that means mounting it as a volume with `-v ./config.json:/app/config.json` - -## profile picture rotation - -it'll take everything in ./avatars and shuffle them before rotating through them. this can also be mounted for docker with `-v ./avatars:/app/avatars` diff --git a/commands/jellyfin.js b/commands/jellyfin.js deleted file mode 100644 index f8d83e2..0000000 --- a/commands/jellyfin.js +++ /dev/null @@ -1,196 +0,0 @@ -let config = {} -try { config = require('../config.json'); } -catch { config.jellyfin = null; } - -const { SlashCommandBuilder } = require('discord.js'); -const { createClient } = require('../lib/jellyfin'); - -async function sendChunked(interaction, content) { - const newlineIndex = content.indexOf('\n'); - - // If there's no newline, or the content is short, just send it all. - if (newlineIndex === -1 || content.length <= 2000) { - return interaction.editReply(content); - } - - const firstLine = content.substring(0, newlineIndex); - const restOfContent = content.substring(newlineIndex + 1); - - await interaction.editReply(firstLine); - - if (restOfContent.length > 0) { - const messages = []; - let i = 0; - while (i < restOfContent.length) { - let end = i + 2000; - if (end > restOfContent.length) { - end = restOfContent.length; - } else { - const lastNewline = restOfContent.lastIndexOf('\n', end); - if (lastNewline > i) { - end = lastNewline; - } - } - messages.push(restOfContent.substring(i, end)); - i = end; - if (restOfContent.charAt(i) === '\n') i++; // move past newline - } - - for (const chunk of messages) { - if (chunk.length > 0) { // Don't send empty messages - await interaction.channel.send({ - content: chunk, - flags: 4096, - }); - } - } - } -} - -module.exports = { - data: new SlashCommandBuilder() - .setName('jellyfin') - .setDescription('Get media from media.cesium.one') - .addSubcommand((s) => - s - .setName('search') - .setDescription('Search items') - .addStringOption((o) => o.setName('query').setDescription('Search query').setRequired(true)) - .addIntegerOption((o) => o.setName('limit').setDescription('Max results').setRequired(false)) - ).addSubcommand((s) => - s - .setName('series') - .setDescription('Get info about a series') - .addStringOption((o) => o.setName('series').setDescription('Series ID or search term').setRequired(true)) - .addIntegerOption((o) => o.setName('season').setDescription('Season number').setRequired(false)) - ).addSubcommand((s) => - s - .setName('movie') - .setDescription('Get info about a movie') - .addStringOption((o) => o.setName('movie').setDescription('Movie ID or search term').setRequired(true)) - ).addSubcommand((s) => - s - .setName('list') - .setDescription('List content on the server') - .addStringOption((o) => o.setName('type').setDescription('Type of either Movie or Series').setRequired(false).addChoices({name: 'Movie', value: 'movie'}, {name: 'Series', value: 'series'})) - ), - - async execute(interaction) { - if (!config.jellyfin) { - interaction.reply({ content: 'This bot is not configured with a Jellyfin instance.', flags: 64 }); - return; - } else if (!config.jellyfin.users.includes(interaction.user.id)) { - interaction.reply({ content: 'You are not authorized to use this command.', flags: 64 }); - return; - } else if (interaction.channel.type !== 1) { - interaction.reply({ content: 'Please keep this command in DMs. It exposes a direct API key for my media server.', flags: 64 }); - return; - } - const sub = interaction.options.getSubcommand(); - const jelly = createClient(config.jellyfin || {}); - - if (!config.jellyfin || !config.jellyfin.url) { - await interaction.reply('Jellyfin not configured (check config.jellyfin.url/key)'); - return; - } - - await interaction.deferReply(); - - try { - if (sub === 'search') { - const query = interaction.options.getString('query'); - const limit = interaction.options.getInteger('limit') || 10; - const params = { - SearchTerm: query, - Limit: limit, - Recursive: true, - IncludeItemTypes: 'Movie,Series', - Fields: 'Overview,PrimaryImageAspectRatio' - }; - const res = await jelly.request('/Items', params); - const items = Array.isArray(res.Items) ? res.Items : []; - if (!items || items.length === 0) return await interaction.editReply('No results'); - const lines = items.slice(0, limit).map((it) => `${it.Name} - ${it.Id} (${it.Type || it.SeriesType || 'item'})`); - const out = `Results for ${query}\n${lines.join('\n')}`; - return sendChunked(interaction, out); - } - - if (sub === 'series') { - const id = interaction.options.getString('series'); - const season = interaction.options.getInteger('season'); - - // If `id` isn't a 32-char hex ID (allowing dashes), treat it as a search term - const cleaned = (id || '').replace(/-/g, ''); - const isId = /^[a-f0-9]{32}$/i.test(cleaned); - let seriesId = id; - if (!isId) { - const sres = await jelly.request('/Items', { - SearchTerm: id, - IncludeItemTypes: 'Series', - Limit: 1, - Recursive: true - }); - const sitems = Array.isArray(sres.Items) ? sres.Items : []; - if (!sitems || sitems.length === 0) return await interaction.editReply('No series found'); - seriesId = sitems[0].Id; - } - if (!season) { - const res = await jelly.request(`/Shows/${seriesId}/Seasons`); - const items = Array.isArray(res.Items) ? res.Items : []; - if (!items || items.length === 0) return await interaction.editReply('No seasons found'); - const lines = items.map((it) => `${it.Name} - ${it.Id}`); - const out = `Seasons for ${items[0].SeriesName}\n${lines.join('\n')}`; - return sendChunked(interaction, out); - } - - const res = await jelly.request(`/Shows/${seriesId}/Episodes`, {season: season}); - const items = Array.isArray(res.Items) ? res.Items : []; - if (!items || items.length === 0) return await interaction.editReply('No episodes found'); - console.log(items[0]) - const lines = items.map((it) => `${it.IndexNumber}. ${it.Name} [[source](${config.jellyfin.url}/Items/${it.Id}/Download?api_key=${config.jellyfin.key})] [[480p](${config.jellyfin.url}/Videos/${it.Id}/stream?api_key=${config.jellyfin.key}&videoCodec=h264&width=854&height=480)]`); - const out = `Episodes for ${items[0].SeriesName} ${items[0].SeasonName}\n${lines.join('\n')}`; - return sendChunked(interaction, out); - } - - if (sub === 'movie') { - const id = interaction.options.getString('movie'); - - // If `id` isn't a 32-char hex ID (allowing dashes), treat it as a search term - const cleaned = (id || '').replace(/-/g, ''); - const isId = /^[a-f0-9]{32}$/i.test(cleaned); - let movieId = id; - if (!isId) { - const sres = await jelly.request('/Items', { - SearchTerm: id, - IncludeItemTypes: 'Movie', - Limit: 1, - Recursive: true - }); - const sitems = Array.isArray(sres.Items) ? sres.Items : []; - if (!sitems || sitems.length === 0) return await interaction.editReply('No movies found'); - movieId = sitems[0].Id; - } - - const res = await jelly.request(`/Items/${movieId}`); - let out = `[${res.Name}](${config.jellyfin.url}/Items/${res.Id}/Download?api_key=${config.jellyfin.key})`; - out += ` [[h264](${config.jellyfin.url}/Videos/${res.Id}/stream?api_key=${config.jellyfin.key}&videoCodec=h264)]` - out += ` [[480p](${config.jellyfin.url}/Videos/${res.Id}/stream?api_key=${config.jellyfin.key}&videoCodec=h264&width=854&height=480)]` - return sendChunked(interaction, out); - } - - if (sub === 'list') { - const type = interaction.options.getString('type'); - const res = await jelly.request('/Items/Latest', {limit: 500, includeItemTypes: type || 'Movie,Series'}); - const items = Array.isArray(res) ? res : []; - if (!items || items.length === 0) return await interaction.editReply('No content found'); - const lines = items.map((it) => `${it.Name} - ${it.Id} (${it.Type || it.SeriesType || 'item'})`); - const out = `Listing content:\n${lines.join('\n')}`; - return sendChunked(interaction, out); - } - - await interaction.editReply('Unknown subcommand'); - } catch (err) { - await interaction.editReply(`Error fetching from Jellyfin: ${err.message}`); - } - }, -}; diff --git a/commands/status.js b/commands/status.js deleted file mode 100644 index 871f938..0000000 --- a/commands/status.js +++ /dev/null @@ -1,18 +0,0 @@ -const { SlashCommandBuilder, ActivityType } = require('discord.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('status') - .setDescription('Change the bot\'s status message') - .addStringOption(option => - option.setName('message') - .setDescription('The new status message') - .setRequired(true) - ), - async execute(interaction) { - const message = interaction.options.getString('message'); - console.log(`${interaction.user.tag} is changing the status to ${message}`); - interaction.client.user.setActivity(message, { type: ActivityType.Custom }); - await interaction.reply({ content: `Status updated to: ${message}`, ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/config.json.default b/config.json.default index 8952784..b253213 100644 --- a/config.json.default +++ b/config.json.default @@ -1,17 +1,4 @@ { "token": "bot token go here", - "status": "looking for x.com links", - "avatarDir": "./avatars", - "avatarInterval": "24h", - "parentsAndOrGuardians": [ - "230659159450845195", - "297983197990354944" - ], - "jellyfin": { - "url": "https://media.example.com", - "key": "jellyfin user key (not generated)", - "users": [ - "000000000000000000" - ] - } + "status": "looking for x.com links" } \ No newline at end of file diff --git a/deploy-commands.js b/deploy-commands.js deleted file mode 100644 index eacab57..0000000 --- a/deploy-commands.js +++ /dev/null @@ -1,44 +0,0 @@ -// yoinked right from the guide - -const { REST, Routes } = require('discord.js'); -const { token } = require('./config.json'); -const fs = require('node:fs'); -const path = require('node:path'); - -const commands = []; -const commandsPath = path.join(__dirname, 'commands'); -const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); -// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment -for (const file of commandFiles) { - const filePath = path.join(commandsPath, file); - const command = require(filePath); - if ('data' in command && 'execute' in command) { - commands.push(command.data.toJSON()); - } else { - console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); - } -} - - -// Construct and prepare an instance of the REST module -const rest = new REST().setToken(token); - -// and deploy your commands! -(async () => { - try { - console.log(`Started refreshing ${commands.length} application (/) commands.`); - - // Determine application (bot) id dynamically and refresh commands - const app = await rest.get(Routes.oauth2CurrentApplication()); - const applicationId = app && app.id; - if (!applicationId) throw new Error('Unable to determine application id. Set CLIENT_ID env var or add id to config.json'); - - // The put method is used to fully refresh all commands with the current set - const data = await rest.put(Routes.applicationCommands(applicationId), { body: commands }); - - console.log(`Successfully reloaded ${data.length} application (/) commands.`); - } catch (error) { - // And of course, make sure you catch and log any errors! - console.error(error); - } -})(); \ No newline at end of file diff --git a/index.js b/index.js index ea4b33e..196f98e 100644 --- a/index.js +++ b/index.js @@ -1,182 +1,62 @@ +import { createRequire } from 'module'; +import { Client } from "stoat.js"; + +const require = createRequire(import.meta.url); + let config = {}; try { config = require('./config.json'); } catch { - if (process.env.DISCORD && process.env.DISCORD != "TOKEN_HERE") { - config.token = process.env.DISCORD; + if (process.env.TOKEN && process.env.TOKEN != "TOKEN_HERE") { + config.token = process.env.TOKEN; } else { - console.error("FATAL: Discord token required. Either pass it as an environment variable \"DISCORD\", or fill out config.json.default."); + console.error("FATAL: Stoat token required. Either pass it as an environment variable \"TOKEN\", or fill out config.json.default."); process.exit(1); } } -// the basic discord setup stuff yoinked from their guide -const { Client, Events, GatewayIntentBits, Partials, ActivityType, MessageFlags, Collection } = require('discord.js'); -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.DirectMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildMessageReactions - ], - partials: [ - Partials.Channel, - Partials.Message, - Partials.Reaction, - Partials.User - ] + +const client = new Client(); + +client.on("error", (err) => { + console.error("Client error:", err?.data?.type || err?.type || err); }); -const avatarRotation = require('./lib/avatarRotation'); - -// build the button below the message that swaps the URL -const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); -const swapRow = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId('swap_twitter') - .setLabel('Swap URL') - .setStyle(ButtonStyle.Secondary), - ); - -// the main function that swaps the URL, takes function convertURL(url, regex, domain) { const match = url.match(regex); if (match) { - console.log(`Converting ${url} to ${domain}`) + console.log(`Converting ${url} to ${domain}`); return `https://${domain}/${match[1]}/status/${match[2]}`; } } -// called by the swap button, swaps girlcock links to fxtwitter, and back again if needed -function swapify(url) { - const girlcockRegex = /https?:\/\/girlcockx\.com\/(.*?)\/status\/(\d+)/; - const fxtwitterRegex = /https?:\/\/fxtwitter\.com\/(.*?)\/status\/(\d+)/; - const fixupxRegex = /https?:\/\/fixupx\.com\/(.*?)\/status\/(\d+)/; - const twitterRegex = /https?:\/\/x\.com\/(.*?)\/status\/(\d+)/; - if (url.match(girlcockRegex)) return convertURL(url, girlcockRegex, "fxtwitter.com"); - if (url.match(fxtwitterRegex)) return convertURL(url, fxtwitterRegex, "fixupx.com"); - if (url.match(fixupxRegex)) return convertURL(url, fixupxRegex, "x.com"); - if (url.match(twitterRegex)) return convertURL(url, twitterRegex, "girlcockx.com"); - return url; // give up, we'll just return the original URL -} - -// what actually listens for the button press and calls swapify -client.on(Events.InteractionCreate, async interaction => { - if (interaction.isButton()) { - if (interaction.customId === 'swap_twitter') { - try { - await interaction.update(swapify(interaction.message.content)); - } catch (error) { // honestly the swapify function has its own error handling and it should be fine but whatever - console.error(error); - await interaction.reply({ content: "Something went wrong trying to swap the URL", ephemeral: true }); - } - } - return; +client.on("ready", async () => { + console.log(`Stoat: Connected as ${client.user?.username}`); + if (config.status) { + await client.user?.edit({ status: { text: config.status, presence: "Online" } }); } }); -client.once(Events.ClientReady, readyClient => { - console.log(`Discord: Connected as ${readyClient.user.tag}`); - client.user.setActivity(config.status, { type: ActivityType.Custom }); - avatarRotation.start(client, config); -}); -client.login(config.token); +client.on("messageCreate", async (message) => { + // ignore our own messages + if (message.authorId === client.user?.id) return; -client.on(Events.MessageCreate, message => { // if we smell a twitter link, girlcock it! const twitterRegex = /https?:\/\/x\.com\/(.*?)\/status\/(\d+)/; const regexProfile = message.content.match(twitterRegex); if (regexProfile) { - const cocklink = convertURL(regexProfile[0], twitterRegex, "girlcockx.com") - message.channel.send({ content: cocklink, flags: MessageFlags.SuppressNotifications, components: [swapRow] }) - message.suppressEmbeds().catch(err => - // this next bit just cuts down the error to the important part, which will usually end up being "no permissions" - console.error("Removing original embed failed: " + err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0]) - ) - } - - // wouldnt it be funny to react to 1 in like 1000 messages with emoji from a list - if (Math.random() < 0.001 && !message.author.bot) { - const customEmojis = [ - 'Shitten:1430413059574206555', - 'BLOWSUP:1430413011918651503', - 'grin_cat:1445254917991436449', - 'R_:1461939667657298081', - 'crumble:1461939666121920605', - 'jumble:1461939664306045008', - 'scrumble:1461939662930055278', - 'Chundle:1461939661541867713', - 'chimgen:1461939660212408351' - ]; - const randomEmoji = customEmojis[Math.floor(Math.random() * customEmojis.length)]; - message.react(randomEmoji); - } -}); - -// funny auto mpreg react -const mpregs = [ - 'mpreg01:1434029622206398556', - 'mpreg02:1434029708038639807', - 'mpreg03:1434029731321352192', - 'mpreg04:1434029755619086517', - 'mpreg05:1434029779514032228', - 'mpreg06:1434029803358523482', - 'mpreg07:1434029827681161266', - 'mpreg08:1434029848866717798', - 'mpreg09:1434029865593606215', - 'mpreg10:1434029885009166467', - 'mpreg11:1434029910158217327', - 'mpreg12:1434029928768077865', - 'mpreg13:1434029953346830417', - 'mpreg14:1434029984808304730', - 'mpreg15:1434030008124309585', - 'mpreg16:1434030025144795207', - 'mpreg17:1434030048586760303', - 'mpreg18:1434030067419451402', - 'mpreg19:1434030085794435092' -]; -client.on(Events.MessageReactionAdd, (reaction, user) => { - if (reaction.emoji.name === '🫃' && !user.bot) { - reaction.message.react('🫃'); - for (const mpreg of mpregs) { - reaction.message.react(mpreg).catch(err => console.error(err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0])); + const cocklink = convertURL(regexProfile[0], twitterRegex, "girlcockx.com"); + try { + // send the converted link, crediting the sender (silent to avoid pinging) + await message.channel?.sendMessage({ + content: `<@${message.authorId}> sent: ${cocklink}`, + flags: 1, // silent / suppress notifications + }); + // delete the original message to prevent embed + await message.delete(); + } catch (err) { + console.error("Link replacement failed: " + (err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0])); } } }); -// command handling for ./commands -const fs = require('fs'); -const path = require('node:path'); -client.commands = new Collection(); -const commandsPath = path.join(__dirname, 'commands'); -const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); -for (const file of commandFiles) { - const filePath = path.join(commandsPath, file); - const command = require(filePath); - if ('data' in command && 'execute' in command) { - client.commands.set(command.data.name, command); - console.debug(`Commands: Registered "${command.data.name}"`); - } else { - console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); - } -} - -client.on(Events.InteractionCreate, async interaction => { - if (!interaction.isChatInputCommand()) return; - const command = interaction.client.commands.get(interaction.commandName); - if (!command) { - console.error(`No command matching ${interaction.commandName} was found.`); - return; - } try { - console.debug(`Command: Executing ${interaction.commandName}`); - await command.execute(interaction); - } catch (error) { - console.error(error); - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); - } else { - await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); - } - } -}); +client.loginBot(config.token); diff --git a/lib/avatarRotation.js b/lib/avatarRotation.js deleted file mode 100644 index 1ad0473..0000000 --- a/lib/avatarRotation.js +++ /dev/null @@ -1,96 +0,0 @@ -// this entire file is Opus 4.6 sorry - -const fs = require('fs'); -const path = require('path'); - -const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp']; -const DEFAULT_INTERVAL = '24h'; -const DEFAULT_DIR = './avatars'; - -/** Parse an interval string like "24h" or "30m" into milliseconds */ -function parseInterval(str) { - const match = String(str).match(/^(\d+)\s*(h|m)$/i); - if (!match) { - console.warn(`Avatars: Invalid interval "${str}", falling back to ${DEFAULT_INTERVAL}`); - return parseInterval(DEFAULT_INTERVAL); - } - const value = parseInt(match[1], 10); - const unit = match[2].toLowerCase(); - return unit === 'h' ? value * 60 * 60 * 1000 : value * 60 * 1000; -} - -/** Scan the directory and return an array of image file paths */ -function scanAvatars(dir) { - try { - return fs.readdirSync(dir) - .filter(f => IMAGE_EXTENSIONS.includes(path.extname(f).toLowerCase())) - .map(f => path.join(dir, f)); - } catch (err) { - console.warn(`Avatars: Could not read directory "${dir}": ${err.message}`); - return []; - } -} - -/** Pick a random avatar and set it on the client */ -async function setRandomAvatar(client, avatars) { - if (!avatars.length) { - console.warn('Avatars: No images found, skipping avatar change'); - return; - } - const pick = avatars[Math.floor(Math.random() * avatars.length)]; - try { - await client.user.setAvatar(pick); - console.log(`Avatars: Changed to ${path.basename(pick)}`); - } catch (err) { - console.error(`Avatars: Failed to set avatar: ${err.message}`); - } -} - -/** - * Start the avatar rotation system. - * @param {import('discord.js').Client} client - The Discord client (must be ready) - * @param {object} config - Bot config object - */ -function start(client, config = {}) { - const avatarDir = path.resolve(config.avatarDir || DEFAULT_DIR); - const intervalMs = parseInterval(config.avatarInterval || DEFAULT_INTERVAL); - - // ensure the directory exists - if (!fs.existsSync(avatarDir)) { - console.warn(`Avatars: Directory "${avatarDir}" does not exist, avatar rotation disabled`); - return; - } - - let avatars = scanAvatars(avatarDir); - console.log(`Avatars: Found ${avatars.length} image(s) in ${avatarDir}`); - console.log(`Avatars: Rotation interval set to ${config.avatarInterval || DEFAULT_INTERVAL}`); - - // set one immediately on boot - setRandomAvatar(client, avatars); - - // rotate on the configured interval - setInterval(() => { - avatars = scanAvatars(avatarDir); // re-scan in case watcher missed something - setRandomAvatar(client, avatars); - }, intervalMs); - - // live-watch the folder for changes - let debounceTimer = null; - try { - fs.watch(avatarDir, (eventType, filename) => { - if (debounceTimer) clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => { - const updated = scanAvatars(avatarDir); - if (updated.length !== avatars.length) { - console.log(`Avatars: Folder changed, now ${updated.length} image(s)`); - } - avatars = updated; - }, 500); // 500ms debounce - }); - console.log(`Avatars: Watching "${avatarDir}" for changes`); - } catch (err) { - console.warn(`Avatars: Could not watch directory: ${err.message}`); - } -} - -module.exports = { start }; diff --git a/lib/jellyfin.js b/lib/jellyfin.js deleted file mode 100644 index 204b76b..0000000 --- a/lib/jellyfin.js +++ /dev/null @@ -1,75 +0,0 @@ -const https = require('https'); -const http = require('http'); - -function fetchJson(urlStr, headers = {}, redirectCount = 0) { - const MAX_REDIRECTS = 5; - return new Promise((resolve, reject) => { - if (redirectCount > MAX_REDIRECTS) return reject(new Error('Too many redirects')); - const url = new URL(urlStr); - const lib = url.protocol === 'https:' ? https : http; - const opts = { - hostname: url.hostname, - port: url.port || (url.protocol === 'https:' ? 443 : 80), - path: url.pathname + url.search, - method: 'GET', - headers: Object.assign({ Accept: 'application/json', 'User-Agent': 'cockbot/1.0' }, headers), - }; - - const req = lib.request(opts, (res) => { - if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - const next = new URL(res.headers.location, url); - res.resume(); - return resolve(fetchJson(next.toString(), headers, redirectCount + 1)); - } - - let data = ''; - res.on('data', (chunk) => (data += chunk)); - res.on('end', () => { - if (!data || data.trim() === '') return resolve({}); - try { - resolve(JSON.parse(data)); - } catch (e) { - console.log('Jellyfin response invalid JSON:'); - console.log('Status:', res.statusCode); - console.log('Headers:', res.headers); - console.log('Body:', data); - reject(new Error(`Invalid JSON response (status ${res.statusCode})`)); - } - }); - }); - req.on('error', reject); - req.end(); - }); -} - -function createClient(cfg) { - if (!cfg || !cfg.url) throw new Error('Missing jellyfin config.url'); - const base = cfg.url.replace(/\/$/, ''); - const key = cfg.key; - - function buildUrl(path, params = {}) { - const u = new URL(`${base}${path}`); - //if (key) u.searchParams.set('api_key', key); - Object.keys(params).forEach((k) => u.searchParams.set(k, params[k])); - return u.toString(); - } - - async function request(path, params = {}) { - const url = buildUrl(path, params); - const headers = {}; - if (key) headers['X-Emby-Token'] = key; - return fetchJson(url, headers); - } - - async function getCurrentUser() { - return request('/Users/Me'); - } - - async function getUserViews(userId) { - return request(`/Users/${encodeURIComponent(userId)}/Views`); - } - - return { getCurrentUser, getUserViews, request }; -} - -module.exports = { createClient }; diff --git a/package-lock.json b/package-lock.json index 474b03e..bd72e2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,156 +1,28 @@ { "name": "cockbot", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cockbot", - "version": "1.0.0", + "version": "2.0.0", "license": "ISC", "dependencies": { - "discord.js": "^14.25.1", - "fs": "^0.0.1-security" + "stoat.js": "^7.3.6" }, "devDependencies": { "@eslint/js": "^9.24.0", "eslint": "^9.24.0" - } - }, - "node_modules/@discordjs/builders": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", - "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/formatters": "^0.6.2", - "@discordjs/util": "^1.2.0", - "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "^0.38.33", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.4", - "tslib": "^2.6.3" }, "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/collection": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", - "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/@discordjs/formatters": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", - "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", - "license": "Apache-2.0", - "dependencies": { - "discord-api-types": "^0.38.33" - }, - "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/rest": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", - "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^2.1.1", - "@discordjs/util": "^1.1.1", - "@sapphire/async-queue": "^1.5.3", - "@sapphire/snowflake": "^3.5.3", - "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.38.16", - "magic-bytes.js": "^1.10.0", - "tslib": "^2.6.3", - "undici": "6.21.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/util": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", - "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", - "license": "Apache-2.0", - "dependencies": { - "discord-api-types": "^0.38.33" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", - "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.5.1", - "@discordjs/util": "^1.1.0", - "@sapphire/async-queue": "^1.5.2", - "@types/ws": "^8.5.10", - "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "^0.38.1", - "tslib": "^2.6.2", - "ws": "^8.17.0" - }, - "engines": { - "node": ">=16.11.0" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", - "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" + "node": ">=22.15.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -180,9 +52,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -190,34 +62,37 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -228,20 +103,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -252,19 +127,22 @@ } }, "node_modules/@eslint/js": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", - "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -272,32 +150,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -309,33 +174,19 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -351,9 +202,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -364,43 +215,55 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@sapphire/async-queue": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", - "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/shapeshift": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", - "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "node_modules/@solid-primitives/map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/map/-/map-0.7.3.tgz", + "integrity": "sha512-2Ach52ANEWYUKFtlrKWljrCtAHJwXnfNEvNfQwA+80nS/Bdw9fSumWQiRJNoDQLN0k5iEggWRBHd6vC/uqYKcA==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" + "@solid-primitives/trigger": "^1.2.3" }, - "engines": { - "node": ">=v16" + "peerDependencies": { + "solid-js": "^1.6.12" } }, - "node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "node_modules/@solid-primitives/set": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/set/-/set-0.7.3.tgz", + "integrity": "sha512-Ipk8izBOfIjWxKfkW+la/4uDHCsOlK783g4lEyQzSFUrDUlNn1D7UMyUPSudK/HatgD8qOmVox9wMj2tNPNrrQ==", "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "dependencies": { + "@solid-primitives/trigger": "^1.2.3" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/trigger": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/trigger/-/trigger-1.2.3.tgz", + "integrity": "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.4.0" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/utils": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==", + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.6.12" } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -411,24 +274,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", - "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@vladfrangu/async_event_emitter": { "version": "2.4.7", "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", @@ -440,9 +285,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -463,9 +308,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -510,9 +355,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -589,10 +434,16 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -614,42 +465,6 @@ "dev": true, "license": "MIT" }, - "node_modules/discord-api-types": { - "version": "0.38.42", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz", - "integrity": "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ==", - "license": "MIT", - "workspaces": [ - "scripts/actions/documentation" - ] - }, - "node_modules/discord.js": { - "version": "14.25.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", - "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/builders": "^1.13.0", - "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.6.2", - "@discordjs/rest": "^2.6.0", - "@discordjs/util": "^1.2.0", - "@discordjs/ws": "^1.2.3", - "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.38.33", - "fast-deep-equal": "3.1.3", - "lodash.snakecase": "4.1.1", - "magic-bytes.js": "^1.10.0", - "tslib": "^2.6.3", - "undici": "6.21.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/discordjs/discord.js?sponsor" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -664,33 +479,32 @@ } }, "node_modules/eslint": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", - "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.24.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -702,7 +516,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -725,9 +539,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -742,9 +556,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -755,15 +569,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -773,9 +587,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -822,6 +636,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { @@ -883,18 +698,12 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", - "license": "ISC" - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -999,9 +808,9 @@ "license": "ISC" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -1032,6 +841,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1072,12 +887,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -1085,22 +894,10 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT" - }, - "node_modules/magic-bytes.js": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", - "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==", - "license": "MIT" - }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1237,6 +1034,27 @@ "node": ">=4" } }, + "node_modules/seroval": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.1.tgz", + "integrity": "sha512-OwrZRZAfhHww0WEnKHDY8OM0U/Qs8OTfIDWhUD4BLpNJUfXK4cGmjiagGze086m+mhI+V2nD0gfbHEnJjb9STA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.1.tgz", + "integrity": "sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1260,6 +1078,41 @@ "node": ">=8" } }, + "node_modules/solid-js": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.12.tgz", + "integrity": "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.5.0", + "seroval-plugins": "~1.5.0" + } + }, + "node_modules/stoat-api": { + "version": "0.8.9-4", + "resolved": "https://registry.npmjs.org/stoat-api/-/stoat-api-0.8.9-4.tgz", + "integrity": "sha512-6N4kfE1x+j/XYVaBuvYqzbWxjgqbxSarvNjMv8GEcBaqbkiyPzUwsCj12NJsNNp4uyZmsqoUYUj2jIq018uqiA==", + "license": "MIT", + "dependencies": { + "json-with-bigint": "^3.4.4" + } + }, + "node_modules/stoat.js": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/stoat.js/-/stoat.js-7.3.6.tgz", + "integrity": "sha512-q2+00tjuaGHVSBA5Sg8vBDp73k3yZtsfDyS8OUqYgHYT2uL8x5RZHsrViuloPwHHuKNldzQjib/VdQMISZIfOg==", + "license": "MIT", + "dependencies": { + "@solid-primitives/map": "^0.7.1", + "@solid-primitives/set": "^0.7.1", + "@vladfrangu/async_event_emitter": "^2.4.6", + "json-with-bigint": "^3.4.4", + "solid-js": "^1.9.6", + "stoat-api": "0.8.9-4", + "ulid": "^2.4.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1286,18 +1139,6 @@ "node": ">=8" } }, - "node_modules/ts-mixer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1311,21 +1152,15 @@ "node": ">= 0.8.0" } }, - "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "node_modules/ulid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz", + "integrity": "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==", "license": "MIT", - "engines": { - "node": ">=18.17" + "bin": { + "ulid": "bin/cli.js" } }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT" - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1362,27 +1197,6 @@ "node": ">=0.10.0" } }, - "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b988800..d8d95db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "cockbot", - "version": "1.0.0", + "version": "2.0.0", + "type": "module", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" @@ -16,9 +17,11 @@ }, "homepage": "https://github.com/CesiumCs/tornbot#readme", "description": "", + "engines": { + "node": ">=22.15.0" + }, "dependencies": { - "discord.js": "^14.25.1", - "fs": "^0.0.1-security" + "stoat.js": "^7.3.6" }, "devDependencies": { "@eslint/js": "^9.24.0",