From b8033dfa96bdaa43d5cf0b9de6b06fdce31447b7 Mon Sep 17 00:00:00 2001 From: GalvinPython <77013913+GalvinPython@users.noreply.github.com> Date: Sat, 7 Sep 2024 22:50:52 +0100 Subject: [PATCH] 1.1.0 --- README.md | 3 +- bot/commands.ts | 11 ++++-- bot/database.ts | 73 ++++++++++++++++++++++++++++--------- bot/events/guildAdd.ts | 21 ++++++++++- bot/events/messageCreate.ts | 14 +++++++ bot/log.ts | 6 +++ bot/types/database.d.ts | 9 +++++ site/index.html | 2 +- 8 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 bot/types/database.d.ts diff --git a/README.md b/README.md index e71ff52..a714813 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ Discord has a rate limit for publishing channels of 10 announcements per server Please do not use this in your server if you exceed this limit # New in 1.1.0 -The bot has been restructured - mainly because of the admin permissions and how volatile it could be. Whilst it worked, I wanted to ditch it and make it more secure. However, it is now slightly more complicated to setup, hence why there's a guide +The bot has been restructured - mainly because of the admin permissions and how volatile it could be. Whilst it worked, I wanted to ditch it and make it more secure. You now have more control over what gets published and when it joins a server, will automatically add all needed permissions to it. +*Not all features that were meant to be in 1.1.0 were added in it. That's for 1.1.1 as I had to ship it early* # Website The website for the bot is [here](https://autopublish.galvindev.me.uk)! diff --git a/bot/commands.ts b/bot/commands.ts index 8969715..5f59f6a 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -5,7 +5,7 @@ import client from '.'; import { ApplicationCommandOptionType, ChannelType, CommandInteractionOptionResolver, PermissionFlagsBits, type CommandInteraction } from 'discord.js'; import quickEmbed from './quickEmbed'; import type { Command } from './types/commands'; -import { addChannelToGuild, checkChannelIsPaused, getChannelIdsOfGuild, hasGuildPaused, removeChannelFromGuild } from './database'; +import { addChannelToGuild, checkChannelIsPaused, getChannelIdsOfGuild, hasGuildPaused, pauseGuild, removeChannelFromGuild, unpauseGuild } from './database'; const commands: Record = { ping: { @@ -150,7 +150,7 @@ const commands: Record = { const guildHasPaused = await hasGuildPaused(guildId); const channels = await interaction.guild.channels.fetch(); - const accessibleChannels = channels?.filter(channel => channel && channel.permissionsFor(interaction.client.user)?.has(PermissionFlagsBits.ViewChannel) && channel.type == ChannelType.GuildAnnouncement); + const accessibleChannels = channels?.filter(channel => channel && client.user && channel.permissionsFor(client.user)?.has(PermissionFlagsBits.ViewChannel) && channel.permissionsFor(client.user)?.has(PermissionFlagsBits.ManageMessages) && channel.type == ChannelType.GuildAnnouncement); const accessibleChannelsIds = accessibleChannels?.map(channel => channel?.id); const channelsCanPublishFrom: string[] = []; @@ -171,7 +171,7 @@ const commands: Record = { const canPublishEmbed = quickEmbed({ color: 'Green', - title: 'Channels the bot can publish from', + title: 'Channels the bot will publish from', description: channelsCanPublishFrom.length > 0 ? channelsCanPublishFrom.map(id => `<#${id}>`).join('\n') : 'No channels available' }, interaction); @@ -183,7 +183,8 @@ const commands: Record = { await interaction.reply({ ephemeral: true, - embeds: guildHasPaused ? [canPublishEmbed, barredEmbed, pausedEmbed] : [canPublishEmbed, barredEmbed] + embeds: guildHasPaused ? [canPublishEmbed, barredEmbed, pausedEmbed] : [canPublishEmbed, barredEmbed], + content: 'If there are any missing channels, please check that the bot has both the `View Channel` and `Manage Messages` permissions.' }).catch(console.error); }, }, @@ -321,12 +322,14 @@ const commands: Record = { await interaction.reply('Already paused in the entire server'); return; } + pauseGuild(interaction.guildId); await interaction.reply('Paused publishing in the entire server'); } else if (action === 'resume') { if (!await hasGuildPaused(interaction.guildId)) { await interaction.reply('Guild is not paused'); return; } + unpauseGuild(interaction.guildId); await interaction.reply('Resumed publishing in the entire server'); } } diff --git a/bot/database.ts b/bot/database.ts index 42fe796..44204ff 100644 --- a/bot/database.ts +++ b/bot/database.ts @@ -1,24 +1,30 @@ import { Database } from 'bun:sqlite'; import path from 'path'; +import type { DatabaseStructureChannels, DatabaseStructureGuilds } from './types/database'; const db: Database = new Database(path.join(__dirname, 'db.sqlite')); -interface DatabaseStructure { - channel_id: string; - guild_id: string; - guild_paused: boolean; -} +export let dbPausedChannels: Record = {}; +export let dbPausedGuilds: string[] = []; export async function initDatabase(): Promise { try { db.query(` - CREATE TABLE IF NOT EXISTS guilds ( + CREATE TABLE IF NOT EXISTS channels ( channel_id TEXT NOT NULL PRIMARY KEY, - guild_id TEXT NOT NULL, - guild_paused BOOLEAN DEFAULT FALSE - ); + guild_id TEXT NOT NULL + ) + `).run(); + + db.query(` + CREATE TABLE IF NOT EXISTS guilds ( + guild_id TEXT NOT NULL PRIMARY KEY, + guild_paused BOOLEAN NOT NULL DEFAULT FALSE + ) `).run(); console.log('Database initialized'); + await updatePausedList(); + setInterval(updatePausedList, 1000 * 60) return true; } catch (error) { console.error('Error initializing database:', error); @@ -27,9 +33,8 @@ export async function initDatabase(): Promise { } export async function getChannelIdsOfGuild(guildId: string): Promise { - const result = db.query('SELECT channel_id FROM guilds WHERE guild_id = $guildId').all({ $guildId: guildId }); - console.dir(result, { depth: null }); - return (result as DatabaseStructure[]).map((row: DatabaseStructure) => row.channel_id); + const result = db.query('SELECT channel_id FROM channels WHERE guild_id = $guildId').all({ $guildId: guildId }); + return (result as DatabaseStructureChannels[]).map((row: DatabaseStructureChannels) => row.channel_id); } export async function hasGuildPaused(guildId: string): Promise { @@ -38,22 +43,56 @@ export async function hasGuildPaused(guildId: string): Promise { } export async function pauseGuild(guildId: string): Promise { - db.query('INSERT INTO guilds (channel_id, guild_id, guild_paused) VALUES (0, $guildId, $guildPaused)').run({ $guildId: guildId, $guildPaused: true }); + db.query('UPDATE guilds SET guild_paused = TRUE WHERE guild_id = $guildId').run({ $guildId: guildId }); } export async function unpauseGuild(guildId: string): Promise { - db.query('DELETE FROM guilds WHERE guild_id = $guildId AND guildPaused = TRUE AND channel_id = 0').run({ $guildId: guildId }); + db.query('UPDATE guilds SET guild_paused = FALSE WHERE guild_id = $guildId').run({ $guildId: guildId }); } export async function addChannelToGuild(guildId: string, channelId: string): Promise { - db.query('INSERT INTO guilds (channel_id, guild_id, guild_paused) VALUES ($channelId, $guildId, FALSE)').run({ $channelId: channelId, $guildId: guildId }); + db.query('INSERT INTO channels (channel_id, guild_id) VALUES ($channelId, $guildId)').run({ $channelId: channelId, $guildId: guildId }); } export async function removeChannelFromGuild(guildId: string, channelId: string): Promise { - db.query('DELETE FROM guilds WHERE guild_id = $guildId AND channel_id = $channelId').run({ $guildId: guildId, $channelId: channelId }); + db.query('DELETE FROM channels WHERE guild_id = $guildId AND channel_id = $channelId').run({ $guildId: guildId, $channelId: channelId }); } export async function checkChannelIsPaused(guildId: string, channelId: string): Promise { - const result = db.query('SELECT guild_paused FROM guilds WHERE guild_id = $guildId AND channel_id = $channelId').get({ $guildId: guildId, $channelId: channelId }); + const result = db.query('SELECT guild_paused FROM guilds WHERE guild_id = $guildId').get({ $guildId: guildId, $channelId: channelId }); return !(!result) } + +//#region Guild Management +export async function addNewGuild(guildId: string): Promise { + db.query('INSERT INTO guilds (guild_id) VALUES ($guildId').run({ $guildId: guildId }); +} +export async function removeGuild(guildId: string): Promise { + db.query('DELETE FROM guilds WHERE guild_id = $guildId').run({ $guildId: guildId }); + db.query('DELETE FROM channels WHERE guild_id = $guildId').run({ $guildId: guildId }); +} +//#endregion + +//#region Update +// Update dbPausedChannels and dbPausedGuilds every minute +export async function updatePausedList() { + dbPausedChannels = {}; + dbPausedGuilds = []; + + const resultGuilds = db.query('SELECT * FROM guilds').all(); + (resultGuilds as DatabaseStructureGuilds[]).forEach((row: DatabaseStructureGuilds) => { + if (row.guild_paused) { + dbPausedGuilds.push(row.guild_id); + } + }); + + const resultChannels = db.query('SELECT * FROM channels').all(); + (resultChannels as DatabaseStructureChannels[]).forEach((row: DatabaseStructureChannels) => { + if (!dbPausedChannels[row.guild_id]) dbPausedChannels[row.guild_id] = []; + dbPausedChannels[row.guild_id].push(row.channel_id); + }); + + console.dir(dbPausedChannels, { depth: null }); + console.dir(dbPausedGuilds, { depth: null }); +} +//#endregion \ No newline at end of file diff --git a/bot/events/guildAdd.ts b/bot/events/guildAdd.ts index 28df1bb..207f9fb 100644 --- a/bot/events/guildAdd.ts +++ b/bot/events/guildAdd.ts @@ -1,5 +1,6 @@ -import { Events, EmbedBuilder, TextChannel } from 'discord.js'; +import { Events, EmbedBuilder, TextChannel, PermissionFlagsBits, ChannelType } from 'discord.js'; import client from '../index'; +import { addNewGuild } from '../database'; client.on(Events.GuildCreate, async guild => { try { @@ -44,4 +45,22 @@ client.on(Events.GuildCreate, async guild => { } catch (error) { console.error(`Could not fetch the owner or send a message: ${error}`); } + + // Try adding the bot permissions to announcement channels + try { + const announcementChannels = guild.channels.cache.filter(channel => channel.type === ChannelType.GuildAnnouncement); + announcementChannels.forEach(async channel => { + if (client.user) { + await channel.permissionOverwrites.create(client.user.id, { + ManageChannels: true, + ManageMessages: true, + SendMessages: true, + ViewChannel: true, + }) + } + }); + addNewGuild(guild.id); + } catch (error) { + console.error(`Could not add permissions to announcement channels: ${error}`); + } }); diff --git a/bot/events/messageCreate.ts b/bot/events/messageCreate.ts index 628ed26..259e373 100644 --- a/bot/events/messageCreate.ts +++ b/bot/events/messageCreate.ts @@ -1,10 +1,24 @@ import { Message, ChannelType } from 'discord.js'; import client from '../index'; import log from '../log'; +import { dbPausedChannels, dbPausedGuilds } from '../database'; // Run this event whenever a message has been sent client.on('messageCreate', async (message: Message) => { if (message.channel.type == ChannelType.GuildAnnouncement) { + const guildId = message.guildId + const channelId = message.channelId + + if (!guildId || !channelId) return; + + if (dbPausedGuilds.includes(guildId) || dbPausedChannels[guildId]?.includes(channelId)) { + log(false, `Message in paused channel: + ${channelId} [${message.channel.name}] + (${guildId}, ${message.guild?.name})` + ) + return + } + try { await message.crosspost() log(true, `Published message in: diff --git a/bot/log.ts b/bot/log.ts index c2d3960..1db4174 100644 --- a/bot/log.ts +++ b/bot/log.ts @@ -3,6 +3,12 @@ import client from '.'; const targetChannel = process.env.DISCORD_LOGGING_CHANNEL; +/** + * Logs to the logging channel + * @param success Display error if false, success if true + * @param msg The message to be logged + * @returns void + */ export default async function (success: boolean, msg: string) { if (!targetChannel || targetChannel === undefined || targetChannel === '') return; diff --git a/bot/types/database.d.ts b/bot/types/database.d.ts new file mode 100644 index 0000000..be11463 --- /dev/null +++ b/bot/types/database.d.ts @@ -0,0 +1,9 @@ +export interface DatabaseStructureChannels { + channel_id: string; + guild_id: string; +} + +export interface DatabaseStructureGuilds { + guild_id: string; + guild_paused: boolean; +} \ No newline at end of file diff --git a/site/index.html b/site/index.html index 7bd9e06..1be7ec6 100644 --- a/site/index.html +++ b/site/index.html @@ -149,7 +149,7 @@

Links

Twitter
- Current version (1.0.3) released 30th June 2024 + Current version (1.1.0) released 7th September 2024