Files
OpenTerminal/scripts/download-stats.cjs

126 lines
4.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Download stats: Gitea releases (domestic channel) + GitHub releases (intl).
// Usage: node scripts/download-stats.cjs
// Credentials from .env (repo root, gitignored): GIT_TOKEN, GH_TOKEN.
// GitHub traffic honors HTTPS_PROXY / HTTP_PROXY (env or .env); Gitea is
// always direct — same policy as scripts/release.cjs.
//
// Caveats the printed report also states:
// - The auto-update channel (Gitea generic package `openterminal-update`)
// does not expose download counts, so updater traffic is not included.
// - Counts reflect currently existing release assets only; re-uploaded
// assets restart from zero.
const fs = require('node:fs')
const path = require('node:path')
const ROOT = path.join(__dirname, '..')
const env = Object.fromEntries(
fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')
.map((l) => l.trim())
.filter((l) => l.includes('=') && !l.startsWith('#'))
.map((l) => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()] })
)
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || env.HTTPS_PROXY || env.PROXY
// Fallback for shells without the env set: the local proxy documented in
// AGENTS.md is how GitHub traffic gets out on this network; a direct call
// dies with ECONNRESET.
const ghAgent = (p) => (p ? new (require('undici').ProxyAgent)(p) : undefined)
const GH_FALLBACK_PROXY = 'http://127.0.0.1:7897'
const GITEA = 'https://git.codingplan.site/api/v1/repos/admin/OpenTerminal'
const GITHUB = 'https://api.github.com/repos/billowliu2/OpenTerminal'
/** Sum download counts per release, split by exe/msi/other. */
function tally(releases) {
let total = 0, exe = 0, msi = 0
const rows = []
for (const r of releases) {
let n = 0, e = 0, m = 0
for (const a of r.assets || []) {
n += a.download_count || 0
if (a.name.endsWith('.exe')) e += a.download_count || 0
else if (a.name.endsWith('.msi')) m += a.download_count || 0
}
rows.push({ tag: r.tag_name || r.name, total: n, exe: e, msi: m })
total += n; exe += e; msi += m
}
rows.sort((a, b) => b.total - a.total)
return { rows, total, exe, msi }
}
async function giteaReleases() {
// Paginated: Gitea caps `limit` at 50.
const all = []
for (let page = 1; ; page++) {
const resp = await fetch(`${GITEA}/releases?limit=50&page=${page}`, {
headers: { Authorization: `token ${env.GIT_TOKEN}` }
})
if (!resp.ok) throw new Error(`gitea HTTP ${resp.status}`)
const batch = await resp.json()
all.push(...batch)
if (batch.length < 50) return all
}
}
async function githubFetch(url) {
const headers = {
Accept: 'application/vnd.github+json',
'User-Agent': 'openterminal-download-stats',
...(env.GH_TOKEN ? { Authorization: `Bearer ${env.GH_TOKEN}` } : {})
}
const attempts = [{}, { dispatcher: ghAgent(proxy) }, { dispatcher: ghAgent(GH_FALLBACK_PROXY) }]
let lastErr
for (const extra of attempts) {
try {
const resp = await fetch(url, { headers, ...extra })
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
return resp.json()
} catch (e) { lastErr = e }
}
throw lastErr
}
async function githubReleases() {
const all = []
for (let page = 1; ; page++) {
const batch = await githubFetch(`${GITHUB}/releases?per_page=100&page=${page}`)
all.push(...batch)
if (batch.length < 100) return all
}
}
function print(source, t) {
console.log(`\n== ${source} ==`)
for (const r of t.rows) {
if (!r.total) continue
console.log(`${r.tag.padEnd(12)} total ${String(r.total).padStart(5)} exe ${String(r.exe).padStart(5)} msi ${String(r.msi).padStart(4)}`)
}
console.log(` 小计 ${t.total}(exe ${t.exe} / msi ${t.msi} / 其他 ${t.total - t.exe - t.msi})`)
}
async function main() {
let gh, gt
const fails = []
try { gt = tally(await giteaReleases()) } catch (e) { fails.push(`Gitea: ${e.message}`) }
try { gh = tally(await githubReleases()) } catch (e) { fails.push(`GitHub: ${e.message}`) }
if (gt) print('国内 · Gitea Releases', gt)
if (gh) print('国外 · GitHub Releases', gh)
if (gt && gh) {
const total = gt.total + gh.total
console.log(`\n合计(可计数部分): ${total}`)
}
console.log('\n口径说明:')
console.log('- 应用内自动更新走 Gitea 通用包通道(openterminal-update),该通道无下载计数接口,未计入。')
console.log('- 计数只反映当前存活的 release 资产,重传过的资产从零累计。')
if (fails.length) {
console.error('\n部分来源失败:')
for (const f of fails) console.error(' -', f)
process.exitCode = 1
}
}
main()