diff --git a/.env.example b/.env.example index 15bf4c4..9513926 100644 --- a/.env.example +++ b/.env.example @@ -14,9 +14,6 @@ COOKIE= # Integer(number), define delay time to fetch to prevent rate limit DELAY= -# Boolean, true will enable annoying "new interval starts" discord message -INTERVAL_LOGGING= - # Boolean, true will enable annoying "Error fetching, Might be rate limited or server is down" discord message ERROR_FETCHING_NOTIFICATION= diff --git a/eslint.config.mjs b/eslint.config.mjs index 7cdda02..c5ec012 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,8 @@ import globals from "globals"; -import tseslint from "typescript-eslint"; import tsPlugin from "@typescript-eslint/eslint-plugin"; import tsParser from "@typescript-eslint/parser"; import unusedImports from "eslint-plugin-unused-imports"; -export default tseslint.config( +export default [ { files: ["src/**/*.ts","tests/**/*.ts"], languageOptions: { @@ -31,4 +30,7 @@ export default tseslint.config( "@typescript-eslint/no-explicit-any": "off" } }, -) \ No newline at end of file + { + ignores: ["*","!src","!tests"] + } +] \ No newline at end of file diff --git a/src/env/env.ts b/src/env/env.ts index 8194234..75487e1 100644 --- a/src/env/env.ts +++ b/src/env/env.ts @@ -19,7 +19,6 @@ const env = cleanEnv(process.env, { ADMIN_USER_ID: str(), COOKIE: str(), DELAY: num(), - INTERVAL_LOGGING: bool({ default: false }), ERROR_FETCHING_NOTIFICATION: bool({ default: false }), AUTO_DETERMINE_YEAR_AND_SEMESTER: bool(), }) diff --git a/src/scraper/determineYearAndSemester.ts b/src/scraper/determineYearAndSemester.ts index b1c60dc..b417030 100644 --- a/src/scraper/determineYearAndSemester.ts +++ b/src/scraper/determineYearAndSemester.ts @@ -4,7 +4,7 @@ export async function determineYearAndSemester($: cheerio.Root): Promise { const currentYearAndSemester = $('h2').first().text() const split = /(\d+)\/(\d+)/.exec(currentYearAndSemester) if (split == undefined) { - console.log('error cannot parse year and semester: ', split) + console.trace('error cannot parse year and semester: ', split) return } const [_beforeSplit, currentYear, currentSemester] = split diff --git a/src/server.ts b/src/server.ts index 7405cd1..ca58afd 100644 --- a/src/server.ts +++ b/src/server.ts @@ -19,7 +19,8 @@ export const client = new Client({ }) export let adminDM: DMChannel -let intervalId: NodeJS.Timeout +export let intervalId: MutableWrapper = + new MutableWrapper(undefined) let commands: DiscordCommandHandler client.on('interactionCreate', async (interaction: Interaction) => { @@ -44,7 +45,7 @@ client.on('interactionCreate', async (interaction: Interaction) => { await interaction.reply('command not found') } } catch (e) { - console.error('error occured', e) + console.trace('error occured', e) if (e instanceof Error) { adminDM.send('error: ' + (e as Error).stack) } else { @@ -60,7 +61,7 @@ client.on('ready', async () => { adminDM.send('server is up!') if (isSome(await updateHandler())) { - intervalId = setInterval(updateHandler, env.DELAY * 1000) + intervalId.value = setInterval(updateHandler, env.DELAY * 1000) } }) @@ -75,7 +76,3 @@ export async function start() { console.log('found commands', Object.keys(commands)) client.login(env.DISCORD_TOKEN) } - -export function getIntervalId() { - return intervalId -} diff --git a/src/utils/fetchAndCatch.ts b/src/utils/fetchAndCatch.ts index ab14e57..54f574a 100644 --- a/src/utils/fetchAndCatch.ts +++ b/src/utils/fetchAndCatch.ts @@ -32,7 +32,6 @@ export default async function fetchAndCatch( if (hasEncounteredError.value) { return option.none } - console.log(await response.text()) hasEncounteredError.value = true await errorFetchingNotify(NotifyMessage.FetchingError) return option.none diff --git a/src/utils/throwErrorToAdmin.ts b/src/utils/throwErrorToAdmin.ts index 5d151b3..fe2e8f4 100644 --- a/src/utils/throwErrorToAdmin.ts +++ b/src/utils/throwErrorToAdmin.ts @@ -1,6 +1,6 @@ -import { adminDM, getIntervalId } from '../server' +import { adminDM, intervalId } from '../server' export async function throwErrorToAdmin(msg: string) { await adminDM.send(msg) - clearInterval(getIntervalId()) + clearInterval(intervalId.value) } diff --git a/src/utils/updateHandler.ts b/src/utils/updateHandler.ts index d41e91d..38a8df0 100644 --- a/src/utils/updateHandler.ts +++ b/src/utils/updateHandler.ts @@ -1,23 +1,18 @@ -import { TextChannel } from 'discord.js' +import { DiscordAPIError, TextChannel } from 'discord.js' import db from '../database/database' -import env from '../env/env' import { updateAll } from '../scraper/updateAll' -import { client } from '../server' -import formatDateToBangkok from './formatDateToBangkok' +import { adminDM, client } from '../server' import { none, Option, some } from 'fp-ts/lib/Option' /** * @description update assignments and send message to all notification channels * */ export default async function updateHandler(): Promise> { - if (env.INTERVAL_LOGGING) { - console.log('new interval started at ' + formatDateToBangkok(new Date())) - } let messages: Array = [] try { messages = await updateAll() } catch (e) { - console.log(e) + console.trace(e) return none } if (messages.length == 0) { @@ -26,11 +21,17 @@ export default async function updateHandler(): Promise> { const channels = await db.getAllChannels() for (const message of messages) { for await (const NotificationChannel of channels) { - const discordChannel = (await client.channels.fetch( - NotificationChannel.channelID - )) as TextChannel - // adminDM.send(message); - await discordChannel.send(message) + try { + const discordChannel = (await client.channels.fetch( + NotificationChannel.channelID + )) as TextChannel + await discordChannel.send(message) + } catch (err) { + console.trace(err) + if (!(err instanceof DiscordAPIError)) { + adminDM.send(JSON.stringify(err)) + } + } } } return some(false) diff --git a/tests/unit/updateHandler.unit.test.ts b/tests/unit/updateHandler.unit.test.ts new file mode 100644 index 0000000..b89a46b --- /dev/null +++ b/tests/unit/updateHandler.unit.test.ts @@ -0,0 +1,93 @@ +import db, { NotificationChannel } from '@/database/database' +import { updateAll } from '@/scraper/updateAll' +import { adminDM, client } from '@/server' +import updateHandler from '@/utils/updateHandler' +import { DiscordAPIError } from 'discord.js' + +jest.mock('@/env/env', () => {}) +jest.mock('@/server', () => { + return { + client: { + channels: { + fetch: jest.fn(), + }, + }, + adminDM: { + send: jest.fn(), + }, + } +}) +jest.mock('@/server') +jest.mock('@/scraper/updateAll') +jest.mock('@/database/database', () => { + return { + getAllChannels: jest.fn(), + } +}) + +describe('catch discord error', () => { + let channels: NotificationChannel[] + let error: any + let messages: string[] + + beforeAll(() => { + ;(updateAll as jest.Mock).mockImplementation(() => { + return messages + }) + ;(db.getAllChannels as jest.Mock).mockImplementation(() => { + return channels + }) + ;(client.channels.fetch as jest.Mock).mockImplementation(() => { + throw error + }) + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('discord error', async () => { + channels = [ + { + guildID: '123', + channelID: '456', + }, + ] + + error = new DiscordAPIError( + { + code: 50001, + errors: {}, + message: '', + }, + 50001, + 403, + 'GET', + '', + {} + ) + + messages = ['g'] + + await updateHandler() + + expect(adminDM.send).toHaveBeenCalledTimes(0) + }) + + it('non-discord error', async () => { + channels = [ + { + guildID: '123', + channelID: '456', + }, + ] + + error = 'gg' + + messages = ['g'] + + await updateHandler() + + expect(adminDM.send).toHaveBeenCalledTimes(1) + }) +})