mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
Remove the presence setting, platform bridge, SDK archive, and SDK preparation from local and CI builds. Reject leftover Discord native libraries in release APK audits.
625 lines
23 KiB
YAML
625 lines
23 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version tag (e.g., v1.0.0)"
|
|
required: true
|
|
default: "v1.0.0"
|
|
|
|
jobs:
|
|
# Get version first (quick job)
|
|
get-version:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
|
|
steps:
|
|
- name: Get version
|
|
id: version
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
INPUT_VERSION: ${{ github.event.inputs.version }}
|
|
run: |
|
|
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
|
|
VERSION="$INPUT_VERSION"
|
|
else
|
|
VERSION="${GITHUB_REF#refs/tags/}"
|
|
fi
|
|
if [[ ! "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
|
|
echo "Invalid release version" >&2
|
|
exit 1
|
|
fi
|
|
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
# Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix)
|
|
VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]')
|
|
if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then
|
|
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
|
echo "Detected pre-release version: $VERSION"
|
|
else
|
|
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
|
echo "Detected stable version: $VERSION"
|
|
fi
|
|
|
|
# Android and iOS build in PARALLEL
|
|
build-android:
|
|
runs-on: ubuntu-latest
|
|
needs: get-version
|
|
|
|
steps:
|
|
- name: Free disk space
|
|
run: |
|
|
# Remove large unused tools (~15GB total). Docker prune was dropped:
|
|
# it took 1-2 minutes and the rm below already frees enough.
|
|
sudo rm -rf /usr/share/dotnet
|
|
sudo rm -rf /opt/ghc
|
|
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
|
sudo rm -rf /usr/local/share/boost
|
|
sudo rm -rf /usr/share/swift
|
|
sudo rm -rf /usr/local/.ghcup
|
|
# Show available space
|
|
df -h
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
|
|
- name: Setup Java
|
|
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
|
|
with:
|
|
distribution: "temurin"
|
|
java-version: "25"
|
|
|
|
- name: Activate pinned Rust toolchain
|
|
working-directory: rust_backend
|
|
run: rustup show
|
|
|
|
- name: Install Rust binding generator dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libclang-dev
|
|
|
|
# Cache Gradle for faster builds
|
|
- name: Cache Gradle
|
|
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: |
|
|
~/.gradle/caches/modules-2
|
|
~/.gradle/wrapper
|
|
# Do not publish transforms of the decrypted SDK in shared caches.
|
|
key: gradle-public-dependencies-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-public-dependencies-${{ runner.os }}-
|
|
|
|
- name: Cache Android NDK
|
|
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: /usr/local/lib/android/sdk/ndk/29.0.14206865
|
|
key: ndk-29.0.14206865
|
|
|
|
- name: Install Android SDK & NDK
|
|
run: |
|
|
# Use pre-installed Android SDK on GitHub runners
|
|
echo "ANDROID_HOME=$ANDROID_HOME"
|
|
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
|
|
|
|
# Accept licenses
|
|
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses || true
|
|
|
|
# Install NDK r29 (supports 16KB page size for Android 15+)
|
|
# Keep the installed platform aligned with compileSdk/targetSdk.
|
|
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
|
|
"ndk;29.0.14206865" \
|
|
"platforms;android-37.0" \
|
|
"build-tools;37.0.0" \
|
|
"cmake;3.22.1"
|
|
|
|
# Set NDK path
|
|
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> "$GITHUB_ENV"
|
|
|
|
- name: Setup Flutter
|
|
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
|
with:
|
|
channel: "stable"
|
|
flutter-version-file: .fvmrc
|
|
cache: true
|
|
|
|
- name: Cache pub dependencies
|
|
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ~/.pub-cache
|
|
key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}
|
|
restore-keys: pub-${{ runner.os }}-
|
|
|
|
- name: Get Flutter dependencies
|
|
run: flutter pub get
|
|
|
|
- name: Generate app icons
|
|
run: dart run flutter_launcher_icons
|
|
|
|
- name: Build release APKs
|
|
run: |
|
|
bash scripts/build_android.sh --target lib/main.dart
|
|
ls -la build/app/outputs/flutter-apk/
|
|
|
|
- name: Sign and verify release APKs (V1/V2/V3)
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
|
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
: "${KEYSTORE_BASE64:?Missing KEYSTORE_BASE64}"
|
|
: "${KEY_ALIAS:?Missing KEY_ALIAS}"
|
|
: "${KEYSTORE_PASSWORD:?Missing KEYSTORE_PASSWORD}"
|
|
export KEY_PASSWORD="${KEY_PASSWORD:-$KEYSTORE_PASSWORD}"
|
|
|
|
umask 077
|
|
signing_dir="$(mktemp -d "$RUNNER_TEMP/spotiflac-signing.XXXXXX")"
|
|
trap 'rm -rf "$signing_dir"' EXIT
|
|
printf '%s' "$KEYSTORE_BASE64" | base64 --decode > "$signing_dir/release.jks"
|
|
apksigner="$ANDROID_HOME/build-tools/37.0.0/apksigner"
|
|
apk_dir=build/app/outputs/flutter-apk
|
|
|
|
for target in arm64-v8a:arm64 armeabi-v7a:arm32; do
|
|
abi="${target%%:*}"
|
|
label="${target##*:}"
|
|
apk="$apk_dir/SpotiFLAC-${VERSION}-${label}.apk"
|
|
"$apksigner" sign \
|
|
--ks "$signing_dir/release.jks" \
|
|
--ks-key-alias "$KEY_ALIAS" \
|
|
--ks-pass env:KEYSTORE_PASSWORD \
|
|
--key-pass env:KEY_PASSWORD \
|
|
--v1-signing-enabled true \
|
|
--v2-signing-enabled true \
|
|
--v3-signing-enabled true \
|
|
--v4-signing-enabled false \
|
|
--out "$apk" "$apk_dir/app-${abi}-release.apk"
|
|
|
|
# Verify the app's supported Android versions, then explicitly
|
|
# exercise V1 too: the manifest's minSdk 24 normally skips it.
|
|
"$apksigner" verify "$apk"
|
|
report="$("$apksigner" verify --verbose --print-certs --min-sdk-version 21 "$apk")"
|
|
for scheme in 1 2 3; do
|
|
if ! grep -Eq "Verified using v${scheme} scheme.*: true" <<< "$report"; then
|
|
echo "ERROR: APK signature V${scheme} did not verify: $apk" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
if grep -qi "Android Debug" <<< "$report"; then
|
|
echo "ERROR: Refusing to publish a debug-signed APK" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "$report" | grep -E '^Verifies$|^Verified using v[123] scheme|certificate SHA-256 digest:'
|
|
done
|
|
|
|
- name: Audit signed Rust APKs before upload
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
run: |
|
|
apk_dir=build/app/outputs/flutter-apk
|
|
python3 scripts/check_backend_apk.py "$apk_dir/SpotiFLAC-${VERSION}-arm64.apk" \
|
|
--backend rust --abis arm64-v8a
|
|
python3 scripts/check_backend_apk.py "$apk_dir/SpotiFLAC-${VERSION}-arm32.apk" \
|
|
--backend rust --abis armeabi-v7a
|
|
for abi in arm64 arm32; do
|
|
"$ANDROID_HOME/build-tools/37.0.0/zipalign" -c -P 16 4 \
|
|
"$apk_dir/SpotiFLAC-${VERSION}-${abi}.apk"
|
|
done
|
|
|
|
- name: Upload APK artifact
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
|
with:
|
|
name: android-apk
|
|
path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk
|
|
|
|
build-ios:
|
|
runs-on: macos-15
|
|
needs: get-version # Only depends on version, NOT android build!
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
|
|
- name: Select Xcode 26.1.1
|
|
run: |
|
|
sudo xcode-select -s /Applications/Xcode_26.1.1.app
|
|
xcodebuild -version
|
|
|
|
- name: Build Rust backend for iOS
|
|
run: bash scripts/build_ios.sh
|
|
|
|
# Cache CocoaPods
|
|
- name: Cache CocoaPods
|
|
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ios/Pods
|
|
key: pods-rust-${{ runner.os }}-${{ hashFiles('ios/Podfile', 'ios/Podfile.lock', 'rust_backend/SpotiFLACBackend.podspec', 'rust_backend/Cargo.lock') }}
|
|
restore-keys: pods-rust-${{ runner.os }}-
|
|
|
|
- name: Setup Flutter
|
|
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
|
with:
|
|
channel: "stable"
|
|
flutter-version-file: .fvmrc
|
|
cache: true
|
|
|
|
- name: Cache pub dependencies
|
|
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
|
|
with:
|
|
path: ~/.pub-cache
|
|
key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}
|
|
restore-keys: pub-${{ runner.os }}-
|
|
|
|
- name: Get Flutter dependencies
|
|
run: flutter pub get
|
|
|
|
- name: Normalize ffmpeg plugin shell scripts (strip CRLF)
|
|
run: |
|
|
find "$HOME/.pub-cache/hosted" -path "*ffmpeg_kit_flutter_new_full*/scripts/*.sh" -type f -print0 |
|
|
while IFS= read -r -d '' f; do
|
|
perl -pi -e 's/\r$//' "$f"
|
|
chmod +x "$f"
|
|
echo "Normalized line endings: $f"
|
|
done
|
|
|
|
- name: Generate app icons
|
|
run: dart run flutter_launcher_icons
|
|
|
|
- name: Configure Rust application pods
|
|
working-directory: ios
|
|
run: pod install
|
|
|
|
- name: Build iOS (unsigned)
|
|
run: |
|
|
# Build Flutter iOS without codesigning
|
|
flutter build ios --release --no-codesign --config-only --target lib/main.dart \
|
|
--dart-define="GIT_COMMIT=$(git rev-parse --short=8 HEAD)"
|
|
|
|
# Use xcodebuild with code signing disabled
|
|
cd ios
|
|
xcodebuild -workspace Runner.xcworkspace \
|
|
-scheme Runner \
|
|
-configuration Release \
|
|
-sdk iphoneos \
|
|
-destination 'generic/platform=iOS' \
|
|
-archivePath build/Runner.xcarchive \
|
|
archive \
|
|
CODE_SIGNING_ALLOWED=NO \
|
|
CODE_SIGNING_REQUIRED=NO \
|
|
CODE_SIGN_IDENTITY="" \
|
|
DEVELOPMENT_TEAM=""
|
|
|
|
- name: Audit Rust iOS archive before packaging
|
|
run: |
|
|
python3 -m unittest discover -s scripts -p 'test_check_backend_ios.py'
|
|
python3 scripts/check_backend_ios.py \
|
|
ios/build/Runner.xcarchive/Products/Applications/Runner.app \
|
|
--dsym ios/build/Runner.xcarchive/dSYMs/Runner.app.dSYM \
|
|
--backend rust --archs arm64 --platform ios --release
|
|
|
|
- name: Create IPA
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
run: |
|
|
mkdir -p build/ios/ipa
|
|
cd ios/build/Runner.xcarchive/Products/Applications
|
|
mkdir Payload
|
|
cp -r Runner.app Payload/
|
|
# Use absolute path to avoid relative path issues
|
|
zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload
|
|
rm -rf Payload
|
|
|
|
- name: Verify IPA created
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
run: |
|
|
ls -la build/ios/ipa/
|
|
if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then
|
|
echo "ERROR: IPA not created!"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload IPA artifact
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
|
with:
|
|
name: ios-ipa
|
|
path: build/ios/ipa/SpotiFLAC-*.ipa
|
|
|
|
create-release:
|
|
runs-on: ubuntu-latest
|
|
needs: [get-version, build-android, build-ios]
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
fetch-depth: 0 # Full history needed for git-cliff
|
|
|
|
- name: Generate changelog with git-cliff
|
|
id: changelog
|
|
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
|
|
with:
|
|
config: cliff.toml
|
|
args: --latest --strip header
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
OUTPUT: /tmp/changelog.txt
|
|
|
|
- name: Show generated changelog
|
|
run: |
|
|
echo "Generated changelog:"
|
|
cat /tmp/changelog.txt
|
|
|
|
- name: Download Android APK
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
name: android-apk
|
|
path: ./release
|
|
|
|
- name: Download iOS IPA
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
name: ios-ipa
|
|
path: ./release
|
|
|
|
- name: Prepare release body
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
REPO_OWNER: ${{ github.repository_owner }}
|
|
REPO_NAME: ${{ github.event.repository.name }}
|
|
run: |
|
|
CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD)
|
|
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true)
|
|
|
|
# Start with git-cliff changelog, but replace its compare footer with a
|
|
# deterministic previous-tag lookup from git.
|
|
sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt
|
|
|
|
if [ -n "$PREVIOUS_TAG" ]; then
|
|
printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \
|
|
"$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \
|
|
>> /tmp/release_body.txt
|
|
fi
|
|
|
|
# Append download section
|
|
cat >> /tmp/release_body.txt << FOOTER
|
|
|
|
---
|
|
|
|
### Downloads
|
|
|
|
#### Android
|
|
- **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices)
|
|
- **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices)
|
|
|
|
#### iOS
|
|
- **iOS**: \`SpotiFLAC-${VERSION}-ios-unsigned.ipa\` (sideload required)
|
|
|
|
### Installation
|
|
**Android**: Enable "Install from unknown sources" and install the APK
|
|
**iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA
|
|
FOOTER
|
|
|
|
echo "Release body:"
|
|
cat /tmp/release_body.txt
|
|
|
|
- name: Create Release
|
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
|
|
with:
|
|
tag_name: ${{ needs.get-version.outputs.version }}
|
|
name: SpotiFLAC-Mobile ${{ needs.get-version.outputs.version }}
|
|
body_path: /tmp/release_body.txt
|
|
files: ./release/*
|
|
draft: false
|
|
prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }}
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
update-altstore:
|
|
runs-on: ubuntu-latest
|
|
needs: [get-version, build-ios, create-release]
|
|
if: ${{ needs.get-version.outputs.is_prerelease != 'true' }}
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout main branch
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
ref: main
|
|
|
|
- name: Download iOS IPA
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
name: ios-ipa
|
|
path: ./release
|
|
|
|
- name: Update apps.json
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
VERSION_NUM="${VERSION#v}"
|
|
DATE=$(date -u +%Y-%m-%d)
|
|
IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1)
|
|
|
|
if [ -z "$IPA_FILE" ]; then
|
|
echo "WARNING: IPA file not found, skipping apps.json update"
|
|
exit 0
|
|
fi
|
|
|
|
IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE")
|
|
BUNDLE_ID=$(python3 - "$IPA_FILE" <<'PY'
|
|
import plistlib
|
|
import sys
|
|
import zipfile
|
|
|
|
with zipfile.ZipFile(sys.argv[1]) as ipa:
|
|
info_path = next(name for name in ipa.namelist()
|
|
if name.startswith("Payload/")
|
|
and name.count("/") == 2
|
|
and name.endswith(".app/Info.plist"))
|
|
print(plistlib.loads(ipa.read(info_path))["CFBundleIdentifier"])
|
|
PY
|
|
)
|
|
|
|
if [ ! -f apps.json ]; then
|
|
echo "WARNING: apps.json not found on main, skipping"
|
|
exit 0
|
|
fi
|
|
|
|
jq --arg ver "$VERSION_NUM" \
|
|
--arg bundle "$BUNDLE_ID" \
|
|
--arg date "$DATE" \
|
|
--arg url "https://github.com/${REPOSITORY}/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \
|
|
--argjson size "$IPA_SIZE" \
|
|
'.apps[0].bundleIdentifier = $bundle | .apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \
|
|
apps.json > apps.json.tmp && mv apps.json.tmp apps.json
|
|
|
|
echo "Updated apps.json:"
|
|
cat apps.json
|
|
|
|
- name: Commit and push
|
|
env:
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git add apps.json
|
|
git diff --cached --quiet && echo "No changes to commit" || \
|
|
(git commit -m "chore: update AltStore source to ${VERSION}" && git push)
|
|
|
|
notify-telegram:
|
|
runs-on: ubuntu-latest
|
|
needs: [get-version, create-release]
|
|
if: ${{ needs.get-version.outputs.is_prerelease != 'true' }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Download Android APK
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
name: android-apk
|
|
path: ./release
|
|
|
|
- name: Download iOS IPA
|
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
|
with:
|
|
name: ios-ipa
|
|
path: ./release
|
|
|
|
- name: Generate changelog with git-cliff for Telegram
|
|
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
|
|
with:
|
|
config: cliff.toml
|
|
args: --latest --strip all
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
OUTPUT: /tmp/cliff_tg.txt
|
|
|
|
- name: Convert changelog for Telegram
|
|
id: changelog
|
|
run: |
|
|
if [ ! -s /tmp/cliff_tg.txt ]; then
|
|
echo "See release notes on GitHub for details." > /tmp/changelog.txt
|
|
else
|
|
# Convert Markdown to Telegram HTML
|
|
CHANGELOG=$(cat /tmp/cliff_tg.txt | \
|
|
sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \
|
|
sed '/^\*\*Full Changelog\*\*/d' | \
|
|
sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \
|
|
sed 's/ by @[A-Za-z0-9_-]\+//g' | \
|
|
sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \
|
|
sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \
|
|
sed 's/&/\&/g' | \
|
|
sed 's/</\</g' | \
|
|
sed 's/>/\>/g' | \
|
|
sed 's/\*\*\([^*]*\)\*\*/<b>\1<\/b>/g' | \
|
|
sed 's/^### \(.*\)$/<b>\1<\/b>/g' | \
|
|
sed 's/^## \(.*\)$/<b>\1<\/b>/g' | \
|
|
sed 's/^- /• /g')
|
|
|
|
# Truncate for Telegram 4096 char limit
|
|
CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d')
|
|
echo "$CHANGELOG" > /tmp/changelog.txt
|
|
fi
|
|
|
|
echo "Telegram changelog:"
|
|
cat /tmp/changelog.txt
|
|
|
|
- name: Send to Telegram Channel
|
|
env:
|
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }}
|
|
VERSION: ${{ needs.get-version.outputs.version }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
CHANGELOG=$(cat /tmp/changelog.txt)
|
|
|
|
# Find APK files
|
|
ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1)
|
|
ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1)
|
|
|
|
# Prepare message with changelog (HTML format)
|
|
printf '%s\n' \
|
|
"<b>SpotiFLAC Mobile ${VERSION} Released!</b>" \
|
|
"" \
|
|
"<b>What's New:</b>" \
|
|
"${CHANGELOG}" \
|
|
"" \
|
|
"<a href=\"https://github.com/${REPOSITORY}/releases/tag/${VERSION}\">View Release Notes</a>" \
|
|
> /tmp/telegram_message.txt
|
|
|
|
MESSAGE=$(cat /tmp/telegram_message.txt)
|
|
|
|
# Send message first (using HTML parse mode)
|
|
# Use --data-urlencode for proper encoding of special chars (+, &, etc.)
|
|
# Use || true to ensure file uploads continue even if message fails
|
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
--data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \
|
|
--data-urlencode "text=${MESSAGE}" \
|
|
--data-urlencode "parse_mode=HTML" \
|
|
--data-urlencode "disable_web_page_preview=true" || true
|
|
|
|
# Upload arm64 APK to channel
|
|
if [ -f "$ARM64_APK" ]; then
|
|
echo "Uploading arm64 APK to Telegram..."
|
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
|
|
-F chat_id="${TELEGRAM_CHANNEL_ID}" \
|
|
-F document=@"${ARM64_APK}" \
|
|
-F caption="SpotiFLAC Mobile ${VERSION} - arm64 (recommended)"
|
|
fi
|
|
|
|
# Upload arm32 APK to channel
|
|
if [ -f "$ARM32_APK" ]; then
|
|
echo "Uploading arm32 APK to Telegram..."
|
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
|
|
-F chat_id="${TELEGRAM_CHANNEL_ID}" \
|
|
-F document=@"${ARM32_APK}" \
|
|
-F caption="SpotiFLAC Mobile ${VERSION} - arm32"
|
|
fi
|
|
|
|
# Upload iOS IPA to channel
|
|
IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1)
|
|
if [ -f "$IOS_IPA" ]; then
|
|
echo "Uploading iOS IPA to Telegram..."
|
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
|
|
-F chat_id="${TELEGRAM_CHANNEL_ID}" \
|
|
-F document=@"${IOS_IPA}" \
|
|
-F caption="SpotiFLAC Mobile ${VERSION} - iOS (unsigned, sideload required)"
|
|
fi
|
|
|
|
echo "Telegram notification sent!"
|