Update release.yml #4
This file contains 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: Update Release Stats | |
on: | |
schedule: | |
- cron: '0 0 * * *' # Runs at 00:00 UTC every day | |
workflow_dispatch: # Allows manual trigger from the GitHub UI | |
push: | |
branches: | |
- release-test | |
jobs: | |
update-stats: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Check branch | |
run: | | |
echo "Current branch: ${{ github.ref }}" | |
if [[ "${{ github.ref }}" != "refs/heads/release-test" ]]; then | |
echo "This workflow should only run on release-test branch" | |
exit 1 | |
fi | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: '20' | |
- name: Create stats script | |
run: | | |
cat > update-stats.mjs << 'EOL' | |
import { writeFile } from "node:fs/promises"; | |
import { env, exit } from "node:process"; | |
const { | |
GITHUB_API_URL = "https://api.github.com", | |
GITHUB_TOKEN | |
} = env; | |
const REPOSITORIES = [ | |
"ShokoAnime/ShokoServer", | |
"ShokoAnime/ShokoDesktop", | |
"ShokoAnime/Shokofin", | |
"ShokoAnime/Shokodi", | |
"Cazzar/ShokoMetadata.bundle", | |
"natyusha/ShokoRelay.bundle", | |
"bigretromike/nakamori", | |
]; | |
const now = Date.now(); | |
async function fetchRepositoryStats(repository) { | |
const releases = []; | |
let pageCounter = 0; | |
let responseJson = []; | |
try { | |
do { | |
if (pageCounter > 0) { | |
await new Promise((resolve) => setTimeout(resolve, 100)); | |
} | |
console.log(`Fetching page ${pageCounter + 1} for ${repository}…`); | |
const response = await fetch( | |
`${GITHUB_API_URL}/repos/${repository}/releases?per_page=100&page=${++pageCounter}`, | |
{ | |
headers: { | |
'Authorization': `Bearer ${GITHUB_TOKEN}`, | |
'Accept': 'application/vnd.github.v3+json', | |
'X-GitHub-Api-Version': '2022-11-28' | |
} | |
} | |
); | |
if (!response.ok) { | |
throw new Error(`GitHub API request failed for ${repository}: ${response.status} ${response.statusText}`); | |
} | |
responseJson = await response.json(); | |
for (const release of responseJson) { | |
if (!release.assets || release.assets.length === 0) { | |
console.log(`Skipping release ${release.tag_name} in ${repository} - no assets found`); | |
continue; | |
} | |
releases.push({ | |
version: release.tag_name.replace(/[vV]/, "").replace("-dev.", "."), | |
tag: release.tag_name, | |
releasedAt: new Date(release.published_at).toISOString(), | |
pre: release.prerelease, | |
downloadCount: release.assets[0].download_count, | |
downloadUrl: release.assets[0].browser_download_url, | |
url: release.html_url, | |
}); | |
} | |
} while (responseJson.length === 100); | |
// Find stable and dev releases | |
const stableRelease = releases.find(release => !release.pre); | |
const devRelease = releases.find(release => release.pre); | |
return { | |
fetchedAt: new Date(now).toISOString(), | |
stats: { | |
overallDownloads: releases.reduce((total, release) => total + release.downloadCount, 0), | |
devDownloads: releases.filter((release) => release.pre) | |
.reduce((total, release) => total + release.downloadCount, 0), | |
stableDownloads: releases.filter((release) => !release.pre) | |
.reduce((total, release) => total + release.downloadCount, 0), | |
}, | |
stable: stableRelease ? { | |
version: stableRelease.version, | |
tag: stableRelease.tag, | |
releasedAt: stableRelease.releasedAt, | |
downloadCount: stableRelease.downloadCount, | |
downloadUrl: stableRelease.downloadUrl, | |
url: stableRelease.url | |
} : null, | |
dev: devRelease ? { | |
version: devRelease.version, | |
tag: devRelease.tag, | |
releasedAt: devRelease.releasedAt, | |
downloadCount: devRelease.downloadCount, | |
downloadUrl: devRelease.downloadUrl, | |
url: devRelease.url | |
} : null | |
}; | |
} catch (error) { | |
console.error(`Error fetching stats for ${repository}:`, error); | |
return null; | |
} | |
} | |
try { | |
const allStats = {}; | |
for (const repo of REPOSITORIES) { | |
console.log(`Processing repository: ${repo}`); | |
const repoName = repo.split('/')[1]; | |
const stats = await fetchRepositoryStats(repo); | |
if (stats) { | |
allStats[repoName] = stats; | |
} | |
} | |
await writeFile("./releases.json", JSON.stringify(allStats, null, 2)); | |
console.log("Successfully updated releases.json"); | |
} catch (error) { | |
console.error("Error updating release stats:", error); | |
exit(1); | |
} | |
EOL | |
- name: Run stats script | |
run: node update-stats.mjs | |
env: | |
GITHUB_TOKEN: ${{ secrets.GH_PAT }} | |
- name: Upload releases.json to server | |
shell: pwsh | |
env: | |
FTP_USERNAME: ${{ secrets.FTP_USERNAME }} | |
FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} | |
FTP_SERVER: ${{ secrets.FTP_SERVER }} | |
run: | | |
$user = $env:FTP_USERNAME | |
$password = $env:FTP_PASSWORD | |
$ftp_server = $env:FTP_SERVER | |
$current = $env:GITHUB_WORKSPACE | |
$fileBytes = [System.IO.File]::ReadAllBytes("$current/releases.json") | |
$request = [System.Net.WebRequest]::Create("$ftp_server/files/releases.json") | |
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile | |
$request.Credentials = New-Object System.Net.NetworkCredential($user, $password) | |
$request.UseBinary = $true | |
$request.ContentLength = $fileBytes.Length | |
try { | |
$requestStream = $request.GetRequestStream() | |
$requestStream.Write($fileBytes, 0, $fileBytes.Length) | |
$requestStream.Close() | |
$response = $request.GetResponse() | |
$response.Close() | |
Write-Host "Successfully uploaded releases.json" | |
} | |
catch { | |
Write-Error "Failed to upload releases.json: $_" | |
throw $_ | |
} |