fix: script
This commit is contained in:
154
ts/send.ts
154
ts/send.ts
@@ -1,24 +1,25 @@
|
|||||||
import { Bot, InlineKeyboard } from "grammy";
|
import { Bot, InlineKeyboard } from "grammy";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const EnvSchema = z.object({
|
const BOT_TOKEN = process.env.BOT_TOKEN!;
|
||||||
BOT_TOKEN: z.string(),
|
const GROUP_ID = Number(process.env.TELEGRAM_GROUP_ID!);
|
||||||
TELEGRAM_GROUP_ID: z.string(),
|
const TOPIC_COMMITS = Number(process.env.TELEGRAM_TOPIC_COMMITS!);
|
||||||
TELEGRAM_TOPIC_COMMITS: z.string(),
|
const TOPIC_PRS = Number(process.env.TELEGRAM_TOPIC_PRS!);
|
||||||
TELEGRAM_TOPIC_PRS: z.string(),
|
const GITHUB_TOKEN = process.env.GITHUB_TOKEN!;
|
||||||
GITHUB_TOKEN: z.string(),
|
const EVENT_PATH = process.env.GITHUB_EVENT_PATH!;
|
||||||
GITHUB_EVENT_PATH: z.string(),
|
|
||||||
});
|
|
||||||
const env = EnvSchema.parse(process.env);
|
|
||||||
|
|
||||||
const bot = new Bot(env.BOT_TOKEN);
|
const bot = new Bot(BOT_TOKEN);
|
||||||
|
|
||||||
|
const FileSchema = z.object({
|
||||||
|
path: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
const PullRequestSchema = z.object({
|
const PullRequestSchema = z.object({
|
||||||
action: z.string(),
|
action: z.string(),
|
||||||
number: z.number(),
|
number: z.number(),
|
||||||
repository: z.object({
|
repository: z.object({
|
||||||
full_name: z.string(),
|
full_name: z.string(),
|
||||||
html_url: z.url(),
|
html_url: z.url()
|
||||||
}),
|
}),
|
||||||
pull_request: z.object({
|
pull_request: z.object({
|
||||||
url: z.url(),
|
url: z.url(),
|
||||||
@@ -26,74 +27,55 @@ const PullRequestSchema = z.object({
|
|||||||
body: z.string().nullable(),
|
body: z.string().nullable(),
|
||||||
user: z.object({
|
user: z.object({
|
||||||
login: z.string(),
|
login: z.string(),
|
||||||
html_url: z.url(),
|
html_url: z.url()
|
||||||
}),
|
}),
|
||||||
head: z.object({ ref: z.string() }),
|
head: z.object({ ref: z.string() }),
|
||||||
base: z.object({ ref: z.string() }),
|
base: z.object({ ref: z.string() }),
|
||||||
changed_files: z.number(),
|
changed_files: z.number(),
|
||||||
additions: z.number(),
|
additions: z.number(),
|
||||||
deletions: z.number(),
|
deletions: z.number()
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const PushSchema = z.object({
|
const PushSchema = z.object({
|
||||||
ref: z.string(),
|
ref: z.string(),
|
||||||
repository: z.object({
|
repository: z.object({
|
||||||
full_name: z.string(),
|
full_name: z.string(),
|
||||||
html_url: z.url(),
|
html_url: z.url()
|
||||||
}),
|
}),
|
||||||
head_commit: z
|
head_commit: z.object({
|
||||||
.object({
|
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
url: z.url(),
|
url: z.url(),
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
author: z.object({
|
author: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.email(),
|
email: z.email()
|
||||||
}),
|
}),
|
||||||
added: z.array(z.string()),
|
added: z.array(z.string()),
|
||||||
modified: z.array(z.string()),
|
modified: z.array(z.string()),
|
||||||
removed: z.array(z.string()),
|
removed: z.array(z.string())
|
||||||
})
|
}).nullable()
|
||||||
.nullable(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function detectLanguage(files: string[]): string {
|
function detectLanguage(files: string[]): string {
|
||||||
const extCount = files.reduce<Record<string, number>>((count, file) => {
|
const ext = files.map(f => f.split(".").pop()?.toLowerCase() || "");
|
||||||
const ext = file.split(".").pop()?.toLowerCase() ?? "";
|
if (ext.includes("kt") || ext.includes("kts")) return "Kotlin";
|
||||||
count[ext] = (count[ext] || 0) + 1;
|
if (ext.includes("c") && !ext.includes("h")) return "C";
|
||||||
return count;
|
if (ext.includes("rs")) return "Rust";
|
||||||
}, {});
|
if (ext.includes("sh")) return "Shell";
|
||||||
if (extCount.kt || extCount.kts) return "Kotlin";
|
if (ext.includes("ts") || ext.includes("tsx")) return "TypeScript";
|
||||||
if (extCount.c && !extCount.h) return "C";
|
|
||||||
if (extCount.rs) return "Rust";
|
|
||||||
if (extCount.sh) return "Shell";
|
|
||||||
if (extCount.ts || extCount.tsx) return "TypeScript";
|
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitHubPRFilesResponse {
|
async function fetchPrFiles(repoFullName: string, prNumber: number): Promise<string[]> {
|
||||||
data: {
|
|
||||||
repository: {
|
|
||||||
pullRequest: {
|
|
||||||
files: {
|
|
||||||
nodes: { path: string }[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPrFiles(
|
|
||||||
repoFullName: string,
|
|
||||||
prNumber: number,
|
|
||||||
): Promise<string[]> {
|
|
||||||
const query = `
|
const query = `
|
||||||
query($owner: String!, $name: String!, $number: Int!) {
|
query($owner: String!, $name: String!, $number: Int!) {
|
||||||
repository(owner: $owner, name: $name) {
|
repository(owner: $owner, name: $name) {
|
||||||
pullRequest(number: $number) {
|
pullRequest(number: $number) {
|
||||||
files(first: 100) {
|
files(first: 100) {
|
||||||
nodes { path }
|
nodes {
|
||||||
|
path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,25 +84,17 @@ async function fetchPrFiles(
|
|||||||
const resp = await fetch("https://api.github.com/graphql", {
|
const resp = await fetch("https://api.github.com/graphql", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `bearer ${env.GITHUB_TOKEN}`,
|
"Authorization": `bearer ${GITHUB_TOKEN}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({ query, variables: { owner, name, number: prNumber } })
|
||||||
query,
|
|
||||||
variables: { owner, name, number: prNumber },
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
const json = await resp.json() as any;
|
||||||
throw new Error(`GitHub API error: ${resp.status} ${resp.statusText}`);
|
const nodes = json.data.repository.pullRequest.files.nodes as { path: string }[];
|
||||||
}
|
return nodes.map(n => n.path);
|
||||||
const json = (await resp.json()) as GitHubPRFilesResponse;
|
|
||||||
if (!json?.data?.repository?.pullRequest?.files?.nodes) return [];
|
|
||||||
return json.data.repository.pullRequest.files.nodes.map((n) => n.path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function formatPrMessage(
|
async function formatPrMessage(evt: z.infer<typeof PullRequestSchema>): Promise<string> {
|
||||||
evt: z.infer<typeof PullRequestSchema>,
|
|
||||||
): Promise<string> {
|
|
||||||
const files = await fetchPrFiles(evt.repository.full_name, evt.number);
|
const files = await fetchPrFiles(evt.repository.full_name, evt.number);
|
||||||
const lang = detectLanguage(files);
|
const lang = detectLanguage(files);
|
||||||
return `Repository: [${evt.repository.full_name}](${evt.repository.html_url})
|
return `Repository: [${evt.repository.full_name}](${evt.repository.html_url})
|
||||||
@@ -129,7 +103,7 @@ Author: [${evt.pull_request.user.login}](${evt.pull_request.user.html_url})
|
|||||||
Files Changed: ${evt.pull_request.changed_files}, +${evt.pull_request.additions}/- ${evt.pull_request.deletions}
|
Files Changed: ${evt.pull_request.changed_files}, +${evt.pull_request.additions}/- ${evt.pull_request.deletions}
|
||||||
Likely Language: ${lang}
|
Likely Language: ${lang}
|
||||||
Title: ${evt.pull_request.title}
|
Title: ${evt.pull_request.title}
|
||||||
Description: ${evt.pull_request.body ?? "_None provided_"}`;
|
Description: ${evt.pull_request.body ?? "_None provided_"}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPushMessage(evt: z.infer<typeof PushSchema>): string {
|
function formatPushMessage(evt: z.infer<typeof PushSchema>): string {
|
||||||
@@ -141,10 +115,8 @@ Push event but no head commit data.`;
|
|||||||
const details = [
|
const details = [
|
||||||
c.added.length ? `Added: ${c.added.join(", ")}` : "",
|
c.added.length ? `Added: ${c.added.join(", ")}` : "",
|
||||||
c.modified.length ? `Modified: ${c.modified.join(", ")}` : "",
|
c.modified.length ? `Modified: ${c.modified.join(", ")}` : "",
|
||||||
c.removed.length ? `Removed: ${c.removed.join(", ")}` : "",
|
c.removed.length ? `Removed: ${c.removed.join(", ")}` : ""
|
||||||
]
|
].filter(Boolean).join("\n");
|
||||||
.filter(Boolean)
|
|
||||||
.join("\n");
|
|
||||||
const lang = detectLanguage([...c.added, ...c.modified, ...c.removed]);
|
const lang = detectLanguage([...c.added, ...c.modified, ...c.removed]);
|
||||||
return `Repository: [${evt.repository.full_name}](${evt.repository.html_url})
|
return `Repository: [${evt.repository.full_name}](${evt.repository.html_url})
|
||||||
Commit: [${c.id}](${c.url})
|
Commit: [${c.id}](${c.url})
|
||||||
@@ -154,46 +126,32 @@ ${details ? details + "\n" : ""}Detected Language: ${lang}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
try {
|
const raw = await (await import("node:fs/promises")).readFile(EVENT_PATH, "utf-8");
|
||||||
const raw = await Bun.file(env.GITHUB_EVENT_PATH).text();
|
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
|
|
||||||
let message: string;
|
let message: string;
|
||||||
let topic: number;
|
let topic: number;
|
||||||
|
|
||||||
const prResult = PullRequestSchema.safeParse(parsed);
|
try {
|
||||||
if (prResult.success) {
|
const prEvt = PullRequestSchema.parse(parsed);
|
||||||
message = await formatPrMessage(prResult.data);
|
message = await formatPrMessage(prEvt);
|
||||||
topic = Number(env.TELEGRAM_TOPIC_PRS);
|
topic = TOPIC_PRS;
|
||||||
} else {
|
} catch {
|
||||||
const pushResult = PushSchema.safeParse(parsed);
|
const pushEvt = PushSchema.parse(parsed);
|
||||||
if (!pushResult.success) {
|
message = formatPushMessage(pushEvt);
|
||||||
console.error(
|
topic = TOPIC_COMMITS;
|
||||||
"Unrecognized event payload",
|
|
||||||
prResult.error,
|
|
||||||
pushResult.error,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
message = formatPushMessage(pushResult.data);
|
|
||||||
topic = Number(env.TELEGRAM_TOPIC_COMMITS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyboard = new InlineKeyboard().url(
|
const keyboard = new InlineKeyboard().url("View on GitHub", (parsed.repository as any).html_url as string);
|
||||||
"View on GitHub",
|
await bot.api.sendMessage(GROUP_ID, message, {
|
||||||
(parsed.repository as any).html_url as string,
|
|
||||||
);
|
|
||||||
await bot.api.sendMessage(Number(env.TELEGRAM_GROUP_ID), message, {
|
|
||||||
parse_mode: "Markdown",
|
parse_mode: "Markdown",
|
||||||
message_thread_id: topic,
|
message_thread_id: topic,
|
||||||
reply_markup: keyboard,
|
reply_markup: keyboard
|
||||||
});
|
});
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (err) {
|
|
||||||
console.error("Fatal error in main execution:", err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void main();
|
main().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user