Skip to content

Commit 29c70c5

Browse files
authored
Merge pull request #167 from GalvinPython/feat/dm-notifications
2 parents 0d2e8ee + b7c7ffe commit 29c70c5

File tree

12 files changed

+120
-66
lines changed

12 files changed

+120
-66
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DISCORD_DEV_TOKEN='YOUR_DISCORD_DEV_TOKEN'
88

99
# YouTube API Key
1010
YOUTUBE_API_KEY='YOUR_YOUTUBE_API_KEY'
11+
YOUTUBE_INNERTUBE_PROXY_URL='YOUR_OPTIONAL_YOUTUBE_INNERTUBE_PROXY_URL'
1112

1213
# Twitch
1314
TWITCH_CLIENT_ID='YOUR_TWITCH_CLIENT_ID'

.github/dependabot.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
# Moving back to npm for package management rather than bun
2-
# because wow dependabot is so broken for it 😭
31
# TODO: Labels on package updates after project restructure
42

53
version: 2
64
updates:
7-
- package-ecosystem: "npm"
5+
- package-ecosystem: "bun"
86
directory: "/"
97
schedule:
108
interval: "weekly"
119
target-branch: "dev"
1210

13-
- package-ecosystem: "npm"
11+
- package-ecosystem: "bun"
1412
directory: "/web"
1513
schedule:
1614
interval: "weekly"

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ target/
1111
*.sql
1212
dbtests.ts
1313
perftesting.ts
14-
drizzle/
14+
drizzle/
15+
fetchTest.ts

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,26 @@ These guidelines ensure predictable behavior and simplify error handling across
9696
> [!NOTE]
9797
> WIP update!
9898
99-
### Bot
99+
### Fixes
100100

101101
- Fixed the double notification bug
102+
103+
### Changes
104+
102105
- Moved to Postgres as our database engine
106+
107+
### Features
108+
103109
- Improved flow of `/track` command
104110
- Autocomplete for YouTube
105111
- Filter by videos, shorts and streams for YouTube!
112+
- `/tracked` is now improved and is an interactive embed!
113+
- Can now use search/autocomplete for `/track` for both YouTube and Twitch
106114

107-
### API
115+
### Known Issues
108116

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

111120
## V1
112121

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

120129
### 1.3.0
121130

bun.lock

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"pg": "^8.15.6",
1212
},
1313
"devDependencies": {
14-
"@types/bun": "1.2.10",
14+
"@types/bun": "1.2.21",
1515
"@types/pg": "^8.11.14",
1616
"@typescript-eslint/eslint-plugin": "8.11.0",
1717
"@typescript-eslint/parser": "8.11.0",
@@ -131,7 +131,7 @@
131131

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

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

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

@@ -141,6 +141,8 @@
141141

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

144+
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
145+
144146
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
145147

146148
"@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=="],
@@ -199,7 +201,7 @@
199201

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

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

204206
"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=="],
205207

@@ -227,6 +229,8 @@
227229

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

232+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
233+
230234
"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=="],
231235

