Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@ Feedr strives for constant improvement, so here's what will be implemented
* Embed Links
* Attach Files
* Add Reactions

# Changelog
## 1.1.0
* Replies are no longer deferred
* Messages can now be sent in Announcement channels [1.0.3]
* Better checking for valid YouTube channel IDs
* Channels with no uploads will be tracked now

## 1.0.0
* Initial release
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": "videonotifier",
"module": "src/index.ts",
"type": "module",
"version": "1.0.0",
"version": "1.1.0",
"devDependencies": {
"@types/bun": "1.1.6"
},
Expand Down
54 changes: 31 additions & 23 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,14 @@ const commands: Record<string, Command> = {
const discordChannelId = interaction.options.get('updates_channel')?.value as string;
const guildId = interaction.guildId;

// Deferring the reply is not the best practice,
// but in case the network/database is slow, it's better to defer the reply
// so we don't get a timeout error
await interaction.deferReply();
// Check that the channel ID is in a valid format
if (youtubeChannelId.length != 24 || !youtubeChannelId.startsWith('UC')) {
await interaction.reply({
ephemeral: true,
content: 'Invalid YouTube channel ID format!',
});
return;
}

// DMs are currently not supported, so throw back an error
if (!guildId || interaction.channel?.isDMBased()) {
Expand All @@ -173,24 +177,28 @@ const commands: Record<string, Command> = {

// Check if the bot has the required permissions for the target channel
const targetChannel = await client.channels.fetch(discordChannelId);
if (targetChannel && targetChannel.type === ChannelType.GuildText) {
if (targetChannel && (targetChannel.type === ChannelType.GuildText || targetChannel.type === ChannelType.GuildAnnouncement)) {
const requiredPermissions = [
{ flag: PermissionFlagsBits.ViewChannel, name: 'View Channel' },
{ flag: PermissionFlagsBits.SendMessages, name: 'Send Messages' },
{ flag: PermissionFlagsBits.SendMessagesInThreads, name: 'Send Messages in Threads' },
{ flag: PermissionFlagsBits.EmbedLinks, name: 'Embed Links' },
{ flag: PermissionFlagsBits.AttachFiles, name: 'Attach Files' },
{ flag: PermissionFlagsBits.AddReactions, name: 'Add Reactions' }
];
const botPermissions = targetChannel.permissionsFor(client.user?.id as unknown as GuildMember);
if (
!botPermissions?.has(PermissionFlagsBits.ViewChannel) ||
!botPermissions?.has(PermissionFlagsBits.SendMessages) ||
!botPermissions?.has(PermissionFlagsBits.SendMessagesInThreads) ||
!botPermissions?.has(PermissionFlagsBits.EmbedLinks) ||
!botPermissions?.has(PermissionFlagsBits.AttachFiles) ||
!botPermissions?.has(PermissionFlagsBits.AddReactions)
) {
await interaction.followUp({
const missingPermissions = requiredPermissions
.filter(permission => !botPermissions?.has(permission.flag))
.map(permission => permission.name);
if (missingPermissions.length > 0) {
await interaction.reply({
ephemeral: true,
content: 'The bot does not have the required permissions for the target channel!',
content: `The bot does not have the required permissions for the target channel! Missing permissions: ${missingPermissions.join(', ')}`,
});
return;
}
} else {
await interaction.followUp({
await interaction.reply({
ephemeral: true,
content: 'The target channel is not a text channel!',
});
Expand All @@ -199,16 +207,16 @@ const commands: Record<string, Command> = {

// Check if the channel is valid
if (!await checkIfChannelIdIsValid(youtubeChannelId)) {
await interaction.followUp({
await interaction.reply({
ephemeral: true,
content: 'Invalid YouTube channel ID!',
content: 'That channel doesn\'t exist!',
});
return;
}

// Check if the channel is already being tracked in the guild
if (await checkIfGuildIsTrackingChannelAlready(youtubeChannelId, guildId)) {
await interaction.followUp({
await interaction.reply({
ephemeral: true,
content: 'This channel is already being tracked!',
});
Expand All @@ -218,7 +226,7 @@ const commands: Record<string, Command> = {
// Check if the channel is already being tracked globally
if (!await checkIfChannelIsAlreadyTracked(youtubeChannelId)) {
if (!await addNewChannelToTrack(youtubeChannelId)) {
await interaction.followUp({
await interaction.reply({
ephemeral: true,
content: 'An error occurred while trying to add the channel to track! This is a new channel being tracked globally, please report this error!',
});
Expand All @@ -229,15 +237,15 @@ const commands: Record<string, Command> = {
// Add the guild to the database
if (await addNewGuildToTrackChannel(guildId, youtubeChannelId, discordChannelId, interaction.options.get('role')?.value as string ?? null)) {
const channelIdInfo = await client.channels.fetch(discordChannelId);
if (channelIdInfo && channelIdInfo.type === ChannelType.GuildText) {
if (channelIdInfo && (channelIdInfo.type === ChannelType.GuildText || channelIdInfo.type === ChannelType.GuildAnnouncement)) {
const youtubeChannelInfo = await getChannelDetails(youtubeChannelId)

await interaction.followUp({
await interaction.reply({
ephemeral: true,
content: `Started tracking the channel ${youtubeChannelInfo?.channelName ?? youtubeChannelId} in ${channelIdInfo.name}!`,
});
} else {
await interaction.followUp({
await interaction.reply({
ephemeral: true,
content: 'The channel to send updates to is not a text channel! Please make sure to set a text channel!',
});
Expand Down
4 changes: 2 additions & 2 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function initTables(): Promise<boolean> {
const createYouTubeTable = `
CREATE TABLE IF NOT EXISTS youtube (
youtube_channel_id VARCHAR(255) NOT NULL PRIMARY KEY,
latest_video_id VARCHAR(255) NOT NULL UNIQUE
latest_video_id VARCHAR(255) UNIQUE
);
`;
const createDiscordTable = `
Expand Down Expand Up @@ -71,7 +71,7 @@ export async function addNewChannelToTrack(channelId: string) {
}

const data = await res.json();
const videoId = data.items[0].snippet.thumbnails.default.url.split('/')[4];
const videoId = data.items?.[0]?.snippet?.thumbnails?.default?.url?.split('/')[4] || null;

const query = `INSERT INTO youtube (youtube_channel_id, latest_video_id) VALUES (?, ?)`;
return new Promise<boolean>((resolve, reject) =>
Expand Down
5 changes: 3 additions & 2 deletions src/utils/checkIfChannelIdIsValid.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { env } from "../config"

export default async function checkIfChannelIdIsValid(channelId: string) {
const res = await fetch(`https://www.googleapis.com/youtube/v3/channels?part=snippet&id=${channelId}&key=${env.youtubeApiKey}`)
return res.ok
const res = await fetch(`https://www.googleapis.com/youtube/v3/channels?part=snippet&id=${channelId}&key=${env.youtubeApiKey}`);
const data = await res.json();
return data.items !== undefined && data.items.length > 0;
}
6 changes: 3 additions & 3 deletions src/utils/fetchLatestUploads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ export default async function fetchLatestUploads() {
}

await (channelObj as TextChannel).send({
content: guild.guild_ping_role && channelInfo ? `<@&${guild.guild_ping_role}> New video uploaded for ${channelInfo?.channelName}! https://www.youtube.com/watch?v=${videoId}` :
content: guild.guild_ping_role && channelInfo ? `<@&${guild.guild_ping_role}> New video uploaded for ${channelInfo?.channelName}! https://www.youtube.com/watch?v=${videoId}` :
guild.guild_ping_role ? `<@&${guild.guild_ping_role}> New video uploaded! https://www.youtube.com/watch?v=${videoId}` :
channelInfo ? `New video uploaded for ${channelInfo.channelName}! https://www.youtube.com/watch?v=${videoId}` :
`New video uploaded! https://www.youtube.com/watch?v=${videoId}`
channelInfo ? `New video uploaded for ${channelInfo.channelName}! https://www.youtube.com/watch?v=${videoId}` :
`New video uploaded! https://www.youtube.com/watch?v=${videoId}`
});
} catch (error) {
console.error("Error fetching or sending message to channel in fetchLatestUploads:", error);
Expand Down