3 Commits

Author SHA1 Message Date
Saksham
9e65012c99 Clean unused
Some checks failed
Crowdin Action / synchronize-with-crowdin (push) Has been cancelled
2025-11-30 11:06:38 +05:30
Saksham
91ede12d23 clean unused 2025-11-30 11:05:51 +05:30
MorStar
cd12337f2a Update README with SukiSU module mounting note
Added note about SukiSU's module mounting delegation.
2025-11-29 19:04:18 +08:00
8 changed files with 1 additions and 407 deletions

View File

@@ -1,65 +0,0 @@
name: Notify
on:
push:
branches:
- main
- 'release/**'
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- main
defaults:
run:
working-directory: ts
jobs:
notify:
runs-on: ubuntu-latest
concurrency:
group: notify-telegram-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Checkout repository with sparse-checkout
uses: actions/checkout@v5
with:
fetch-depth: 1
sparse-checkout: |
ts/**
sparse-checkout-cone-mode: false
- name: Setup Bun runtime
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Cache Bun modules
uses: actions/cache@v4
with:
path: |
~/.bun
ts/.node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('ts/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --cache
- name: Run telegram bot script
env:
BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_GROUP_ID: ${{ secrets.TELEGRAM_GROUP_ID }}
TELEGRAM_TOPIC_COMMITS: ${{ secrets.TELEGRAM_TOPIC_COMMITS }}
TELEGRAM_TOPIC_PRS: ${{ secrets.TELEGRAM_TOPIC_PRS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT_PATH: ${{ github.event_path }}
run: bun run send.ts

View File

@@ -15,6 +15,7 @@ A kernel-based root solution for Android devices, forked from [`tiann/KernelSU`]
1. Kernel-based `su` and root access management 1. Kernel-based `su` and root access management
2. Module system based on [Magic Mount](https://github.com/5ec1cff/KernelSU) 2. Module system based on [Magic Mount](https://github.com/5ec1cff/KernelSU)
> **Note:** SukiSU now delegates all module mounting to the installed *metamodule*; the core no longer handles mount operations.
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage 3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage
4. Support non-GKI and GKI 1.0 4. Support non-GKI and GKI 1.0
5. KPM Support 5. KPM Support

34
ts/.gitignore vendored
View File

@@ -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

View File

@@ -1,13 +0,0 @@
# ts
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run send.ts
```

View File

@@ -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=="],
}
}

View File

@@ -1,17 +0,0 @@
{
"name": "ts",
"module": "send.ts",
"type": "module",
"private": true,
"author": "TypeFlu <TypeFlu@gmail.com> (https://Typeflu.me/)",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"grammy": "^1.38.4",
"zod": "^4.1.12"
}
}

View File

@@ -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<string[]> {
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<typeof PullRequestSchema>,
): 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<typeof PushSchema>): {
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<void> {
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);
});

View File

@@ -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
}
}