232236
"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=="],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"type": "module",
55
"version": "2.0.0-dev",
66
"devDependencies": {
7-
"@types/bun": "1.2.10",
7+
"@types/bun": "1.2.21",
88
"@types/pg": "^8.11.14",
99
"@typescript-eslint/eslint-plugin": "8.11.0",
1010
"@typescript-eslint/parser": "8.11.0",

src/commands.ts

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ActionRowBuilder,
55
ApplicationCommandOptionType,
66
ApplicationCommandType,
7+
ApplicationIntegrationType,
78
AutocompleteInteraction,
89
ButtonBuilder,
910
ButtonStyle,
@@ -12,6 +13,7 @@ import {
1213
ComponentType,
1314
EmbedBuilder,
1415
GuildMember,
16+
InteractionContextType,
1517
MessageFlags,
1618
type ApplicationCommandOptionData,
1719
type CacheType,
@@ -31,6 +33,8 @@ import search from "./utils/youtube/search";
3133
import {
3234
checkIfGuildIsTrackingUserAlready,
3335
discordAddGuildTrackingUser,
36+
discordAddNewGuild,
37+
discordCheckIfDmChannelExists,
3438
discordGetAllTrackedInGuild,
3539
discordRemoveGuildTrackingChannel,
3640
} from "./db/discord";
@@ -54,8 +58,8 @@ interface Command {
5458
name: string;
5559
description: string;
5660
options?: ApplicationCommandOptionData[];
57-
integration_types?: number[];
58-
contexts?: number[];
61+
integration_types?: ApplicationIntegrationType[];
62+
contexts?: InteractionContextType[];
5963
type?: ApplicationCommandType;
6064
};
6165
execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
@@ -64,6 +68,8 @@ interface Command {
6468
) => Promise<any>;
6569
}
6670

71+
// Context 2: Interaction can be used within Group DMs and DMs other than the app's bot user
72+
// /track, /tracked and /untracked can't be used in these contexts
6773
const commands: Record<string, Command> = {
6874
ping: {
6975
data: {
@@ -76,7 +82,7 @@ const commands: Record<string, Command> = {
7682
execute: async (interaction: CommandInteraction) => {
7783
await interaction
7884
.reply({
79-
ephemeral: false,
85+
flags: MessageFlags.Ephemeral,
8086
content: `Ping: ${interaction.client.ws.ping}ms`,
8187
})
8288
.catch(console.error);
@@ -134,7 +140,7 @@ const commands: Record<string, Command> = {
134140
execute: async (interaction: CommandInteraction) => {
135141
await interaction
136142
.reply({
137-
ephemeral: false,
143+
flags: MessageFlags.Ephemeral,
138144
content: `Uptime: ${(
139145
performance.now() /
140146
(86400 * 1000)
@@ -149,7 +155,7 @@ const commands: Record<string, Command> = {
149155
name: "hmm",
150156
description: "What does this command do?",
151157
integration_types: [0, 1],
152-
contexts: [0, 1],
158+
contexts: [0, 1, 2],
153159
},
154160
execute: async (interaction: CommandInteraction) => {
155161
await interaction.reply({
@@ -173,7 +179,7 @@ const commands: Record<string, Command> = {
173179
Bun.gc(false);
174180
await interaction
175181
.reply({
176-
ephemeral: false,
182+
flags: MessageFlags.Ephemeral,
177183
content: [
178184
`Heap size: ${(heap.heapSize / 1024 / 1024).toFixed(2)} MB / ${(
179185
heap.heapCapacity /
@@ -266,9 +272,11 @@ const commands: Record<string, Command> = {
266272
description:
267273
"Track a channel to get notified when they upload a video!",
268274
integration_types: [0, 1],
269-
contexts: [0, 1, 2],
275+
contexts: [0, 1],
270276
},
271277
execute: async (interaction: CommandInteraction) => {
278+
const isDm = !interaction.inGuild();
279+
272280
// Get the YouTube Channel ID
273281
const targetPlatform = (
274282
interaction as ChatInputCommandInteraction
@@ -280,7 +288,7 @@ const commands: Record<string, Command> = {
280288
const discordChannelId =
281289
(interaction.options.get("updates_channel")?.value as string) ??
282290
interaction.channelId;
283-
const guildId = interaction.guildId;
291+
const guildId = isDm ? discordChannelId : interaction.guildId;
284292

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

315-
// TODO: Enable DMs :)
316-
const isDm = interaction.channel?.isDMBased();
317-
318-
if (!guildId || isDm || isDm === undefined) {
319-
await interaction.reply({
320-
flags: MessageFlags.Ephemeral,
321-
content:
322-
"This command is not supported in DMs currently!\nNot a DM? Then the bot failed to get the guild info",
323-
});
323+
console.log(interaction.channelId);
324324

325-
return;
326-
}
325+
if (isDm) console.log("DM");
327326

328327
// TODO: Embed
329328
// Check the permissions of the user
330-
if (
331-
!interaction.memberPermissions?.has(
332-
PermissionFlagsBits.ManageChannels,
333-
)
334-
) {
335-
await interaction.reply({
336-
flags: MessageFlags.Ephemeral,
337-
content:
338-
"You do not have the permission to manage channels!",
339-
});
329+
if (!isDm) {
330+
if (
331+
!interaction.memberPermissions?.has(
332+
PermissionFlagsBits.ManageChannels,
333+
)
334+
) {
335+
await interaction.reply({
336+
flags: MessageFlags.Ephemeral,
337+
content:
338+
"You do not have the permission to manage channels!",
339+
});
340340

341-
return;
341+
return;
342+
}
342343
}
343344

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

394395
return;
395396
}
397+
} else if (isDm) {
398+
// DM channels don't need permission checks
396399
} else {
397400
await interaction.reply({
398401
flags: MessageFlags.Ephemeral,
@@ -402,6 +405,17 @@ const commands: Record<string, Command> = {
402405
return;
403406
}
404407

408+
// Before attempting to add the subscription, if it's a DM, check if it's already in the database. If not add it
409+
if (isDm) {
410+
const data = (
411+
await discordCheckIfDmChannelExists(discordChannelId)
412+
).data;
413+
414+
if (!data.length) {
415+
await discordAddNewGuild(discordChannelId, true);
416+
}
417+
}
418+
405419
switch (targetPlatform) {
406420
case "youtube": {
407421
const contentType = interaction.options.get("content_type")
@@ -567,7 +581,7 @@ const commands: Record<string, Command> = {
567581

568582
await interaction.reply({
569583
flags: MessageFlags.Ephemeral,
570-
content: `Started tracking the channel ${youtubeChannelInfo?.channelName ?? platformUserId} in ${targetChannel.name}!`,
584+
content: `Started tracking the channel ${youtubeChannelInfo?.channelName ?? platformUserId} in <#${targetChannel?.id}>!`,
571585
});
572586
} else {
573587
await interaction.reply({
@@ -694,7 +708,7 @@ const commands: Record<string, Command> = {
694708
) {
695709
await interaction.reply({
696710
flags: MessageFlags.Ephemeral,
697-
content: `Started tracking the streamer ${platformUserId} (${platformUserId}) in ${targetChannel.name}!`,
711+
content: `Started tracking the streamer ${platformUserId} (${platformUserId}) in <#${targetChannel?.id}>!`,
698712
});
699713
} else {
700714
await interaction.reply({
@@ -815,24 +829,15 @@ const commands: Record<string, Command> = {
815829
contexts: [0, 1],
816830
},
817831
execute: async (interaction: CommandInteraction) => {
832+
const isDm = !interaction.inGuild();
833+
818834
// Get the YouTube Channel ID
819835
const platformUserId = interaction.options.get("user_id")
820836
?.value as string;
821-
const guildId = interaction.guildId;
822-
823-
// DMs are currently not supported, so throw back an error
824-
if (!guildId || interaction.channel?.isDMBased()) {
825-
await interaction.reply({
826-
flags: MessageFlags.Ephemeral,
827-
content:
828-
"This command is not supported in DMs currently!\nNot a DM? Then an error has occurred :(",
829-
});
830-
831-
return;
832-
}
833837

834838
// Check the permissions of the user
835839
if (
840+
!isDm &&
836841
!interaction.memberPermissions?.has(
837842
PermissionFlagsBits.ManageChannels,
838843
)
@@ -865,7 +870,7 @@ const commands: Record<string, Command> = {
865870
},
866871
autoComplete: async (interaction: AutocompleteInteraction) => {
867872
const trackedChannels = await discordGetAllTrackedInGuild(
868-
interaction.guildId as string,
873+
interaction.guildId ?? (interaction.channelId as string),
869874
);
870875

871876
console.dir(
@@ -912,14 +917,16 @@ const commands: Record<string, Command> = {
912917
contexts: [0, 1],
913918
},
914919
execute: async (interaction: CommandInteraction) => {
915-
const guildId = interaction.guildId;
916-
const channelId = interaction.channelId;
920+
let guildId = interaction.guildId;
917921

918-
if (!guildId || !channelId) {
922+
const isDm = !interaction.inGuild();
923+
924+
if (isDm) guildId = interaction.channelId;
925+
926+
if (!guildId) {
919927
await interaction.reply({
920928
flags: MessageFlags.Ephemeral,
921-
content:
922-
"You are likely in a DM, this command is not supported in DMs!",
929+
content: "An error occurred! Please report",
923930
});
924931

925932
return;

0 commit comments

Comments
 (0)