From 8f917d9ac6c5352b237368b05889a80dc4cf2c84 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Mon, 21 Sep 2026 08:31:20 -0700 Subject: [PATCH] Sync GitHub organization avatars to Gitea --- mirror.js | 13 +++++++++++++ sync-org-avatar.js | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 sync-org-avatar.js diff --git a/mirror.js b/mirror.js index 53c1848..cb5515d 100644 --- a/mirror.js +++ b/mirror.js @@ -1,3 +1,4 @@ +const syncOrgAvatar = require('./sync-org-avatar'); const { GH_PAT, GITEA_TOKEN, GITEA_URL } = process.env; const G_API = `https://${GITEA_URL.replace(/^https?:\/\//, '')}/api/v1`; const headers = { @@ -41,6 +42,7 @@ const getPages = async (url) => { ]); let allRepos = [...ghUserRepos]; + let avatarFailures = 0; for (const org of ghOrgs) { console.log(`Checking Org: ${org.login}`); @@ -54,6 +56,13 @@ const getPages = async (url) => { await req(`${G_API}/orgs/${org.login}`, headers.GT, 'PATCH', { visibility: 'public' }); } + try { + if (await syncOrgAvatar(org, G_API, headers.GT)) console.log(`Updated Org Avatar: ${org.login}`); + } catch (e) { + avatarFailures++; + console.error(`Failed to sync avatar for ${org.login}:`, e.message); + } + const orgRepos = await getPages(`https://api.github.com/orgs/${org.login}/repos?type=all`); allRepos.push(...orgRepos); } catch (e) { @@ -96,6 +105,10 @@ const getPages = async (url) => { console.error(`Failed to mirror ${r.owner.login}/${r.name}:`, e.message); } } + if (avatarFailures) { + console.error(`Failed to sync ${avatarFailures} organization avatar(s).`); + process.exitCode = 1; + } } catch (err) { console.error('Fatal Error:', err); process.exit(1); diff --git a/sync-org-avatar.js b/sync-org-avatar.js new file mode 100644 index 0000000..4826370 --- /dev/null +++ b/sync-org-avatar.js @@ -0,0 +1,20 @@ +const syncOrgAvatar = async (org, giteaApi, headers) => { + if (!org.avatar_url) return false; + + const avatar = await fetch(org.avatar_url, { signal: AbortSignal.timeout(30_000) }); + if (!avatar.ok) throw new Error(`GitHub avatar download failed: ${avatar.status}`); + + const image = Buffer.from(await avatar.arrayBuffer()); + if (!image.length) throw new Error('GitHub avatar is empty'); + + const upload = await fetch(`${giteaApi}/orgs/${encodeURIComponent(org.login)}/avatar`, { + method: 'POST', + headers, + body: JSON.stringify({ image: image.toString('base64') }), + signal: AbortSignal.timeout(30_000) + }); + if (!upload.ok) throw new Error(`Gitea avatar upload failed: ${upload.status} ${await upload.text()}`); + return true; +}; + +module.exports = syncOrgAvatar;