2 Commits

Author SHA1 Message Date
ShirkNeko
bdddca8637 New translations strings.xml (Vietnamese) 2025-11-30 21:53:05 +08:00
ShirkNeko
032c3d4c54 New translations strings.xml (Turkish) 2025-11-28 17:15:28 +08:00
10 changed files with 486 additions and 2 deletions

65
.github/workflows/notify.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
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,7 +15,6 @@ A kernel-based root solution for Android devices, forked from [`tiann/KernelSU`]
1. Kernel-based `su` and root access management
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
4. Support non-GKI and GKI 1.0
5. KPM Support

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home">Ana Sayfa</string>
<string name="learn_more">Daha fazla bilgi edinin</string>
<string name="home_not_installed">Yüklü değil</string>
<string name="home_click_to_install">Yüklemek için tıklayın</string>
<string name="home_working">Çalışıyor</string>
@@ -34,6 +35,7 @@
<string name="reboot_edl">EDL moduna yeniden başlat</string>
<string name="about">Hakkında</string>
<string name="module_uninstall_confirm">%s modülünü kaldırmak istediğinizden emin misiniz?</string>
<string name="metamodule_uninstall_confirm">"%s modülünü kaldırmak istediğinizden emin misiniz? Bu eylem tüm modülleri etkileyecek ve meta modül tarafından sağlanan belirli özellikler (örneğin bağlama) artık çalışmayacaktır."</string>
<string name="module_uninstall_success">%s kaldırıldı</string>
<string name="module_uninstall_failed">Kaldırılamadı: %s</string>
<string name="module_version">Sürüm</string>
@@ -109,6 +111,7 @@
<string name="install_inactive_slot">Etkin olmayan yuvaya yükle (OTA sonrası)</string>
<string name="install_inactive_slot_warning">Cihazınız, yeniden başlatma sonrasında **ZORUNLU** olarak mevcut etkin olmayan yuvaya önyükleme yapacaktır!\nSadece OTA tamamlandıktan sonra bu seçeneği kullanın.\nDevam etmek istiyor musunuz?</string>
<string name="install_next">İleri</string>
<string name="install_select_partition">Bölüm seçin</string>
<string name="install_upload_lkm_file">Yerel LKM dosyası kullan</string>
<string name="install_only_support_ko_file">Yalnızca .ko dosyaları desteklenir</string>
<string name="select_file_tip">%1$s bölüm görüntüsü önerilir</string>
@@ -167,6 +170,8 @@
<string name="settings_disable_kernel_umount_summary">KernelSU tarafından kontrol edilen çekirdek seviyesindeki ayırma davranışını devre dışı bırakın.</string>
<string name="settings_enable_enhanced_security">Gelişmiş güvenliği etkinleştir</string>
<string name="settings_enable_enhanced_security_summary">Daha katı güvenlik politikalarını etkinleştirin.</string>
<string name="feature_status_unsupported_summary">Çekirdek bu özelliği desteklemiyor.</string>
<string name="feature_status_managed_summary">Bu özellik bir modül tarafından yönetiliyor.</string>
<string name="settings_mode_default">Varsayılan</string>
<string name="settings_mode_temp_enable">Geçici olarak etkinleştir</string>
<string name="settings_mode_always_enable">Kalıcı olarak etkinleştir</string>
@@ -185,6 +190,8 @@
<string name="hide_susfs_status_summary">Ana sayfadaki SuSFS durum bilgilerini gizle</string>
<string name="hide_zygisk_implement">Zygisk durumunu gizle</string>
<string name="hide_zygisk_implement_summary">Ana sayfada Zygisk uygulama bilgisini gizle</string>
<string name="hide_meta_module_implement">Meta Modül durumunu gizle</string>
<string name="hide_meta_module_implement_summary">Ana sayfadaki Meta Modül uygulama bilgilerini gizle</string>
<string name="hide_link_card">Bağlantı Kartı Durumunu Gizle</string>
<string name="hide_link_card_summary">Ana sayfadaki bağlantı kartı bilgilerini gizle</string>
<string name="hide_tag_card">Modül etiket satırlarını gizle</string>
@@ -194,6 +201,7 @@
<string name="theme_light">ık</string>
<string name="theme_dark">Koyu</string>
<string name="manual_hook">Manuel Kanca</string>
<string name="inline_hook">Satır İçi Kanca</string>
<string name="dynamic_color_title">Dinamik renkler</string>
<string name="dynamic_color_summary">Sistem temaları kullanarak dinamik renkler</string>
<string name="choose_theme_color">Bir tema rengi seçin</string>
@@ -325,6 +333,8 @@
<string name="module_failed_count">%d yeni modül yüklenemedi</string>
<string name="module_download_error">Modül indirme başarısız</string>
<string name="kernel_flashing">Çekirdek Yükleniyor</string>
<string name="warning_of_meta_module_title">Meta modül gerektirir</string>
<string name="warning_of_meta_module_summary">Bu modül /system bölümünü bağlamak istiyor, meta modül bunu halledecektir. Aksi takdirde, çalışmayabilir.</string>
<!-- 分类相关 -->
<string name="category_all_apps">Tümü</string>
<string name="category_root_apps">Root</string>
@@ -558,6 +568,7 @@
<string name="no_active_manager">Aktif yönetici yok</string>
<string name="default_signature">SukiSU</string>
<string name="home_zygisk_implement">Zygisk uygulaması</string>
<string name="home_meta_module_implement">Meta Modül uygulaması</string>
<!-- 循环路径相关 -->
<string name="susfs_tab_sus_loop_paths">SUS Döngü Yolları</string>
<string name="susfs_add_sus_loop_path">SUS Döngü Yolu Ekle</string>
@@ -691,4 +702,27 @@ etkin: Çekirdekteki AVC günlük kaydında, \'su\' komutuna ait tcontext\'i \'k
<string name="miui_uninstall_title">SukiSU Yöneticisi Kaldırılsın mı?</string>
<string name="miui_uninstall_content">Kaldırma işlemine devam etmek, root erişiminizin temel işlevselliğini etkilemeyecektir. Root, bu yöneticiden bağımsız olarak çalışacak şekilde tasarlanmıştır.</string>
<string name="incompatible_kernel_msg">Mevcut yönetici bu çekirdekle uyumsuz! Lütfen çekirdeği %2$d veya daha yüksek bir sürüme yükseltin (mevcut sürüm %1$d)</string>
<string name="umount_path_manager">Bağlantı Kaldırma Yolu Yönetimi</string>
<string name="umount_path_manager_summary">Çekirdek bağlantı kaldırma yollarını yönetin</string>
<string name="umount_path_restart_notice">Değişikliklerin etkili olması için yeniden başlatma gereklidir. Sistem, yeni yapılandırmayı bir sonraki önyüklemede uygulayacaktır.</string>
<string name="add_umount_path">Bağlantı Kaldırma Yolu Ekle</string>
<string name="mount_path">Bağlama Yolu</string>
<string name="umount_flags">Bağlantı Kaldırma Bayrakları</string>
<string name="umount_flags_hint">0=Normal, 8=MNT_DETACH, -1=Otomatik</string>
<string name="flags">Bayraklar</string>
<string name="confirm_delete">Silmeyi Onayla</string>
<string name="confirm_delete_umount_path">%s yolunu silmek istediğinizden emin misiniz?</string>
<string name="umount_path_added">Yol eklendi, yeniden başlattıktan sonra geçerli olacak</string>
<string name="umount_path_removed">Yol kaldırıldı, yeniden başlattıktan sonra geçerli olacak</string>
<string name="operation_failed">İşlem başarısız oldu</string>
<string name="confirm_action">Eylemi Onayla</string>
<string name="confirm_clear_custom_paths">Tüm özel yolları temizlemek istediğinizden emin misiniz? (Varsayılan yollar korunacaktır)</string>
<string name="custom_paths_cleared">Özel yollar temizlendi</string>
<string name="clear_custom_paths">Özel Yolları Temizle</string>
<string name="apply_config">Yapılandırmayı Uygula</string>
<string name="config_applied">Yapılandırma çekirdeğe uygulandı</string>
<string name="mnt_detach">MNT_DETACH</string>
<string name="group_contains_apps">%d uygulama içeriyor</string>
<string name="settings_disable_sulog">Süper kullanıcı günlük kaydını devre dışı bırak</string>
<string name="settings_disable_sulog_summary">KernelSU süper kullanıcı erişim günlük kaydını devre dışı bırakır</string>
</resources>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home">Trang chủ</string>
<string name="learn_more">Tìm hiểu thêm</string>
<string name="home_not_installed">Chưa cài đặt</string>
<string name="home_click_to_install">Nhấn để cài đặt</string>
<string name="home_working">Đang hoạt động</string>
@@ -30,10 +31,11 @@
<string name="reboot_userspace">Khởi động lại mềm</string>
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
<string name="reboot_download">Khởi động lại vào Download Mode</string>
<string name="reboot_download">Khởi động lại vào Download</string>
<string name="reboot_edl">Khởi động lại vào EDL</string>
<string name="about">Thông tin</string>
<string name="module_uninstall_confirm">Bạn có THẬT SỰ muốn gỡ cài đặt module %s không?</string>
<string name="metamodule_uninstall_confirm">"Bạn có chắc chắn muốn gỡ cài đặt module %s không? Thao tác này sẽ ảnh hưởng đến tất cả các module và một số tính năng do siêu module cung cấp (chẳng hạn như mount) sẽ không còn hoạt động nữa."</string>
<string name="module_uninstall_success">%s đã được gỡ cài đặt</string>
<string name="module_uninstall_failed">Gỡ cài đặt thất bại: %s</string>
<string name="module_version">Phiên bản</string>
@@ -109,6 +111,9 @@
<string name="install_inactive_slot">Cài đặt vào phân vùng chưa được sử dụng (Sau OTA)</string>
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BUỘC** phải khởi động vào phân vùng chưa được sử dụng!\nChỉ sử dụng tùy chọn này sau khi cập nhật OTA hoàn tất.\nTiếp tục?</string>
<string name="install_next">Kế tiếp</string>
<string name="install_select_partition">Chọn phân vùng</string>
<string name="install_upload_lkm_file">Sử dụng file LKM cục bộ</string>
<string name="install_only_support_ko_file">Chỉ hỗ trợ các file .ko</string>
<string name="select_file_tip">Phân vùng %1$s được khuyến nghị</string>
<string name="select_kmi">Chọn KMI</string>
<string name="settings_uninstall">Gỡ cài đặt</string>
@@ -161,6 +166,15 @@
<string name="su_not_allowed">Không thể cấp quyền Superuser cho %s</string>
<string name="settings_disable_su">Vô hiệu hoá lệnh SU</string>
<string name="settings_disable_su_summary">Vô hiệu hoá khả năng thực thi lệnh SU để lấy quyền root (Những app đã cấp trước đó không bị ảnh hưởng)</string>
<string name="settings_disable_kernel_umount">Vô hiệu hoá kernel umount</string>
<string name="settings_disable_kernel_umount_summary">Vô hiệu hoá umount kernel-level được kiểm soát bởi KernelSU.</string>
<string name="settings_enable_enhanced_security">Kích hoạt bảo mật nâng cao</string>
<string name="settings_enable_enhanced_security_summary">Cho phép chính sách bảo mật chặt chẽ hơn.</string>
<string name="feature_status_unsupported_summary">Kernel không hỗ trợ tính năng này.</string>
<string name="feature_status_managed_summary">Tính năng này được quản lý bởi một module.</string>
<string name="settings_mode_default">Mặc định</string>
<string name="settings_mode_temp_enable">Tạm thời kích hoạt</string>
<string name="settings_mode_always_enable">Luôn luôn kích hoạt</string>
<string name="module_install_multiple_confirm_with_names">Bạn có chắc muốn cài đặt các module %1$d không? \n\n%2$s</string>
<string name="more_settings">Nhiều cài đặt hơn</string>
<string name="selinux">SELinux</string>
@@ -176,6 +190,8 @@
<string name="hide_susfs_status_summary">Ẩn thông tin trạng thái SuSFS ở trang chủ</string>
<string name="hide_zygisk_implement">Ẩn trạng thái Zygisk</string>
<string name="hide_zygisk_implement_summary">Ẩn thông tin triển khai Zygisk trên trang chủ</string>
<string name="hide_meta_module_implement">Ẩn trạng thái Siêu Module</string>
<string name="hide_meta_module_implement_summary">Ẩn thông tin triển khai Siêu Module trên trang chủ.</string>
<string name="hide_link_card">Ẩn trạng thái thẻ liên kết</string>
<string name="hide_link_card_summary">Ẩn thông tin thẻ liên kết ở trang chủ</string>
<string name="hide_tag_card">Ẩn các nhãn module</string>
@@ -185,6 +201,7 @@
<string name="theme_light">Sáng</string>
<string name="theme_dark">Tối</string>
<string name="manual_hook">Hook thủ công</string>
<string name="inline_hook">Inline Hook</string>
<string name="dynamic_color_title">Màu sắc động</string>
<string name="dynamic_color_summary">Sử dụng màu sắc động làm chủ đề hệ thống</string>
<string name="choose_theme_color">Chọn màu chủ đề</string>
@@ -316,6 +333,8 @@
<string name="module_failed_count">Cài đặt module %d thất bại</string>
<string name="module_download_error">Tải xuống module thất bại</string>
<string name="kernel_flashing">Kernel Flashing</string>
<string name="warning_of_meta_module_title">Yêu cầu Siêu Module</string>
<string name="warning_of_meta_module_summary">Module này muốn mount /system, Siêu Module sẽ xử lý việc đó. Nếu không, nó có thể không hoạt động.</string>
<!-- 分类相关 -->
<string name="category_all_apps">Tất cả</string>
<string name="category_root_apps">Root</string>
@@ -549,6 +568,7 @@
<string name="no_active_manager">Trình quản lý đang không hoạt động</string>
<string name="default_signature">SukiSU</string>
<string name="home_zygisk_implement">Triển khai Zygisk</string>
<string name="home_meta_module_implement">Triển khai Siêu Module</string>
<!-- 循环路径相关 -->
<string name="susfs_tab_sus_loop_paths">Đường dẫn Vòng lặp SuS</string>
<string name="susfs_add_sus_loop_path">Thêm Đường dẫn Vòng lặp SuS</string>
@@ -681,4 +701,28 @@ Bật: Kích hoạt tính năng giả mạo sus tcontext của \'su\' thành \'k
<!-- MiUI Uninstall Desc -->
<string name="miui_uninstall_title">Bạn muốn xoá tôi đi sao 😭</string>
<string name="miui_uninstall_content">Hừm, được rồi, tôi sẽ bị bạn gỡ cài đặt. Chức năng root sẽ không ngừng hoạt động chỉ vì bạn mất một Trình quản lý. Đừng lo chỉ Gỡ cài đặt Trình quản lý thôi thì không thể mất quyền truy cập root được đâu, zako~❤️</string>
<string name="incompatible_kernel_msg">Trình quản lý hiện tại không tương thích với Kernel này! Vui lòng nâng cấp Kernel lên phiên bản %2$d hoặc cao hơn (hiện tại là %1$d)</string>
<string name="umount_path_manager">Quản lý Đường dẫn Umount</string>
<string name="umount_path_manager_summary">Quản lý Đường dẫn Kernel Umount.</string>
<string name="umount_path_restart_notice">Cần khởi động lại để các thay đổi có hiệu lực. Hệ thống sẽ áp dụng cấu hình mới vào lần khởi động tiếp theo</string>
<string name="add_umount_path">Thêm Đường dẫn Umount</string>
<string name="mount_path">Đường dẫn Mount</string>
<string name="umount_flags">Umount Flags</string>
<string name="umount_flags_hint">0=Normal Umount, 8=MNT_DETACH, -1=Auto</string>
<string name="flags">Flags</string>
<string name="confirm_delete">Xác nhận Xoá?</string>
<string name="confirm_delete_umount_path">Bạn có chắc chắn muốn xóa đường dẫn %s không?</string>
<string name="umount_path_added">Đã thêm đường dẫn, sẽ có hiệu lực sau khi khởi động lại</string>
<string name="umount_path_removed">Đường dẫn đã bị xóa, sẽ có hiệu lực sau khi khởi động lại</string>
<string name="operation_failed">Thao tác thất bại</string>
<string name="confirm_action">Xác nhận Khởi chạy?</string>
<string name="confirm_clear_custom_paths">Bạn có chắc chắn muốn xóa tất cả các đường dẫn tùy chỉnh không? (Các đường dẫn mặc định sẽ được giữ nguyên)</string>
<string name="custom_paths_cleared">Đường dẫn tùy chỉnh đã được xóa</string>
<string name="clear_custom_paths">Xóa Đường dẫn Tùy chỉnh</string>
<string name="apply_config">Áp dụng Cấu hình</string>
<string name="config_applied">Cấu hình đã được áp dụng cho Kernel</string>
<string name="mnt_detach">MNT_DETACH</string>
<string name="group_contains_apps">Chứa %d ứng dụng</string>
<string name="settings_disable_sulog">Vô hiệu hoá ghi nhật ký Superuser</string>
<string name="settings_disable_sulog_summary">Vô hiệu hoá ghi nhật ký truy cập Superuser.</string>
</resources>

34
ts/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# 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

13
ts/README.md Normal file
View File

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

52
ts/bun.lock Normal file
View File

@@ -0,0 +1,52 @@
{
"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=="],
}
}

17
ts/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"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"
}
}

197
ts/send.ts Normal file
View File

@@ -0,0 +1,197 @@
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);
});

29
ts/tsconfig.json Normal file
View File

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