diff --git a/CODEOWNERS b/CODEOWNERS index d35d8c8..282be8e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -4,3 +4,4 @@ /bots/asakatsu-bot/ @nakaterm /bots/auto-moderator/ @chelproc @aster-void /bots/gsc-report/ @na-trium-144 +/bots/joji-bot/ @tknkaa diff --git a/bots/joji-bot/.env.sample b/bots/joji-bot/.env.sample new file mode 100644 index 0000000..7c3876a --- /dev/null +++ b/bots/joji-bot/.env.sample @@ -0,0 +1,2 @@ +WEBHOOK_URL=https://discord.com/api/webhooks/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +LAST_ID_FILE=~/run/last_video_id.txt diff --git a/bots/joji-bot/.gitignore b/bots/joji-bot/.gitignore new file mode 100644 index 0000000..df6f70b --- /dev/null +++ b/bots/joji-bot/.gitignore @@ -0,0 +1,36 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +last_video_id.txt diff --git a/bots/joji-bot/README.md b/bots/joji-bot/README.md new file mode 100644 index 0000000..b851bd5 --- /dev/null +++ b/bots/joji-bot/README.md @@ -0,0 +1,15 @@ +# joji-bot + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.5. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bots/joji-bot/bun.lock b/bots/joji-bot/bun.lock new file mode 100644 index 0000000..8ec2ace --- /dev/null +++ b/bots/joji-bot/bun.lock @@ -0,0 +1,39 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "joji-bot", + "dependencies": { + "rss-parser": "^3.13.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + + "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + + "entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], + + "rss-parser": ["rss-parser@3.13.0", "", { "dependencies": { "entities": "^2.0.3", "xml2js": "^0.5.0" } }, "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w=="], + + "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + + "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + } +} diff --git a/bots/joji-bot/index.ts b/bots/joji-bot/index.ts new file mode 100644 index 0000000..dd1a876 --- /dev/null +++ b/bots/joji-bot/index.ts @@ -0,0 +1,53 @@ +import Parser from 'rss-parser'; + +const WEBHOOK_URL = process.env.WEBHOOK_URL; +const CHANNEL_ID = 'UCGxzWLH1_1ABKKfSiy2WIAw'; +const RSS_URL = `https://www.youtube.com/feeds/videos.xml?channel_id=${CHANNEL_ID}`; +const LAST_ID_FILE = process.env.LAST_ID_FILE || 'last_video_id.txt'; + +async function main() { + const parser = new Parser(); + const response = await fetch(RSS_URL); + const feedText = await response.text(); + const feed = await parser.parseString(feedText); + if (!feed.items || feed.items.length === 0) { + console.log('No entries found in RSS feed'); + return; + } + + const latestVideo = feed.items[0]; + const latestId = latestVideo.id?.split(':').pop(); + const videoUrl = latestVideo.link; + const videoTitle = latestVideo.title; + console.log(`Latest video: ${videoTitle} (ID: ${latestId})`); + + let lastId = ''; + try { + lastId = await Bun.file(LAST_ID_FILE).text(); + } catch {} + if (lastId === latestId) { + console.log(`No new video (last ID: ${lastId})`); + return; + } + + const message = { + content: `**${videoTitle}**\nNew video uploaded!\n${videoUrl}` + }; + const res = await fetch(WEBHOOK_URL!, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(message) + }); + console.log(`Discord response: ${res.status}`); + + if (res.status === 204) { + await Bun.write(LAST_ID_FILE, latestId!); + console.log('Notification sent successfully!'); + } else { + const errorText = await res.text(); + console.log(`Failed to send notification: ${errorText}`); + } +} + +main() + diff --git a/bots/joji-bot/package.json b/bots/joji-bot/package.json new file mode 100644 index 0000000..18b6c09 --- /dev/null +++ b/bots/joji-bot/package.json @@ -0,0 +1,15 @@ +{ + "name": "joji-bot", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "rss-parser": "^3.13.0" + } +} diff --git a/bots/joji-bot/run.sh b/bots/joji-bot/run.sh new file mode 100755 index 0000000..f7295cd --- /dev/null +++ b/bots/joji-bot/run.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +bun install --frozen-lockfile +bun --env-file=.env run index.ts diff --git a/bots/joji-bot/tsconfig.json b/bots/joji-bot/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/bots/joji-bot/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/rollcron.yaml b/rollcron.yaml index ce844d9..f26adf8 100644 --- a/rollcron.yaml +++ b/rollcron.yaml @@ -34,3 +34,10 @@ jobs: env_file: ~/run/discord-bots/asakatsu-bot/env log: ~/run/discord-bots/asakatsu-bot/log + joji-bot: + schedule: "7pm" + working_dir: bots/joji-bot + run: ./run.sh + env_file: ~/run/discord-bots/joji-bot/env + log: ~/run/discord-bots/joji-bot/log +