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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ DISCORD_DEV_TOKEN='YOUR_DISCORD_DEV_TOKEN'

# YouTube API Key
YOUTUBE_API_KEY='YOUR_YOUTUBE_API_KEY'
YOUTUBE_INNERTUBE_PROXY_URL='YOUR_OPTIONAL_YOUTUBE_INNERTUBE_PROXY_URL'

# Twitch
TWITCH_CLIENT_ID='YOUR_TWITCH_CLIENT_ID'
Expand Down
6 changes: 2 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# Moving back to npm for package management rather than bun
# because wow dependabot is so broken for it 😭
# TODO: Labels on package updates after project restructure

version: 2
updates:
- package-ecosystem: "npm"
- package-ecosystem: "bun"
directory: "/"
schedule:
interval: "weekly"
target-branch: "dev"

- package-ecosystem: "npm"
- package-ecosystem: "bun"
directory: "/web"
schedule:
interval: "weekly"
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ target/
*.sql
dbtests.ts
perftesting.ts
drizzle/
drizzle/
fetchTest.ts
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,26 @@ These guidelines ensure predictable behavior and simplify error handling across
> [!NOTE]
> WIP update!

### Bot
### Fixes

- Fixed the double notification bug

### Changes

- Moved to Postgres as our database engine

### Features

- Improved flow of `/track` command
- Autocomplete for YouTube
- Filter by videos, shorts and streams for YouTube!
- `/tracked` is now improved and is an interactive embed!
- Can now use search/autocomplete for `/track` for both YouTube and Twitch

### API
### Known Issues

### Site
- Twitch channel username doesn't show up in `/track`
- Unable to subscribe to updates via the bot

## V1

