Skip to content

Commit

Permalink
1.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
GalvinPython committed Sep 8, 2024
1 parent b8033df commit d6fe8ee
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 214 deletions.
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
# Discord Auto Publisher Bot

📢 The simplest Discord Bot ever that automatically publishes announcements for you. No setup required; it just runs

[Invite it here](https://discord.com/oauth2/authorize?client_id=1241739031252045935&permissions=8&scope=bot+applications.commands)
[Invite it here](https://discord.com/oauth2/authorize?client_id=1241739031252045935&permissions=268446736&integration_type=0&scope=bot+applications.commands)

# Why use this?
* Automatically publishes announcements for you, in case you forget; or a bot sends a message; or you just want to take the easier route
* No setup required. It'll automatically publish messages if they're sent in announcement channels
* No message delay either. It just listens for messages rather than searching for the latest message in annoucement channel

- Automatically publishes announcements for you, in case you forget; or a bot sends a message; or you just want to take the easier route
- No setup required. It'll automatically publish messages if they're sent in announcement channels
- No message delay either. It just listens for messages rather than searching for the latest message in annoucement channel

# Instructions
1) [Invite the bot to a server of your choice, with all the permissions](https://discord.com/oauth2/authorize?client_id=1241739031252045935&permissions=8&scope=bot+applications.commands)
2) That's it! Once the bot has been invited, it'll listen out for any messages and will publish them automatically

1. [Invite the bot to a server of your choice, with all the permissions](https://discord.com/oauth2/authorize?client_id=1241739031252045935&permissions=268446736&integration_type=0&scope=bot+applications.commands)
2. That's it! Once the bot has been invited, it'll listen out for any messages and will publish them automatically

# Required Permissions

- Manage Channels
- Manage Messages
- Manage Roles
- Send Messages
- View Channels

# Rate Limits

Discord has a rate limit for publishing channels of 10 announcements per server **per hour**.
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. 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*
_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)!

<!-- # Support
[Support Discord Server](https://discord.gg/<REDACTED_FOR_NOW>) -->

# User Integration

User Integration is only there so you can use the commands, which are for utility/informational purposes only. You **cannot** use the User Integration to publish messages from other servers, because there is no command to do that

# Dev Instructions

To install dependencies:

```bash
Expand Down
70 changes: 58 additions & 12 deletions bot/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { heapStats } from 'bun:jsc';
import client from '.';
import { ApplicationCommandOptionType, ChannelType, CommandInteractionOptionResolver, PermissionFlagsBits, type CommandInteraction } from 'discord.js';
import quickEmbed from './quickEmbed';
import quickEmbed from './utils/quickEmbed';
import type { Command } from './types/commands';
import { addChannelToGuild, checkChannelIsPaused, getChannelIdsOfGuild, hasGuildPaused, pauseGuild, removeChannelFromGuild, unpauseGuild } from './database';

Expand Down Expand Up @@ -150,13 +150,18 @@ const commands: Record<string, Command> = {
const guildHasPaused = await hasGuildPaused(guildId);

const channels = await interaction.guild.channels.fetch();
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 accessibleChannels = channels?.filter(channel => channel &&
client.user &&
channel.permissionsFor(client.user)?.has(PermissionFlagsBits.ViewChannel) &&
channel.permissionsFor(client.user)?.has(PermissionFlagsBits.ManageMessages) &&
channel.permissionsFor(client.user)?.has(PermissionFlagsBits.SendMessages) &&
channel.type == ChannelType.GuildAnnouncement
).map(channel => channel?.id);

const channelsCanPublishFrom: string[] = [];
const channelsBarredFromPublishing: string[] = await getChannelIdsOfGuild(guildId)

accessibleChannelsIds?.forEach(channelId => {
accessibleChannels?.forEach(channelId => {
if (!channelId) return;
if (!channelsBarredFromPublishing.includes(channelId)) {
channelsCanPublishFrom.push(channelId);
Expand Down Expand Up @@ -302,39 +307,80 @@ const commands: Record<string, Command> = {
}
if (subcommand === 'pause') {
if (await checkChannelIsPaused(interaction.guildId, targetChannel?.id)) {
await interaction.reply('Already paused in this channel');
await interaction.reply({
ephemeral: true,
content: 'Already paused in this channel'
});
return;
}
await addChannelToGuild(interaction.guildId, targetChannel?.id);
await interaction.reply(`Paused publishing in ${targetChannel?.name}`);
await interaction.reply({
ephemeral: true,
content: `Paused publishing in ${targetChannel?.name}`
});
} else if (subcommand === 'resume') {
if (!await checkChannelIsPaused(interaction.guildId, targetChannel?.id)) {
await interaction.reply('Channel is not paused');
await interaction.reply({
ephemeral: true,
content: 'Channel is not paused'
});
return;
}
await removeChannelFromGuild(interaction.guildId, targetChannel?.id);
await interaction.reply(`Resumed publishing in ${targetChannel?.name}`);
await interaction.reply({
ephemeral: true,
content: `Resumed publishing in ${targetChannel?.name}`
});
}
} else if (subcommand === 'server') {
const action = (interaction.options as CommandInteractionOptionResolver).getString('action');
if (action === 'pause') {
if (await hasGuildPaused(interaction.guildId)) {
await interaction.reply('Already paused in the entire server');
await interaction.reply({
ephemeral: true,
content: 'Already paused in the entire server'
});
return;
}
pauseGuild(interaction.guildId);
await interaction.reply('Paused publishing in the entire server');
await interaction.reply({
ephemeral: true,
content: 'Paused publishing in the entire server'
});
} else if (action === 'resume') {
if (!await hasGuildPaused(interaction.guildId)) {
await interaction.reply('Guild is not paused');
await interaction.reply({
ephemeral: true,
content: 'Guild is not paused'
});
return;
}
unpauseGuild(interaction.guildId);
await interaction.reply('Resumed publishing in the entire server');
await interaction.reply({
ephemeral: true,
content: 'Resumed publishing in the entire server'
});
}
}
},
},
support: {
data: {
options: [],
name: 'support',
description: 'Get support for the bot!',
integration_types: [0, 1],
contexts: [0, 1, 2],
},
execute: async (interaction: CommandInteraction) => {
await interaction
.reply({
ephemeral: true,
content: 'https://discord.gg/AppBeYXVNt',
})
.catch(console.error);
},
}
};

// Convert commands to a Map
Expand Down
3 changes: 0 additions & 3 deletions bot/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,5 @@ export async function updatePausedList() {
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
8 changes: 8 additions & 0 deletions bot/events/channelCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ChannelType, Events } from "discord.js";
import client from "..";
import addPermissionsToChannel from "../utils/addPermissionsToChannel";

client.on(Events.ChannelCreate, async channel => {
if (channel.type != ChannelType.GuildAnnouncement) return;
await addPermissionsToChannel(channel);
});
30 changes: 3 additions & 27 deletions bot/events/guildAdd.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import { Events, EmbedBuilder, TextChannel, PermissionFlagsBits, ChannelType } from 'discord.js';
import { Events, EmbedBuilder, TextChannel, ChannelType } from 'discord.js';
import client from '../index';
import { addNewGuild } from '../database';
import addPermissionsToChannel from '../utils/addPermissionsToChannel';

client.on(Events.GuildCreate, async guild => {
try {
// Fetch the owner of the guild
const owner = await guild.fetchOwner();

// Create an embed with the guild's information
const embedOwner = new EmbedBuilder()
.setTitle(`Hi! Thanks for adding me to your server: **${guild.name}**.`)
.setDescription('Hello! Please make sure that I have admin permissions enabled to function correctly.')
.setThumbnail(guild.iconURL())
.addFields(
{ name: 'Guild ID', value: guild.id, inline: true }
)
.setColor('#00FF00')
.setTimestamp();

// Send a message to the owner
await owner.send({
embeds: [embedOwner]
});
console.log(`Sent a welcome message to the owner of the guild: ${guild.name}`);

// Send a message to the logging channel
const embedLoggingChannel = new EmbedBuilder()
.setTitle("New Guild Added!")
Expand All @@ -41,7 +25,6 @@ client.on(Events.GuildCreate, async guild => {

const channel = await client.channels.fetch(loggingChannelId) as TextChannel;
await channel?.send({ embeds: [embedLoggingChannel] });

} catch (error) {
console.error(`Could not fetch the owner or send a message: ${error}`);
}
Expand All @@ -50,14 +33,7 @@ client.on(Events.GuildCreate, async guild => {
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,
})
}
addPermissionsToChannel(channel);
});
addNewGuild(guild.id);
} catch (error) {
Expand Down
27 changes: 27 additions & 0 deletions bot/events/guildDelete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Events, EmbedBuilder, TextChannel } from 'discord.js';
import client from '../index';
import { removeGuild } from '../database';

client.on(Events.GuildDelete, async guild => {
try {
// Send a message to the logging channel
const embedLoggingChannel = new EmbedBuilder()
.setTitle("Left Guild :<")
.setColor('Blurple')
.setDescription(`Left guild: **${guild.name}**`)
.setThumbnail(guild.iconURL())
.addFields({ name: 'Guild ID', value: guild.id, inline: true })

const loggingChannelId = process.env.DISCORD_LOGGING_CHANNEL;
if (!loggingChannelId) {
throw new Error('DISCORD_LOGGING_CHANNEL environment variable is not defined.');
}

const channel = await client.channels.fetch(loggingChannelId) as TextChannel;
await channel?.send({ embeds: [embedLoggingChannel] });
removeGuild(guild.id);

} catch (error) {
console.error(`Error when leaving server: ${error}`);
}
});
4 changes: 2 additions & 2 deletions bot/events/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Message, ChannelType } from 'discord.js';
import client from '../index';
import log from '../log';
import log from '../utils/log';
import { dbPausedChannels, dbPausedGuilds } from '../database';

// Run this event whenever a message has been sent
Expand All @@ -18,7 +18,7 @@ client.on('messageCreate', async (message: Message) => {
)
return
}

try {
await message.crosspost()
log(true, `Published message in:
Expand Down
12 changes: 6 additions & 6 deletions bot/types/commands.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApplicationCommandOptionType, CommandInteraction } from "discord.js";

export interface Command {
data: {
options: Option[];
options: Options[];
name: string;
description: string;
integration_types: number[];
Expand All @@ -11,28 +11,28 @@ export interface Command {
execute: (interaction: CommandInteraction) => Promise<void>;
}

export interface Option {
export interface Options {
type: ApplicationCommandOptionType
name: string
description: string
options: Option2[]
options: Options2[]
}

export interface Option2 {
export interface Options2 {
type: ApplicationCommandOptionType
name: string
description: string
required?: boolean
choices?: Choice[]
options?: Option3[]
options?: Options3[]
}

export interface Choice {
name: string
value: string
}

export interface Option3 {
export interface Options3 {
type: ApplicationCommandOptionType
name: string
description: string
Expand Down
21 changes: 21 additions & 0 deletions bot/utils/addPermissionsToChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { NewsChannel } from "discord.js";
import client from "..";
import log from "./log";

export default async function addPermissionsToChannel(targetChannel: NewsChannel): Promise<boolean> {
try {
if (client.user) {
await targetChannel.permissionOverwrites.create(client.user.id, {
ManageChannels: true,
ManageMessages: true,
SendMessages: true,
ViewChannel: true,
})
}
return true;
} catch (error) {
console.error(`Could not add permissions to channel: ${error}`);
log(false, `Could not add permissions to channel: ${error}`);
return false;
}
}
2 changes: 1 addition & 1 deletion bot/log.ts → bot/utils/log.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EmbedBuilder, TextChannel } from 'discord.js';
import client from '.';
import client from '..';

const targetChannel = process.env.DISCORD_LOGGING_CHANNEL;

Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "autopublish",
"module": "bot/index.ts",
"type": "module",
"version": "1.1.0",
"version": "1.1.1",
"scripts": {
"start": "bun ."
},
Expand Down
Loading

0 comments on commit d6fe8ee

Please sign in to comment.