From 624fa898e25975a147cc4fffefad2c791d2312a5 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 14 Oct 2025 10:23:08 +1100 Subject: [PATCH] feat: add release stats for android --- .github/workflows/fetch-release-stats.yml | 27 ++-- release_stats/fetch-release-stats.ts | 175 +++++++++++++++++++++- 2 files changed, 182 insertions(+), 20 deletions(-) diff --git a/.github/workflows/fetch-release-stats.yml b/.github/workflows/fetch-release-stats.yml index 9885b6a..28c7b74 100644 --- a/.github/workflows/fetch-release-stats.yml +++ b/.github/workflows/fetch-release-stats.yml @@ -26,9 +26,9 @@ jobs: run: | cd release_stats && npx tsx fetch-release-stats.ts - - name: Display CSV in summary + - name: Display Desktop CSV in summary run: | - echo "# Release Download Statistics" >> $GITHUB_STEP_SUMMARY + echo "# Desktop Release Download Statistics" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Snapshot Date:** $(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -41,14 +41,17 @@ jobs: print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |" }' release_stats/session-desktop-release-stats.csv >> $GITHUB_STEP_SUMMARY - - name: Upload CSV artifact - uses: actions/upload-artifact@v4 - with: - name: release-stats-${{ github.run_number }} - path: release_stats/session-desktop-release-stats.csv - retention-days: 5 - - - name: Display CSV preview + - name: Display Android CSV in summary run: | - echo "CSV Preview:" - head -n 5 release_stats/session-desktop-release-stats.csv + echo "# Android Release Download Statistics" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Snapshot Date:** $(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Convert CSV to markdown table + awk -F',' 'NR==1 { + print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |" + print "|---|---|---|---|---|---|---|---|---|---|---|" + } NR>1 { + print "| " $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 " | " $7 " | " $8 " | " $9 " | " $10 " | " $11 " |" + }' release_stats/session-android-release-stats.csv >> $GITHUB_STEP_SUMMARY diff --git a/release_stats/fetch-release-stats.ts b/release_stats/fetch-release-stats.ts index 6a75308..43a0515 100644 --- a/release_stats/fetch-release-stats.ts +++ b/release_stats/fetch-release-stats.ts @@ -2,8 +2,8 @@ import https from 'https'; import fs from 'fs'; // GitHub API configuration -const REPO = 'session-foundation/session-desktop'; -const API_URL = `https://api.github.com/repos/${REPO}/releases`; +const DESKTOP_API_URL = `https://api.github.com/repos/session-foundation/session-desktop/releases`; +const ANDROID_API_URL = `https://api.github.com/repos/session-foundation/session-android/releases`; // Types interface Asset { @@ -17,7 +17,7 @@ interface Release { assets: Asset[]; } -interface ReleaseStats { +interface DesktopReleaseStats { version: string; snapshotDate: string; dateCaptured: string; @@ -90,10 +90,10 @@ function getCurrentDate(): string { } // Main function -async function generateReleaseStatsCSV(): Promise { +async function generateDesktopReleaseStatsCSV(): Promise { try { console.log('Fetching releases from GitHub...'); - const releases = await fetchJSON(API_URL); + const releases = await fetchJSON(DESKTOP_API_URL); // Take only the last 10 releases const last10Releases = releases.slice(0, 10); @@ -113,7 +113,7 @@ async function generateReleaseStatsCSV(): Promise { // Data rows for (const release of last10Releases) { - const stats: ReleaseStats = { + const stats: DesktopReleaseStats = { version: release.tag_name.replace(/^v/, ''), // Remove 'v' prefix snapshotDate: snapshotDate, dateCaptured: release.published_at.split('T')[0], // Get YYYY-MM-DD @@ -174,5 +174,164 @@ async function generateReleaseStatsCSV(): Promise { } } -// Run the script -generateReleaseStatsCSV(); +// Android-specific types +interface AndroidReleaseStats { + version: string; + snapshotDate: string; + dateCaptured: string; + aabDownloads: number; + apkArm64Downloads: number; + apkArmv7aDownloads: number; + apkUniversalHuaweiDownloads: number; + apkUniversalPlayDownloads: number; + apkX86Downloads: number; + apkX86_64Downloads: number; +} + +// Helper function to count downloads for Android APKs with specific architecture +function countAndroidDownloadsByPattern( + assets: any[], + extension: string, + architecture: string, + buildType: string = 'play-release' +): number { + return assets + .filter( + (asset) => + asset.name.endsWith(extension) && + asset.name.includes(architecture) && + asset.name.includes(buildType) + ) + .reduce((sum, asset) => sum + asset.download_count, 0); +} + +// Helper function for AAB files +function countAABDownloads(assets: any[]): number { + return assets + .filter( + (asset) => + asset.name.endsWith('.aab') && asset.name.includes('play-release') + ) + .reduce((sum, asset) => sum + asset.download_count, 0); +} + +// Helper function for universal APKs +function countUniversalAPKDownloads( + assets: any[], + storeType: 'play' | 'huawei' +): number { + return assets + .filter( + (asset) => + asset.name.endsWith('.apk') && + asset.name.includes('universal') && + asset.name.includes(`${storeType}-release`) + ) + .reduce((sum, asset) => sum + asset.download_count, 0); +} + +// Helper function to count downloads for x86 (excluding x86_64) +function countX86Downloads(assets: any[]): number { + return assets + .filter( + (asset) => + asset.name.endsWith('.apk') && + asset.name.includes('x86') && + !asset.name.includes('x86_64') && // Explicitly exclude x86_64 + asset.name.includes('play-release') + ) + .reduce((sum, asset) => sum + asset.download_count, 0); +} + +// Main function for Android releases +async function generateAndroidReleaseStatsCSV(): Promise { + try { + console.log('Fetching releases from GitHub...'); + const releases = await fetchJSON(ANDROID_API_URL); + + // Take only the last 10 releases + const last10Releases = releases.slice(0, 10); + + console.log(`Processing ${last10Releases.length} releases...`); + + const snapshotDate = getCurrentDate(); + console.log(`Snapshot date: ${snapshotDate}`); + + // Build CSV data + const csvRows: string[] = []; + + // Header row + csvRows.push( + 'version,snapshot_date,release_date,.aab,.apk_arm64,.apk_armv7a,.apk_universal_huawei,.apk_universal_play,.apk_x86,.apk_x86_64' + ); + + // Data rows + for (const release of last10Releases) { + const stats: AndroidReleaseStats = { + version: release.tag_name.replace(/^v/, ''), + snapshotDate: snapshotDate, + dateCaptured: release.published_at.split('T')[0], + aabDownloads: countAABDownloads(release.assets), + apkArm64Downloads: countAndroidDownloadsByPattern( + release.assets, + '.apk', + 'arm64-v8a' + ), + apkArmv7aDownloads: countAndroidDownloadsByPattern( + release.assets, + '.apk', + 'armeabi-v7a' + ), + apkUniversalHuaweiDownloads: countUniversalAPKDownloads( + release.assets, + 'huawei' + ), + apkUniversalPlayDownloads: countUniversalAPKDownloads( + release.assets, + 'play' + ), + apkX86Downloads: countX86Downloads(release.assets), // Use the dedicated function + apkX86_64Downloads: countAndroidDownloadsByPattern( + release.assets, + '.apk', + 'x86_64' + ), + }; + + csvRows.push( + [ + stats.version, + stats.snapshotDate, + stats.dateCaptured, + stats.aabDownloads, + stats.apkArm64Downloads, + stats.apkArmv7aDownloads, + stats.apkUniversalHuaweiDownloads, + stats.apkUniversalPlayDownloads, + stats.apkX86Downloads, + stats.apkX86_64Downloads, + ].join(',') + ); + } + + // Write to file + const csvContent = csvRows.join('\n'); + const filename = 'session-android-release-stats.csv'; + + fs.writeFileSync(filename, csvContent); + + console.log(`\nCSV file generated: ${filename}`); + console.log(`Total releases processed: ${last10Releases.length}`); + console.log('\nFull content:'); + console.log(csvRows.join('\n')); + } catch (error) { + console.error( + 'Error:', + error instanceof Error ? error.message : 'Unknown error' + ); + process.exit(1); + } +} + +generateDesktopReleaseStatsCSV(); +generateAndroidReleaseStatsCSV();