126 lines
4.6 KiB
JavaScript
126 lines
4.6 KiB
JavaScript
// 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()
|