diff --git a/ts/.gitignore b/ts/.gitignore deleted file mode 100644 index a14702c4..00000000 --- a/ts/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# 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 diff --git a/ts/README.md b/ts/README.md deleted file mode 100644 index bb8bd0b5..00000000 --- a/ts/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# ts - -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run send.ts -``` \ No newline at end of file diff --git a/ts/bun.lock b/ts/bun.lock deleted file mode 100644 index 125e92fa..00000000 --- a/ts/bun.lock +++ /dev/null @@ -1,52 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "ts", - "dependencies": { - "grammy": "^1.38.4", - "zod": "^4.1.12", - }, - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@grammyjs/types": ["@grammyjs/types@3.22.2", "", {}, "sha512-uu7DX2ezhnBPozL3bXHmwhLvaFsh59E4QyviNH4Cij7EdVekYrs6mCzeXsa2pDk30l3uXo7DBahlZLzTPtpYZg=="], - - "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - - "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - - "grammy": ["grammy@1.38.4", "", { "dependencies": { "@grammyjs/types": "3.22.2", "abort-controller": "^3.0.0", "debug": "^4.4.3", "node-fetch": "^2.7.0" } }, "sha512-z07Kin3HgRwMdy40KUs+c9fmNBvGlSxGwcqY8NAH0a8KULGFYEMQaFAo3ge0V5tvmgr02Jgubkf54KjHLAMCbw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "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=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], - } -} diff --git a/ts/package.json b/ts/package.json deleted file mode 100644 index 6d8ccf81..00000000 --- a/ts/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "ts", - "module": "send.ts", - "type": "module", - "private": true, - "author": "TypeFlu (https://Typeflu.me/)", - "devDependencies": { - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5" - }, - "dependencies": { - "grammy": "^1.38.4", - "zod": "^4.1.12" - } -} diff --git a/ts/send.ts b/ts/send.ts deleted file mode 100644 index a1c18e9a..00000000 --- a/ts/send.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { Bot, InlineKeyboard } from "grammy"; -import { z } from "zod"; - -const BOT_TOKEN = process.env.BOT_TOKEN!; -const GROUP_ID = Number(process.env.TELEGRAM_GROUP_ID!); -const TOPIC_COMMITS = Number(process.env.TELEGRAM_TOPIC_COMMITS!); -const TOPIC_PRS = Number(process.env.TELEGRAM_TOPIC_PRS!); -const GITHUB_TOKEN = process.env.GITHUB_TOKEN!; -const EVENT_PATH = process.env.GITHUB_EVENT_PATH!; - -const bot = new Bot(BOT_TOKEN); - -const FileNode = z.object({ path: z.string() }); - -const PullRequestSchema = z.object({ - action: z.string(), - number: z.number(), - repository: z.object({ - full_name: z.string(), - html_url: z.url(), - }), - pull_request: z.object({ - html_url: z.url().optional(), - url: z.url().optional(), - title: z.string(), - body: z.string().nullable(), - user: z.object({ login: z.string(), html_url: z.url() }), - head: z.object({ ref: z.string() }), - base: z.object({ ref: z.string() }), - changed_files: z.number().optional().default(0), - additions: z.number().optional().default(0), - deletions: z.number().optional().default(0), - }), -}); - -const PushSchema = z.object({ - ref: z.string(), - repository: z.object({ - full_name: z.string(), - html_url: z.url(), - }), - head_commit: z - .object({ - id: z.string(), - url: z.url(), - message: z.string(), - author: z.object({ name: z.string(), email: z.email() }), - added: z.array(z.string()).optional().default([]), - modified: z.array(z.string()).optional().default([]), - removed: z.array(z.string()).optional().default([]), - }) - .nullable(), -}); - -function detectLanguage(files: string[]): string { - const ext = files.map((f) => (f.split(".").pop() || "").toLowerCase()); - if (ext.some((e) => e === "kt" || e === "kts")) return "Kotlin"; - if (ext.some((e) => e === "rs")) return "Rust"; - if (ext.some((e) => e === "c")) return "C"; - if (ext.some((e) => e === "sh")) return "Shell"; - if (ext.some((e) => e === "ts" || e === "tsx")) return "TypeScript"; - return "Other"; -} - -async function fetchPrFiles( - repoFullName: string, - prNumber: number, -): Promise { - const query = ` - query($owner:String!, $name:String!, $number:Int!) { - repository(owner:$owner, name:$name) { - pullRequest(number:$number) { - files(first:100) { - nodes { - path - } - } - } - } - }`; - const [owner, name] = repoFullName.split("/"); - const resp = await fetch("https://api.github.com/graphql", { - method: "POST", - headers: { - Authorization: `bearer ${GITHUB_TOKEN}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query, - variables: { owner, name, number: prNumber }, - }), - }); - if (!resp.ok) { - const body = await resp.text(); - throw new Error(`GitHub GraphQL API error: ${resp.status} ${body}`); - } - const json = (await resp.json()) as any; - const nodes = json?.data?.repository?.pullRequest?.files?.nodes as - | { path: string }[] - | undefined; - if (!nodes || !Array.isArray(nodes)) return []; - return nodes.map((n) => n.path); -} - -function prUrlOf(pr: { html_url?: string; url?: string }) { - return pr.html_url ?? pr.url ?? ""; -} - -async function formatPrMessage( - evt: z.infer, -): Promise<{ text: string; fileLink: string }> { - const pr = evt.pull_request; - const repo = evt.repository; - const files = await fetchPrFiles(repo.full_name, evt.number); - const lang = detectLanguage(files); - const prUrl = prUrlOf(pr); - const fileLink = prUrl ? `${prUrl}/files` : repo.html_url; - const bodyText = pr.body ? pr.body : "_No description provided_"; - const text = - `### Repository\n[${repo.full_name}](${repo.html_url})\n\n` + - `**Pull Request #${evt.number}:** [${pr.title}](${prUrl || repo.html_url})\n\n` + - `**Author:** [${pr.user.login}](${pr.user.html_url})\n` + - `**Files Changed:** ${pr.changed_files}\n` + - `**Additions / Deletions:** +${pr.additions} / -${pr.deletions}\n` + - `**Language:** ${lang}\n\n` + - `**Description:**\n\`\`\`\n${bodyText}\n\`\`\``; - return { text, fileLink }; -} - -function formatPushMessage(evt: z.infer): { - text: string; - fileLink: string; -} { - const repo = evt.repository; - const c = evt.head_commit; - if (!c) { - const text = `### Repository\n[${repo.full_name}](${repo.html_url})\n\nPush event detected, but no head commit data available.`; - return { text, fileLink: repo.html_url }; - } - const added = c.added ?? []; - const modified = c.modified ?? []; - const removed = c.removed ?? []; - const details = [ - added.length ? `➕ Added: ${added.join(", ")}` : "", - modified.length ? `✏️ Modified: ${modified.join(", ")}` : "", - removed.length ? `❌ Removed: ${removed.join(", ")}` : "", - ] - .filter(Boolean) - .join("\n"); - const lang = detectLanguage([...added, ...modified, ...removed]); - const fileLink = c.url; - const text = - `### Repository\n[${repo.full_name}](${repo.html_url})\n\n` + - `**Commit:** [${c.id}](${c.url})\n` + - `**Author:** ${c.author.name} <${c.author.email}>\n` + - `**Message:**\n\`\`\`\n${c.message}\n\`\`\`\n` + - (details ? `**Changes:**\n${details}\n` : "") + - `**Language:** ${lang}`; - return { text, fileLink }; -} - -async function main(): Promise { - const raw = await ( - await import("node:fs/promises") - ).readFile(EVENT_PATH, "utf-8"); - const parsed = JSON.parse(raw); - let messageObj: { text: string; fileLink: string }; - let topic: number; - if ("pull_request" in parsed) { - const prEvt = PullRequestSchema.parse(parsed); - messageObj = await formatPrMessage(prEvt); - topic = TOPIC_PRS; - } else { - const pushEvt = PushSchema.parse(parsed); - messageObj = formatPushMessage(pushEvt); - topic = TOPIC_COMMITS; - } - const repoUrl = - (parsed.repository && - (parsed.repository.html_url ?? parsed.repository.url)) || - ""; - const keyboard = new InlineKeyboard() - .url("View on GitHub", repoUrl) - .row() - .url("View Files", messageObj.fileLink); - await bot.api.sendMessage(GROUP_ID, messageObj.text, { - parse_mode: "Markdown", - message_thread_id: topic, - reply_markup: keyboard, - }); - process.exit(0); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/ts/tsconfig.json b/ts/tsconfig.json deleted file mode 100644 index bfa0fead..00000000 --- a/ts/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "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 - } -}