# release: 2.26.4 #179
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release APK | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Tag version (e.g., v1.0.0)' | |
| required: true | |
| type: string | |
| release_notes: | |
| description: 'Release notes (Use \n for new lines, leave empty to use commit message)' | |
| required: false | |
| type: string | |
| default: '' | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-android-linux: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| - name: Extract version from tag | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| TAG="${{ github.event.inputs.tag }}" | |
| else | |
| TAG=${GITHUB_REF#refs/tags/} | |
| fi | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| # Check if tag contains beta | |
| if [[ "$TAG" == *"beta"* ]]; then | |
| echo "is_prerelease=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_prerelease=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update pubspec.yaml version | |
| run: | | |
| sed -i "s/^version:.*/version: ${{ steps.version.outputs.version }}+${{ github.run_number }}/" pubspec.yaml | |
| - name: Setup Java | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: 'zulu' | |
| java-version: '17' | |
| - name: Get Java Version | |
| run: echo "JAVA_VERSION=$(java -XshowSettings:properties -version 2>&1 | grep 'java.runtime.version' | awk '{print $3}')" >> $GITHUB_ENV | |
| # Flutter SDK 缓存 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version-file: .flutter-version | |
| channel: 'main' | |
| cache: true # 启用 Flutter SDK 缓存 | |
| cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' | |
| cache-path: '${{ runner.tool_cache }}/flutter' | |
| - name: Cache Pub dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: ${{ runner.os }}-pub-cache-${{ hashFiles('pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub-cache- | |
| # Gradle 缓存 | |
| - name: Cache Gradle | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('pubspec.lock', '**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Decode keystore | |
| run: | | |
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore.jks | |
| - name: Create key.properties | |
| run: | | |
| echo "storePassword=${{ secrets.STORE_PASSWORD }}" > android/key.properties | |
| echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties | |
| echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties | |
| echo "storeFile=keystore.jks" >> android/key.properties | |
| - name: Build APK - ARM64 (v8a) | |
| run: | | |
| flutter build apk --release --target-platform android-arm64 | |
| mv build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/pt_mate-${{ steps.version.outputs.version }}-arm64-v8a.apk | |
| - name: Upload Android artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: android-artifacts | |
| path: | | |
| build/app/outputs/flutter-apk/pt_mate-${{ steps.version.outputs.version }}-arm64-v8a.apk | |
| retention-days: 1 | |
| # 直接在Android构建后添加Linux构建步骤 | |
| - name: Install Linux dependencies | |
| run: | | |
| sudo apt-get update -y | |
| sudo apt-get install -y ninja-build libgtk-3-dev libblkid-dev libsecret-1-dev file libfuse2 lld | |
| # CMake 缓存 | |
| - name: Cache CMake | |
| id: cache_cmake_release | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| build/linux | |
| ~/.cmake | |
| key: ${{ runner.os }}-java-${{ env.JAVA_VERSION }}-cmake-release-${{ hashFiles('pubspec.lock', '**/CMakeLists.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-java-${{ env.JAVA_VERSION }}-cmake-release- | |
| - name: Build Linux | |
| run: | | |
| if [ "${{ steps.cache_cmake_release.outputs.cache-hit }}" != "true" ]; then | |
| echo "Cache miss or partial hit, cleaning build directory to avoid stale JDK paths..." | |
| rm -rf build/linux | |
| fi | |
| flutter build linux --release | |
| # AppImageTool 缓存 | |
| - name: Cache AppImageTool | |
| uses: actions/cache@v5 | |
| with: | |
| path: appimagetool-x86_64.AppImage | |
| key: ${{ runner.os }}-appimagetool-continuous | |
| restore-keys: | | |
| ${{ runner.os }}-appimagetool- | |
| - name: Download AppImageTool | |
| run: | | |
| if [ ! -f appimagetool-x86_64.AppImage ]; then | |
| wget -O appimagetool-x86_64.AppImage https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage | |
| fi | |
| chmod +x appimagetool-x86_64.AppImage | |
| - name: Create AppImage | |
| run: | | |
| # 创建AppDir结构 | |
| mkdir -p AppDir/usr/bin | |
| mkdir -p AppDir/usr/lib | |
| mkdir -p AppDir/usr/share/applications | |
| mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps | |
| # 复制应用文件 | |
| cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/ | |
| # 创建桌面文件 | |
| cat > AppDir/usr/share/applications/pt_mate.desktop << EOF | |
| [Desktop Entry] | |
| Type=Application | |
| Name=PT Mate | |
| Comment=PT伴侣 | |
| Exec=pt_mate | |
| Icon=pt_mate | |
| Categories=Network; | |
| Terminal=false | |
| EOF | |
| cp mt.png AppDir/usr/share/icons/hicolor/256x256/apps/pt_mate.png | |
| # 创建AppRun脚本 | |
| cat > AppDir/AppRun << 'EOF' | |
| #!/bin/bash | |
| HERE="$(dirname "$(readlink -f "${0}")")" | |
| export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}" | |
| exec "${HERE}/usr/bin/pt_mate" "$@" | |
| EOF | |
| chmod +x AppDir/AppRun | |
| # 复制桌面文件到根目录 | |
| cp AppDir/usr/share/applications/pt_mate.desktop AppDir/ | |
| # 复制图标到根目录 | |
| cp AppDir/usr/share/icons/hicolor/256x256/apps/pt_mate.png AppDir/ | |
| # 创建AppImage | |
| ./appimagetool-x86_64.AppImage AppDir pt_mate-${{ steps.version.outputs.version }}-linux-x64.AppImage | |
| - name: Upload Linux artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: linux-artifact | |
| path: pt_mate-${{ steps.version.outputs.version }}-linux-x64.AppImage | |
| retention-days: 1 | |
| build-windows: | |
| runs-on: windows-2022 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| - name: Extract version from tag | |
| id: version | |
| shell: bash | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| TAG="${{ github.event.inputs.tag }}" | |
| else | |
| TAG=${GITHUB_REF#refs/tags/} | |
| fi | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: Setup Java | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: 'zulu' | |
| java-version: '17' | |
| # Flutter SDK 缓存 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version-file: .flutter-version | |
| channel: 'main' | |
| cache: true # 启用 Flutter SDK 缓存 | |
| cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' | |
| cache-path: '${{ runner.tool_cache }}/flutter' | |
| - name: Cache Pub dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~\AppData\Local\Pub\Cache | |
| key: ${{ runner.os }}-pub-cache-${{ hashFiles('pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub-cache- | |
| # Visual Studio 状态缓存。不要缓存 build/windows;其中包含 CMake 生成目录, | |
| # 会固化 FindJNI 的旧探测结果并导致 jni.h include 路径失效。 | |
| - name: Cache Visual Studio | |
| uses: actions/cache@v5 | |
| with: | |
| path: C:\Users\runneradmin\AppData\Local\Microsoft\VisualStudio | |
| key: ${{ runner.os }}-vs-state-v2-release-${{ hashFiles('pubspec.lock', '**/CMakeLists.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-vs-state-v2-release- | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Clear stale Windows CMake build | |
| run: | | |
| Remove-Item -Recurse -Force build/windows -ErrorAction SilentlyContinue | |
| - name: Build Windows | |
| env: | |
| CMAKE_INCLUDE_PATH: ${{ env.JAVA_HOME }}\include;${{ env.JAVA_HOME }}\include\win32 | |
| run: flutter build windows --release | |
| - name: Install Inno Setup | |
| run: | | |
| # 检查是否已安装,避免重复安装 | |
| if (-not (Test-Path "C:\Program Files (x86)\Inno Setup 6\ISCC.exe")) { | |
| choco install innosetup -y | |
| } else { | |
| Write-Host "Inno Setup already installed" | |
| } | |
| - name: Download Chinese language file | |
| run: | | |
| $languagesDir = "C:\Program Files (x86)\Inno Setup 6\Languages" | |
| if (-not (Test-Path "$languagesDir\ChineseSimplified.isl")) { | |
| Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jrsoftware/issrc/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "$languagesDir\ChineseSimplified.isl" | |
| } | |
| - name: Compile Inno Setup installer | |
| run: | | |
| iscc /DMyAppVersion="${{ steps.version.outputs.version }}" windows/inno_setup.iss | |
| - name: Upload Windows artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-artifact | |
| path: windows/Output/pt_mate-${{ steps.version.outputs.version }}-windows-x64.exe | |
| retention-days: 1 | |
| build-ios-and-macos: | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| - name: Select Xcode 26.1.1 | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_26.1.1.app | |
| xcodebuild -version | |
| - name: Extract version from tag | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| TAG="${{ github.event.inputs.tag }}" | |
| else | |
| TAG=${GITHUB_REF#refs/tags/} | |
| fi | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| # Flutter SDK 缓存 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version-file: .flutter-version | |
| channel: 'main' | |
| cache: true # 启用 Flutter SDK 缓存 | |
| cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' | |
| cache-path: '${{ runner.tool_cache }}/flutter' | |
| - name: Cache Pub dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: ${{ runner.os }}-pub-cache-${{ hashFiles('pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub-cache- | |
| # CocoaPods 缓存 | |
| - name: Cache CocoaPods | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ios/Pods | |
| macos/Pods | |
| ~/.cocoapods | |
| key: ${{ runner.os }}-pods-${{ hashFiles('pubspec.lock', 'ios/Podfile', 'macos/Podfile') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pods- | |
| # Xcode 缓存 | |
| - name: Cache Xcode DerivedData | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/Library/Developer/Xcode/DerivedData | |
| key: ${{ runner.os }}-xcode-release-${{ hashFiles('pubspec.lock', '**/project.pbxproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-xcode-release- | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Install CocoaPods dependencies | |
| run: | | |
| cd ios | |
| pod install | |
| cd ../macos | |
| pod install | |
| - name: Build unsigned IPA | |
| run: | | |
| # Build iOS app bundle | |
| flutter build ios --release --no-codesign | |
| # Create build directory | |
| mkdir -p ios/build | |
| # Create IPA manually from app bundle | |
| mkdir -p Payload | |
| cp -r build/ios/iphoneos/Runner.app Payload/ | |
| zip -r ios/build/pt_mate-${{ steps.version.outputs.version }}-unsigned.ipa Payload/ | |
| rm -rf Payload | |
| - name: Create xcarchive | |
| run: | | |
| cd ios | |
| xcodebuild -workspace Runner.xcworkspace \ | |
| -scheme Runner \ | |
| -configuration Release \ | |
| -destination generic/platform=iOS \ | |
| -archivePath build/Runner.xcarchive \ | |
| archive \ | |
| CODE_SIGNING_ALLOWED=NO | |
| - name: Create xcarchive zip | |
| run: | | |
| cd ios/build | |
| zip -r pt_mate-${{ steps.version.outputs.version }}.xcarchive.zip Runner.xcarchive | |
| - name: Build macOS | |
| run: flutter build macos --release | |
| - name: Ad-hoc sign macOS app | |
| run: | | |
| codesign --force --deep --sign - \ | |
| --entitlements "macos/Runner/Release.entitlements" \ | |
| "build/macos/Build/Products/Release/pt_mate.app" | |
| - name: Create macOS DMG | |
| run: | | |
| hdiutil create -volname "PT Mate ${{ steps.version.outputs.version }}" \ | |
| -srcfolder "build/macos/Build/Products/Release/pt_mate.app" \ | |
| -ov -format UDZO \ | |
| "build/macos/Build/Products/Release/pt_mate-${{ steps.version.outputs.version }}-macos-x64.dmg" | |
| - name: Zip macOS app | |
| run: | | |
| cd build/macos/Build/Products/Release | |
| zip -r pt_mate-${{ steps.version.outputs.version }}-macos-x64.app.zip pt_mate.app | |
| cd - | |
| - name: Upload iOS and macOS artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ios-macos-artifacts | |
| path: | | |
| ios/build/pt_mate-${{ steps.version.outputs.version }}-unsigned.ipa | |
| ios/build/pt_mate-${{ steps.version.outputs.version }}.xcarchive.zip | |
| build/macos/Build/Products/Release/pt_mate-${{ steps.version.outputs.version }}-macos-x64.dmg | |
| build/macos/Build/Products/Release/pt_mate-${{ steps.version.outputs.version }}-macos-x64.app.zip | |
| retention-days: 1 | |
| create-release: | |
| runs-on: ubuntu-latest | |
| needs: [build-android-linux, build-windows, build-ios-and-macos] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| - name: Extract version from tag | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| TAG="${{ github.event.inputs.tag }}" | |
| else | |
| TAG=${GITHUB_REF#refs/tags/} | |
| fi | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| # Check if tag contains beta | |
| if [[ "$TAG" == *"beta"* ]]; then | |
| echo "is_prerelease=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_prerelease=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Get release message | |
| id: commit_message | |
| env: | |
| # Use environment variable to safely pass the input | |
| INPUT_NOTES: ${{ github.event.inputs.release_notes }} | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "$INPUT_NOTES" ]; then | |
| # Support manual \n for new lines in UI input | |
| # Use sed for replacement | |
| echo -e "$INPUT_NOTES" | sed 's/\\n/\n/g' > /tmp/release_msg.txt | |
| else | |
| # Get the latest commit message (full message including body) | |
| git log -1 --pretty=format:"%B" > /tmp/release_msg.txt | |
| fi | |
| # Save to output using multiline format | |
| echo "message<<EOF" >> $GITHUB_OUTPUT | |
| cat /tmp/release_msg.txt >> $GITHUB_OUTPUT | |
| echo "" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v8 | |
| - name: Create Release and Upload All Artifacts | |
| uses: softprops/action-gh-release@v3 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ steps.version.outputs.tag }} | |
| name: Release ${{ steps.version.outputs.tag }} | |
| body: | | |
| ## Changes in this release | |
| ${{ steps.commit_message.outputs.message }} | |
| ## 📱 Android APK Downloads | |
| - **ARM64 APK** (arm64-v8a): For modern 64-bit devices | |
| ## 💻 Desktop Downloads | |
| - **Windows x64**: Windows installer for 64-bit systems | |
| - **Linux x64**: Linux AppImage for 64-bit systems (portable, no installation required) | |
| - **macOS x64**: macOS DMG image for 64-bit systems | |
| - **macOS x64 (app)**: Zipped .app file for manual installation or use on systems where DMG is not preferred | |
| ## 🍎 iOS Downloads | |
| - **Unsigned IPA**: For sideloading with tools like AltStore, Sideloadly, etc. | |
| - **Xcode Archive**: For developers to re-sign and distribute | |
| draft: false | |
| prerelease: ${{ steps.version.outputs.is_prerelease }} | |
| files: | | |
| android-artifacts/pt_mate-${{ steps.version.outputs.version }}-arm64-v8a.apk | |
| windows-artifact/pt_mate-${{ steps.version.outputs.version }}-windows-x64.exe | |
| linux-artifact/pt_mate-${{ steps.version.outputs.version }}-linux-x64.AppImage | |
| ios-macos-artifacts/ios/build/pt_mate-${{ steps.version.outputs.version }}-unsigned.ipa | |
| ios-macos-artifacts/ios/build/pt_mate-${{ steps.version.outputs.version }}.xcarchive.zip | |
| ios-macos-artifacts/build/macos/Build/Products/Release/pt_mate-${{ steps.version.outputs.version }}-macos-x64.dmg | |
| ios-macos-artifacts/build/macos/Build/Products/Release/pt_mate-${{ steps.version.outputs.version }}-macos-x64.app.zip | |
| fail_on_unmatched_files: true | |
| - name: Send Telegram notification | |
| continue-on-error: true | |
| env: | |
| TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }} | |
| TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }} | |
| COMMIT_MSG: ${{ steps.commit_message.outputs.message }} | |
| VERSION: ${{ steps.version.outputs.tag }} | |
| VERSION_NUMBER: ${{ steps.version.outputs.version }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then | |
| echo "TG_BOT_TOKEN or TG_CHAT_ID is not set. Skipping Telegram notification." | |
| exit 0 | |
| fi | |
| npm install telegramify-markdown | |
| ATTACHMENT_MANIFEST=/tmp/tg_attachments.tsv | |
| : > "$ATTACHMENT_MANIFEST" | |
| while IFS='|' read -r label path; do | |
| if [ ! -f "$path" ]; then | |
| echo "Skipping missing Telegram attachment: $path" | |
| continue | |
| fi | |
| printf '%s\n' "$label" >> "$ATTACHMENT_MANIFEST" | |
| done <<EOF | |
| Android APK|android-artifacts/pt_mate-${VERSION_NUMBER}-arm64-v8a.apk | |
| Windows x64|windows-artifact/pt_mate-${VERSION_NUMBER}-windows-x64.exe | |
| Linux x64|linux-artifact/pt_mate-${VERSION_NUMBER}-linux-x64.AppImage | |
| iOS Unsigned IPA|ios-macos-artifacts/ios/build/pt_mate-${VERSION_NUMBER}-unsigned.ipa | |
| macOS DMG|ios-macos-artifacts/build/macos/Build/Products/Release/pt_mate-${VERSION_NUMBER}-macos-x64.dmg | |
| macOS App Zip|ios-macos-artifacts/build/macos/Build/Products/Release/pt_mate-${VERSION_NUMBER}-macos-x64.app.zip | |
| Xcode Archive|ios-macos-artifacts/ios/build/pt_mate-${VERSION_NUMBER}.xcarchive.zip | |
| EOF | |
| export TG_RELEASE_ASSET_LINES | |
| TG_RELEASE_ASSET_LINES=$(awk '{print "- " $0}' "$ATTACHMENT_MANIFEST" || true) | |
| node <<'EOF' | |
| const https = require('https'); | |
| (async () => { | |
| const module = await import('telegramify-markdown'); | |
| const telegramify = module.default || module; | |
| const commitMsg = process.env.COMMIT_MSG; | |
| const version = process.env.VERSION; | |
| const repo = process.env.REPO; | |
| const releaseUrl = `https://github.com/${repo}/releases/tag/${version}`; | |
| const releaseAssetLines = process.env.TG_RELEASE_ASSET_LINES || ''; | |
| const sections = [ | |
| `# 🎉 PT Mate ${version} 发布了!`, | |
| commitMsg, | |
| ]; | |
| if (releaseAssetLines) { | |
| sections.push(`📦 可下载安装包\n${releaseAssetLines}\n请前往 GitHub Release 下载`); | |
| } | |
| sections.push(`📱 [GitHub Release](${releaseUrl})`); | |
| const fullMarkdown = sections.filter(Boolean).join('\n\n'); | |
| const escapedMessage = telegramify(fullMarkdown); | |
| console.log('Processed Message:', escapedMessage); | |
| const payload = { | |
| chat_id: process.env.TG_CHAT_ID, | |
| text: escapedMessage, | |
| parse_mode: 'MarkdownV2', | |
| disable_web_page_preview: false, | |
| }; | |
| const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| function sendRequest(method, endpoint, data) { | |
| return new Promise((resolve, reject) => { | |
| const url = `https://api.telegram.org/bot${process.env.TG_BOT_TOKEN}/${endpoint}`; | |
| const req = https.request( | |
| url, | |
| { | |
| method, | |
| headers: { 'Content-Type': 'application/json' }, | |
| timeout: 15000, | |
| }, | |
| (res) => { | |
| let body = ''; | |
| res.on('data', (chunk) => (body += chunk)); | |
| res.on('end', () => { | |
| try { | |
| resolve(JSON.parse(body)); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }); | |
| }, | |
| ); | |
| req.on('timeout', () => { | |
| req.destroy(); | |
| reject(new Error('Request timed out after 15 seconds')); | |
| }); | |
| req.on('error', reject); | |
| if (data) { | |
| req.write(JSON.stringify(data)); | |
| } | |
| req.end(); | |
| }); | |
| } | |
| async function sendWithRetry(method, endpoint, data, maxRetries = 3) { | |
| for (let index = 0; index < maxRetries; index += 1) { | |
| try { | |
| const response = await sendRequest(method, endpoint, data); | |
| if (response.ok) { | |
| return response; | |
| } | |
| console.error(`Telegram API error (Attempt ${index + 1}):`, JSON.stringify(response)); | |
| } catch (error) { | |
| console.error(`Network error (Attempt ${index + 1}):`, error.message); | |
| } | |
| if (index < maxRetries - 1) { | |
| const waitTime = 2000 * (index + 1); | |
| console.log(`Retrying in ${waitTime}ms...`); | |
| await delay(waitTime); | |
| } | |
| } | |
| throw new Error(`Failed after ${maxRetries} attempts`); | |
| } | |
| try { | |
| console.log('Sending message...'); | |
| const sendRes = await sendWithRetry('POST', 'sendMessage', payload); | |
| const messageId = sendRes.result.message_id; | |
| console.log('Message sent successfully with ID:', messageId); | |
| await sendWithRetry('POST', 'unpinAllChatMessages', { chat_id: process.env.TG_CHAT_ID }); | |
| await sendWithRetry('POST', 'pinChatMessage', { | |
| chat_id: process.env.TG_CHAT_ID, | |
| message_id: messageId, | |
| disable_notification: false, | |
| }); | |
| console.log('Message pinned successfully'); | |
| } catch (error) { | |
| console.error('Final Error:', error.message); | |
| process.exit(1); | |
| } | |
| })(); | |
| EOF | |
| - name: Notify Update Server (POST /api/v1/github/version-update) | |
| env: | |
| UPDATE_SERVER_URL: ${{ vars.UPDATE_SERVER_URL }} | |
| GITHUB_WEBHOOK_SECRET: ${{ secrets.UPDATE_WEBHOOK_SECRET }} | |
| VERSION_TAG: ${{ steps.version.outputs.tag }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| RELEASE_NOTES: ${{ steps.commit_message.outputs.message }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${UPDATE_SERVER_URL:-}" ]; then | |
| echo "UPDATE_SERVER_URL is not set. Skipping update server notification." | |
| exit 0 | |
| fi | |
| DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/tag/${VERSION_TAG}" | |
| ANDROID_DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${VERSION_TAG}/pt_mate-${VERSION}-arm64-v8a.apk" | |
| # Build JSON payload safely | |
| JSON_PAYLOAD=$(jq -n \ | |
| --arg version "$VERSION" \ | |
| --arg release_notes "$RELEASE_NOTES" \ | |
| --arg download_url "$DOWNLOAD_URL" \ | |
| --arg android_download_url "$ANDROID_DOWNLOAD_URL" \ | |
| '{ | |
| version: $version, | |
| release_notes: $release_notes, | |
| download_url: $download_url, | |
| android_download_url: $android_download_url | |
| }') | |
| echo "Sending version update to $UPDATE_SERVER_URL/api/v1/github/version-update" | |
| echo "$JSON_PAYLOAD" | curl -sS -X POST "$UPDATE_SERVER_URL/api/v1/github/version-update" \ | |
| -H "Content-Type: application/json" \ | |
| -H "X-Webhook-Secret: ${GITHUB_WEBHOOK_SECRET}" \ | |
| -d @- | tee /tmp/update_server_response.json | |
| HTTP_OK=$(jq -r '.message? // empty' /tmp/update_server_response.json || true) | |
| if [ -z "$HTTP_OK" ]; then | |
| echo "😒 Update server did not return success message. Response:" >&2 | |
| cat /tmp/update_server_response.json >&2 | |
| exit 1 | |
| else | |
| echo "✅ Update server acknowledged: $HTTP_OK" | |
| fi | |
| update-altsource: | |
| runs-on: ubuntu-latest | |
| needs: create-release | |
| if: success() | |
| steps: | |
| - name: Checkout master | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: master | |
| fetch-depth: 0 | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Update AltSource metadata | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REF_NAME: master | |
| run: python altsource/update_source.py | |
| - name: Commit and push changes | |
| run: | | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git add altsource/AltSource.json | |
| if git diff --cached --quiet; then | |
| echo "No changes detected, skipping commit." | |
| else | |
| git commit -m "chore: update AltSource for ${{ github.ref_name }}" | |
| git push origin master | |
| fi |