Expand All @@ -115,7 +124,7 @@ These guidelines ensure predictable behavior and simplify error handling across
- Added a new command! `/tracked` ([#50](https://github.com/GalvinPython/feedr/issues/50))
- See all the tracked channels in your server
- The channel you ran the command in will appear first as there is no option to only see the current channel for now
- Locale improvments ([#43](https://github.com/GalvinPython/feedr/issues/43))
- Locale improvements ([#43](https://github.com/GalvinPython/feedr/issues/43))

### 1.3.0

Expand Down
10 changes: 7 additions & 3 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"pg": "^8.15.6",
},
"devDependencies": {
"@types/bun": "1.2.10",
"@types/bun": "1.2.21",
"@types/pg": "^8.11.14",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
Expand Down Expand Up @@ -131,7 +131,7 @@

"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],

"@types/bun": ["@types/bun@1.2.10", "", { "dependencies": { "bun-types": "1.2.10" } }, "sha512-eilv6WFM3M0c9ztJt7/g80BDusK98z/FrFwseZgT4bXCq2vPhXD4z8R3oddmAn+R/Nmz9vBn4kweJKmGTZj+lg=="],
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],

"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],

Expand All @@ -141,6 +141,8 @@

"@types/pg": ["@types/pg@8.11.14", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^4.0.1" } }, "sha512-qyD11E5R3u0eJmd1lB0WnWKXJGA7s015nyARWljfz5DcX83TKAIlY+QrmvzQTsbIe+hkiFtkyL2gHC6qwF6Fbg=="],

"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],

"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],

"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.11.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.11.0", "@typescript-eslint/type-utils": "8.11.0", "@typescript-eslint/utils": "8.11.0", "@typescript-eslint/visitor-keys": "8.11.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA=="],
Expand Down Expand Up @@ -199,7 +201,7 @@

"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],

"bun-types": ["bun-types@1.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-b5ITZMnVdf3m1gMvJHG+gIfeJHiQPJak0f7925Hxu6ZN5VKA8AGy4GZ4lM+Xkn6jtWxg5S3ldWvfmXdvnkp3GQ=="],
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],

"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],

Expand Down Expand Up @@ -227,6 +229,8 @@

"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],

"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],

"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],

"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"version": "2.0.0-dev",
"devDependencies": {
"@types/bun": "1.2.10",
"@types/bun": "1.2.21",
"@types/pg": "^8.11.14",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
Expand Down
107 changes: 57 additions & 50 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ActionRowBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ApplicationIntegrationType,
AutocompleteInteraction,
ButtonBuilder,
ButtonStyle,
Expand All @@ -12,6 +13,7 @@ import {
ComponentType,
EmbedBuilder,
GuildMember,
InteractionContextType,
MessageFlags,
type ApplicationCommandOptionData,
type CacheType,
Expand All @@ -31,6 +33,8 @@ import search from "./utils/youtube/search";
import {
checkIfGuildIsTrackingUserAlready,
discordAddGuildTrackingUser,
discordAddNewGuild,
discordCheckIfDmChannelExists,
discordGetAllTrackedInGuild,
discordRemoveGuildTrackingChannel,
} from "./db/discord";
Expand All @@ -54,8 +58,8 @@ interface Command {
name: string;
description: string;
options?: ApplicationCommandOptionData[];
integration_types?: number[];
contexts?: number[];
integration_types?: ApplicationIntegrationType[];
contexts?: InteractionContextType[];
type?: ApplicationCommandType;
};
execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
Expand All @@ -64,6 +68,8 @@ interface Command {
) => Promise<any>;
}

// Context 2: Interaction can be used within Group DMs and DMs other than the app's bot user
// /track, /tracked and /untracked can't be used in these contexts
const commands: Record<string, Command> = {
ping: {
data: {
Expand All @@ -76,7 +82,7 @@ const commands: Record<string, Command> = {
execute: async (interaction: CommandInteraction) => {
await interaction
.reply({
ephemeral: false,
flags: MessageFlags.Ephemeral,
content: `Ping: ${interaction.client.ws.ping}ms`,
})
.catch(console.error);
Expand Down Expand Up @@ -134,7 +140,7 @@ const commands: Record<string, Command> = {
execute: async (interaction: CommandInteraction) => {
await interaction
.reply({
ephemeral: false,
flags: MessageFlags.Ephemeral,
content: `Uptime: ${(
performance.now() /
(86400 * 1000)
Expand All @@ -149,7 +155,7 @@ const commands: Record<string, Command> = {
name: "hmm",
description: "What does this command do?",
integration_types: [0, 1],
contexts: [0, 1],
contexts: [0, 1, 2],
},
execute: async (interaction: CommandInteraction) => {
await interaction.reply({
Expand All @@ -173,7 +179,7 @@ const commands: Record<string, Command> = {
Bun.gc(false);
await interaction
.reply({
ephemeral: false,
flags: MessageFlags.Ephemeral,
content: [
`Heap size: ${(heap.heapSize / 1024 / 1024).toFixed(2)} MB / ${(
heap.heapCapacity /
Expand Down Expand Up @@ -266,9 +272,11 @@ const commands: Record<string, Command> = {
description:
"Track a channel to get notified when they upload a video!",
integration_types: [0, 1],
contexts: [0, 1, 2],
contexts: [0, 1],
},
execute: async (interaction: CommandInteraction) => {
const isDm = !interaction.inGuild();

// Get the YouTube Channel ID
const targetPlatform = (
interaction as ChatInputCommandInteraction
Expand All @@ -280,7 +288,7 @@ const commands: Record<string, Command> = {
const discordChannelId =
(interaction.options.get("updates_channel")?.value as string) ??
interaction.channelId;
const guildId = interaction.guildId;
const guildId = isDm ? discordChannelId : interaction.guildId;

// Log the autocomplete value
console.log(`Autocomplete value: ${platformUserId}`);
Expand Down Expand Up @@ -312,33 +320,26 @@ const commands: Record<string, Command> = {
return;
}

// TODO: Enable DMs :)
const isDm = interaction.channel?.isDMBased();

if (!guildId || isDm || isDm === undefined) {
await interaction.reply({
flags: MessageFlags.Ephemeral,
content:
"This command is not supported in DMs currently!\nNot a DM? Then the bot failed to get the guild info",
});
console.log(interaction.channelId);

return;
}
if (isDm) console.log("DM");

// TODO: Embed
// Check the permissions of the user
if (
!interaction.memberPermissions?.has(
PermissionFlagsBits.ManageChannels,
)
) {
await interaction.reply({
flags: MessageFlags.Ephemeral,
content:
"You do not have the permission to manage channels!",
});
if (!isDm) {
if (
!interaction.memberPermissions?.has(
PermissionFlagsBits.ManageChannels,
)
) {
await interaction.reply({
flags: MessageFlags.Ephemeral,
content:
"You do not have the permission to manage channels!",
});

return;
return;
}
}

// TODO: Embed
Expand Down Expand Up @@ -393,6 +394,8 @@ const commands: Record<string, Command> = {

return;
}
} else if (isDm) {
// DM channels don't need permission checks
} else {
await interaction.reply({
flags: MessageFlags.Ephemeral,
Expand All @@ -402,6 +405,17 @@ const commands: Record<string, Command> = {
return;
}

// Before attempting to add the subscription, if it's a DM, check if it's already in the database. If not add it
if (isDm) {
const data = (
await discordCheckIfDmChannelExists(discordChannelId)
).data;

if (!data.length) {
await discordAddNewGuild(discordChannelId, true);
}
}

switch (targetPlatform) {
case "youtube": {
const contentType = interaction.options.get("content_type")
Expand Down Expand Up @@ -567,7 +581,7 @@ const commands: Record<string, Command> = {

await interaction.reply({
flags: MessageFlags.Ephemeral,
content: `Started tracking the channel ${youtubeChannelInfo?.channelName ?? platformUserId} in ${targetChannel.name}!`,
content: `Started tracking the channel ${youtubeChannelInfo?.channelName ?? platformUserId} in <#${targetChannel?.id}>!`,
});
} else {
await interaction.reply({
Expand Down Expand Up @@ -694,7 +708,7 @@ const commands: Record<string, Command> = {
) {
await interaction.reply({
flags: MessageFlags.Ephemeral,
content: `Started tracking the streamer ${platformUserId} (${platformUserId}) in ${targetChannel.name}!`,
content: `Started tracking the streamer ${platformUserId} (${platformUserId}) in <#${targetChannel?.id}>!`,
});
} else {
await interaction.reply({
Expand Down Expand Up @@ -815,24 +829,15 @@ const commands: Record<string, Command> = {
contexts: [0, 1],
},
execute: async (interaction: CommandInteraction) => {
const isDm = !interaction.inGuild();

// Get the YouTube Channel ID
const platformUserId = interaction.options.get("user_id")
?.value as string;
const guildId = interaction.guildId;

// DMs are currently not supported, so throw back an error
if (!guildId || interaction.channel?.isDMBased()) {
await interaction.reply({
flags: MessageFlags.Ephemeral,
content:
"This command is not supported in DMs currently!\nNot a DM? Then an error has occurred :(",
});

return;
}

// Check the permissions of the user
if (
!isDm &&
!interaction.memberPermissions?.has(
PermissionFlagsBits.ManageChannels,
)
Expand Down Expand Up @@ -865,7 +870,7 @@ const commands: Record<string, Command> = {
},
autoComplete: async (interaction: AutocompleteInteraction) => {
const trackedChannels = await discordGetAllTrackedInGuild(
interaction.guildId as string,
interaction.guildId ?? (interaction.channelId as string),
);

console.dir(
Expand Down Expand Up @@ -912,14 +917,16 @@ const commands: Record<string, Command> = {
contexts: [0, 1],
},
execute: async (interaction: CommandInteraction) => {
const guildId = interaction.guildId;
const channelId = interaction.channelId;
let guildId = interaction.guildId;

if (!guildId || !channelId) {
const isDm = !interaction.inGuild();

if (isDm) guildId = interaction.channelId;

if (!guildId) {
await interaction.reply({
flags: MessageFlags.Ephemeral,
content:
"You are likely in a DM, this command is not supported in DMs!",
content: "An error occurred! Please report",
});

return;
Expand Down
Loading