diff --git a/src/commands/forceload.ts b/src/commands/forceload.ts index ef586f9..32c6a66 100644 --- a/src/commands/forceload.ts +++ b/src/commands/forceload.ts @@ -1,9 +1,10 @@ import { type CommandInteraction, SlashCommandBuilder, - type GuildMember + type GuildMember, + ActivityType } from 'discord.js' -import { checkMemberPermissions, insertGoob } from '../db' +import { checkMemberPermissions, execute, insertGoob } from '../db' module.exports = { data: new SlashCommandBuilder() @@ -41,5 +42,9 @@ module.exports = { } insertGoob(message) await interaction.reply({ content: 'Goober inserted', ephemeral: true }) + const length = await execute('SELECT count(*) as count from goob') + interaction.client.user?.setActivity(`${length[0].count as string} goobers`, { + type: ActivityType.Listening + }) } } diff --git a/src/commands/load.ts b/src/commands/load.ts index a133efa..3995e7f 100644 --- a/src/commands/load.ts +++ b/src/commands/load.ts @@ -1,10 +1,11 @@ import { type CommandInteraction, SlashCommandBuilder, - type Message + type Message, + ActivityType } from 'discord.js' import { owner_id } from '../config.json' -import db, { checkMemberPermissions, insertGoob } from '../db' +import db, { checkMemberPermissions, execute, insertGoob } from '../db' module.exports = { data: new SlashCommandBuilder() @@ -58,7 +59,6 @@ module.exports = { const filteredMessages = (messages as unknown as Message[]).map(e => e).filter((e, i) => permissionFilter[i] && !e.author.bot) - console.log(filteredMessages) // parses the messages // shenanigans to save goobs into the database loadedImages += filteredMessages.map(insertGoob).reduce((acc, v) => v + acc, 0) @@ -98,5 +98,9 @@ module.exports = { $last_message: reply.id } ) + const length = await execute('SELECT count(*) as count from goob') + interaction.client.user?.setActivity(`${length[0].count as string} goobers`, { + type: ActivityType.Listening + }) } } diff --git a/src/db/index.ts b/src/db/index.ts index 7b5fea2..4f1eaec 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,4 +1,4 @@ -import { type Message, type Client, type GuildMember } from 'discord.js' +import { type Message, type Client, type GuildMember, ActivityType } from 'discord.js' import { verbose } from 'sqlite3' const sqlite = verbose() const db = new sqlite.Database('goobers.db') @@ -56,21 +56,26 @@ export function insertGoob (message: Message): number { const embeds = message.embeds.filter((e) => e.image !== undefined || e.video !== undefined) - attachments.forEach(a => db.run('INSERT INTO goob (messageid, guild, channel, url) VALUES ($messageid, $guild, $channel, $url)', { - $messageid: message.id, - $guild: message.guildId, - $channel: message.channelId, - $url: a.url - })) + let promises = attachments.map(async a => + await execute('INSERT INTO goob (messageid, guild, channel, url) VALUES ($messageid, $guild, $channel, $url)', { + $messageid: message.id, + $guild: message.guildId, + $channel: message.channelId, + $url: a.url + }) + ) - embeds.forEach(e => db.run('INSERT INTO goob (messageid, guild, channel, url) VALUES ($messageid, $guild, $channel, $url)', { - $messageid: message.id, - $guild: message.guildId, - $channel: message.channelId, - $url: e.video !== undefined ? e.video?.url : e.image?.url - })) + promises = [...promises, ...embeds.map(async e => + await execute('INSERT INTO goob (messageid, guild, channel, url) VALUES ($messageid, $guild, $channel, $url)', { + $messageid: message.id, + $guild: message.guildId, + $channel: message.channelId, + $url: e.video !== undefined ? e.video?.url : e.image?.url + }) + )] if (attachments.size > 0 || embeds.length > 0) void message.react('📥') + void Promise.all(promises).catch(async () => await message.react('⁉️')) return attachments.size + embeds.length } @@ -87,4 +92,57 @@ export async function deleteGoob (targetimage: { guild: string, channel: string, if (message === undefined) return await message.react('🚫') } + +export async function loadPreviousGoob (client: Client): Promise { + const channels = await execute('SELECT * from tracked') as Array<{ guild: string, channel: string, last_message: string }> + + await Promise.all(channels.map(async c => { + const guild = await client.guilds.fetch(c.guild) + const channel = await guild.channels.fetch(c.channel) + if (channel === null) return + if (!channel.isTextBased()) return + + let messages = await channel?.messages + .fetch({ after: c.last_message }) + .catch(console.error) + let loaded = 0 + let loadedImages = 0 + let lastmessage + while (messages !== undefined && messages.size > 0) { + loaded += messages.size + const permissionFilter = await Promise.all((messages as unknown as Message[]).map(async e => { + let member = e.member + if (member === null) { + const member2 = await e.guild?.members.fetch(e.author.id) + if (member2 === undefined) return + member = member2 + } + return (await checkMemberPermissions(member)).create && e.reactions.resolve('🚫') === null + })) + + const filteredMessages = (messages as unknown as Message[]).map(e => e).filter((e, i) => (permissionFilter[i] ?? false) && !e.author.bot) + + // parses the messages + // shenanigans to save goobs into the database + loadedImages += filteredMessages.map(insertGoob).reduce((acc, v) => v + acc, 0) + + lastmessage = messages.first() as Message | undefined + if (lastmessage === undefined) { + console.error("Coudn't fetch message data") + break + } + client.user?.setActivity(`${loaded} messages with ${loadedImages} images`, { + type: ActivityType.Watching + }) + + messages = await channel?.messages + .fetch({ after: lastmessage.id }) + .catch(console.error) + } + if (lastmessage === undefined) return + await execute('UPDATE tracked SET last_message=$last_message WHERE guild=$guild AND channel=$channel', + { $channel: channel.id, $guild: channel.guildId, $last_message: lastmessage.id }) + })) +} + export default db diff --git a/src/index.ts b/src/index.ts index adf3f08..93e9653 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,36 +1,59 @@ -import { Client, Collection, Events, GatewayIntentBits } from 'discord.js' +import { ActivityType, Client, Collection, Events, GatewayIntentBits } from 'discord.js' import { discord_token } from './config.json' import path from 'path' import fs from 'fs' +import { checkMemberPermissions, execute, insertGoob, loadPreviousGoob } from './db' // Create a new client instance -const client = new Client({ intents: [GatewayIntentBits.Guilds] }) +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers + ] +}) client.commands = new Collection() -// When the client is ready, run this code (only once) -// We use 'c' for the event parameter to keep it separate from the already defined 'client' -client.once(Events.ClientReady, c => { +client.once(Events.ClientReady, async (c) => { console.log(`Ready! Logged in as ${c.user.tag}`) + await loadPreviousGoob(c) + console.log('Loaded') + const length = await execute('SELECT count(*) as count from goob') + client.user?.setActivity(`${length[0].count as string} goobers`, { + type: ActivityType.Listening + }) }) const commandsPath = path.join(__dirname, 'commands') -const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')) +const commandFiles = fs + .readdirSync(commandsPath) + .filter((file) => file.endsWith('.js')) const promises = [] as Array> +// Load Slash commands for (const file of commandFiles) { const filePath = path.join(commandsPath, file) - promises.push(import(filePath).then(({ default: command }) => { - if ('data' in command && 'execute' in command) { - client.commands.set(command.data.name, command) - } else { - console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`) - } - }).catch(e => { console.log(e) })) - // Set a new item in the Collection with the key as the command name and the value as the exported module + promises.push( + import(filePath) + .then(({ default: command }) => { + if ('data' in command && 'execute' in command) { + client.commands.set(command.data.name, command) + } else { + console.log( + `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.` + ) + } + }) + .catch((e) => { + console.log(e) + }) + ) } -client.on(Events.InteractionCreate, async interaction => { +// Handle slash commands +client.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isChatInputCommand()) return const command = interaction.client.commands.get(interaction.commandName) @@ -45,13 +68,56 @@ client.on(Events.InteractionCreate, async interaction => { } catch (error) { console.error(error) if (interaction.replied || interaction.deferred) { - await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true }) + await interaction.followUp({ + content: 'There was an error while executing this command!', + ephemeral: true + }) } else { - await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }) + await interaction.reply({ + content: 'There was an error while executing this command!', + ephemeral: true + }) } } }) -// Log in to Discord with your client's token +// Handle message sent in goober channel +client.on('messageCreate', async (e) => { + if (e.guild === null) return + if (e.member === null) return + if (!e.channel.isTextBased()) return + const channel = await execute( + 'SELECT * from tracked WHERE guild=$guild AND channel=$channel', + { $channel: e.channelId, $guild: e.guildId } + ) + if (!(await checkMemberPermissions(e.member)).create) return + if (channel.length === 0) return -void Promise.all(promises).then(async () => await client.login(discord_token)).catch(e => { console.log(e) }) + insertGoob(e) + const length = await execute('SELECT count(*) as count from goob') + client.user?.setActivity(`${length[0].count as string} goobers`, { + type: ActivityType.Listening + }) + await execute( + 'UPDATE tracked SET last_message=$last_message WHERE guild=$guild AND channel=$channel', + { $channel: e.channelId, $guild: e.guild, $last_message: e.id } + ) +}) + +// remove deleted goobers from databaser +client.on('messageDelete', async (e) => { + void execute('DELETE FROM goob WHERE messageid=$messageid', { + $messageid: e.id + }) + const length = await execute('SELECT count(*) as count from goob') + client.user?.setActivity(`${length[0].count as string} goobers`, { + type: ActivityType.Listening + }) +}) + +// Log in to Discord with your client's token +void Promise.all(promises) + .then(async () => await client.login(discord_token)) + .catch((e) => { + console.log(e) + })