-
Notifications
You must be signed in to change notification settings - Fork 1
New: major refactor, general cleanup #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
dadaede
getting rid of the 'app' class
cbackas 18207a7
fix the calls to get settings manager so it like builds now
cbackas bc887ec
rename SettingsManager to Settings
cbackas 760d318
more public static functions
cbackas eb3f9bf
use a central type-safe getEnv function
cbackas 1fe5619
fixup env parsing stuff
cbackas cc69dc1
move cron jobs into its own file
cbackas eaf1f52
add typecheck task and fix resulting errors
cbackas 8c7737a
break more things out of app.ts
cbackas c1a6ccc
stop using deprecated setDMPermission()
cbackas 97bc70b
add typecheck to CI
cbackas a047cc5
allow scripts in the typecheck job
cbackas 84aeb6c
fix fix typecheck job
cbackas c0bd64b
fix fix fix typecheck job
cbackas 22365f7
add handy script to grab upcoming shows from sonarr to paste into
cbackas 683ab08
remove unused deno.jsonc options
cbackas 98ada6c
Merge branch 'main' into refactor
cbackas 8c0e10a
add explicit permissions to gh actions
cbackas df6637e
fix spelling of executeAutoComplete
cbackas eb477ca
add console log back
cbackas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| const SONARR_URL = Deno.env.get("SONARR_URL") || "http://localhost:8989" | ||
| const API_KEY = Deno.env.get("SONARR_API_KEY") | ||
|
|
||
| interface SonarrCalendarItem { | ||
| seriesId: number | ||
| title: string | ||
| seasonNumber: number | ||
| episodeNumber: number | ||
| airDateUtc: string | ||
| series: { | ||
| title: string | ||
| imdbId: string | ||
| tvdbId: number | ||
| } | ||
| } | ||
|
|
||
| async function getUpcomingShows() { | ||
| if (!API_KEY) throw new Error("SONARR_API_KEY environment variable not set") | ||
|
|
||
| const now = new Date() | ||
| const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000) | ||
|
|
||
| const url = new URL(`${SONARR_URL}/api/v3/calendar`) | ||
| url.searchParams.set("start", now.toISOString()) | ||
| url.searchParams.set("end", nextWeek.toISOString()) | ||
| url.searchParams.set("includeSeries", "true") | ||
|
|
||
| const response = await fetch(url.toString(), { | ||
| headers: { "X-Api-Key": API_KEY }, | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error( | ||
| `Sonarr API error: ${response.status} - ${await response.text()}`, | ||
| ) | ||
| } | ||
|
|
||
| const data: SonarrCalendarItem[] = await response.json() | ||
|
|
||
| return data.map((item) => ({ | ||
| title: item.series.title, | ||
| imdbId: item.series.imdbId, | ||
| airDate: item.airDateUtc, | ||
| season: item.seasonNumber, | ||
| episode: item.episodeNumber, | ||
| })) | ||
| } | ||
|
|
||
| const shows = (await getUpcomingShows()).map((show) => show.imdbId) | ||
| for (let i = 0; i < shows.length; i += 10) { | ||
| const chunk = shows.slice(i, i + 10) | ||
| console.log(chunk.join(",")) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,144 +1,54 @@ | ||
| import "jsr:@std/dotenv/load" | ||
| import process from "node:process" | ||
| import { ChannelType, Client, Events, GatewayIntentBits } from "npm:discord.js" | ||
| import { Client, ClientUser, Events, GatewayIntentBits } from "npm:discord.js" | ||
| import { CommandManager } from "lib/commandManager.ts" | ||
| import { | ||
| checkForAiringEpisodes, | ||
| pruneUnsubscribedShows, | ||
| removeAllSubscriptions, | ||
| } from "lib/shows.ts" | ||
| import { checkForAiringEpisodes, pruneUnsubscribedShows } from "lib/shows.ts" | ||
| import { sendAiringMessages } from "lib/episodeNotifier.ts" | ||
| import { type Settings, SettingsManager } from "lib/settingsManager.ts" | ||
| import { sendMorningSummary } from "lib/morningSummary.ts" | ||
| import { Settings } from "lib/settingsManager.ts" | ||
| import { | ||
| setRandomShowActivity, | ||
| setTVDBLoadingActivity, | ||
| } from "lib/discordActivities.ts" | ||
| import { getEnv } from "lib/env.ts" | ||
| import { scheduleCronJobs } from "./cron.ts" | ||
| import assert from "node:assert" | ||
| import { handleChannelDelete, handleThreadDelete } from "./handlers.ts" | ||
|
|
||
| /** | ||
| * The main bot application | ||
| */ | ||
| export class App { | ||
| private readonly client: Client | ||
| private readonly commands: CommandManager | ||
| private readonly settings: SettingsManager | ||
|
|
||
| private readonly token: string | ||
| private readonly clientId: string | ||
| private readonly guildId: string | ||
| const token = getEnv("DISCORD_TOKEN") | ||
| const clientId = getEnv("DISCORD_CLIENT_ID") | ||
| const guildId = getEnv("DISCORD_GUILD_ID") | ||
|
|
||
| constructor() { | ||
| if (process.env.DISCORD_TOKEN === undefined) { | ||
| throw new Error("DISCORD_TOKEN is not defined") | ||
| } | ||
| if (process.env.DISCORD_CLIENT_ID === undefined) { | ||
| throw new Error("DISCORD_CLIENT_ID is not defined") | ||
| } | ||
| if (process.env.DISCORD_GUILD_ID === undefined) { | ||
| throw new Error("DISCORD_GUILD_ID is not defined") | ||
| } | ||
| if (process.env.TZ === undefined) throw new Error("TZ is not defined") | ||
| await Settings.refresh() | ||
|
|
||
| this.token = process.env.DISCORD_TOKEN | ||
| this.clientId = process.env.DISCORD_CLIENT_ID | ||
| this.guildId = process.env.DISCORD_GUILD_ID | ||
| const commandManager = new CommandManager() | ||
| await commandManager.registerCommands(clientId, token, guildId) | ||
|
|
||
| this.client = new Client({ intents: [GatewayIntentBits.Guilds] }) | ||
| this.commands = new CommandManager( | ||
| this, | ||
| this.clientId, | ||
| this.token, | ||
| this.guildId, | ||
| ) | ||
| this.settings = new SettingsManager() | ||
| const discordClient = new Client({ intents: [GatewayIntentBits.Guilds] }) | ||
|
|
||
| void this.init() | ||
| discordClient.on(Events.ClientReady, async (client) => { | ||
| if (client.user == null) { | ||
| throw new Error("Fatal: Client user is null") | ||
| } | ||
|
|
||
| /** | ||
| * Async init function for app | ||
| */ | ||
| private readonly init = async (): Promise<void> => { | ||
| await this.settings.refresh() | ||
| await this.commands.registerCommands() | ||
| this.startBot() | ||
| } | ||
|
|
||
| /** | ||
| * Start the bot and register listeners | ||
| */ | ||
| private readonly startBot = (): void => { | ||
| this.client.on(Events.ClientReady, async () => { | ||
| const { user } = this.client | ||
| if (user == null) throw new Error("User is null") | ||
| console.log(`Logged in as ${user.tag}!`) | ||
|
|
||
| // run initial scheduled activities | ||
| setTVDBLoadingActivity(user) | ||
| await pruneUnsubscribedShows() | ||
| if (process.env.UPDATE_SHOWS !== "false") await checkForAiringEpisodes() | ||
| void sendAiringMessages(this) | ||
| void setRandomShowActivity(user) | ||
|
|
||
| Deno.cron("Announcements", { minute: { every: 10, start: 8 } }, () => { | ||
| void sendAiringMessages(this) | ||
| void setRandomShowActivity(user) | ||
| }) | ||
|
|
||
| Deno.cron("Fetch Episode Data", { hour: { every: 4 } }, async () => { | ||
| setTVDBLoadingActivity(user) | ||
| await pruneUnsubscribedShows() | ||
| await checkForAiringEpisodes() | ||
| }) | ||
|
|
||
| Deno.cron("Morning Summary", { hour: 8, minute: 0 }, async () => { | ||
| const settings = this.getSettings() | ||
| if (settings == null) throw new Error("Settings not found") | ||
|
|
||
| await sendMorningSummary(settings, this.client) | ||
| }) | ||
|
|
||
| const healthcheckUrl = process.env.HEALTHCHECK_URL | ||
| if (healthcheckUrl != null) { | ||
| Deno.cron("Healthcheck", { minute: { every: 1 } }, async () => { | ||
| await fetch(healthcheckUrl) | ||
| console.debug("[Healthcheck] Healthcheck ping sent") | ||
| }) | ||
| } | ||
| }) | ||
|
|
||
| this.client.on(Events.InteractionCreate, this.commands.interactionHandler) | ||
|
|
||
| /** | ||
| * When a thread (forum post) is deleted, remove all subscriptions for that post | ||
| */ | ||
| this.client.on(Events.ThreadDelete, async (thread) => { | ||
| await removeAllSubscriptions(thread.id, "channelId") | ||
| await pruneUnsubscribedShows() | ||
| }) | ||
|
|
||
| /** | ||
| * When a forum is deleted, remove all subscriptions for post in that forum | ||
| */ | ||
| this.client.on(Events.ChannelDelete, async (channel) => { | ||
| if (channel.type === ChannelType.GuildForum) { | ||
| await removeAllSubscriptions(channel.id, "forumId") | ||
| await pruneUnsubscribedShows() | ||
| } | ||
|
|
||
| if (channel.type === ChannelType.GuildText) { | ||
| await removeAllSubscriptions(channel.id, "channelId") | ||
| await pruneUnsubscribedShows() | ||
| await this.settings.removeGlobalDestination(channel.id) | ||
| } | ||
| }) | ||
|
|
||
| void this.client.login(this.token) | ||
| } | ||
|
|
||
| public getClient = (): Client<boolean> => this.client | ||
| public getSettings = (): Settings | undefined => this.settings.fetch() | ||
| public getSettingsManager = (): SettingsManager => this.settings | ||
| } // make an instance of the application class | ||
|
|
||
| ;(() => new App())() | ||
| console.info(`Logged in as ${client.user.tag}!`) | ||
|
|
||
| // run initial scheduled activities | ||
| setTVDBLoadingActivity() | ||
| await pruneUnsubscribedShows() | ||
| if (getEnv("UPDATE_SHOWS")) await checkForAiringEpisodes() | ||
| void sendAiringMessages() | ||
| void setRandomShowActivity() | ||
|
|
||
| scheduleCronJobs() | ||
| }) | ||
|
|
||
| discordClient.on(Events.InteractionCreate, commandManager.interactionHandler) | ||
| discordClient.on(Events.ThreadDelete, handleThreadDelete) | ||
| discordClient.on(Events.ChannelDelete, handleChannelDelete) | ||
|
|
||
| // start the bot | ||
| await discordClient.login(token) | ||
|
|
||
| export const getClient = (): Client<boolean> => discordClient | ||
| export const getClientUser = (): ClientUser => { | ||
| assert(discordClient.user != null, "Client user is null") | ||
| return discordClient.user | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.