/
oinktech
/
readmef
Обзор
Документация
Войти
/
oinktech
/
readmef
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
master
server/providers/github.js
129 строк
4 KB
TwixoffStudio
Create: package-lock.json, README.md, app.js, index.html, styles.css, index.js, aggregate.js, cache.js, readmeBuilder.js, svg.js, theme.js, base.js, github.js, gitverse.js, index.js, routes.js, .env.example, LICENSE, package.json
05 июл 2026, 13:09
Верифицирован
05 июл 2026, 13:09
dc3c4b9
Код
Авторство
О чём код?
'use strict'; const fetch = require('node-fetch'); const BaseProvider = require('./base'); const API = 'https://api.github.com'; function headers(token) { const h = { Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', 'User-Agent': 'ReadmeForge', }; if (token) h.Authorization = `Bearer ${token}`; return h; } async function gh(path, token, opts = {}) { const res = await fetch(`${API}${path}`, { ...opts, headers: { ...headers(token), ...(opts.headers || {}) }, }); if (!res.ok) { const body = await res.text().catch(() => ''); const err = new Error(`GitHub API ${res.status}: ${body.slice(0, 300)}`); err.status = res.status; throw err; } if (res.status === 204) return null; return res.json(); } class GitHubProvider extends BaseProvider { async getViewer() { const u = await gh('/user', this.token); return { login: u.login, name: u.name || u.login, avatarUrl: u.avatar_url, bio: u.bio || '', htmlUrl: u.html_url, followers: u.followers, publicRepos: u.public_repos, company: u.company || '', location: u.location || '', blog: u.blog || '', }; } async listRepos() { const repos = []; let page = 1; // до 5 страниц (500 репозиториев) — вполне достаточно для профиля while (page <= 5) { const batch = await gh( `/user/repos?per_page=100&page=${page}&sort=pushed&affiliation=owner`, this.token ); repos.push(...batch); if (batch.length < 100) break; page += 1; } return repos.map((r) => ({ name: r.name, fullName: r.full_name, description: r.description || '', language: r.language || null, stars: r.stargazers_count || 0, forks: r.forks_count || 0, updatedAt: r.pushed_at, private: r.private, htmlUrl: r.html_url, fork: r.fork, topics: r.topics || [], })); } async getFileSha(owner, repo, path) { try { const data = await gh( `/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`, this.token ); return data && data.sha ? data.sha : null; } catch (e) { if (e.status === 404) return null; throw e; } } async putFile(owner, repo, path, content, message, sha) { const body = { message, content: Buffer.from(content, 'utf8').toString('base64'), }; if (sha) body.sha = sha; return gh(`/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`, this.token, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); } /** Публичные события пользователя — используем для тепловой карты активности без OAuth-скоупов */ async getPublicEvents(username) { const events = await gh(`/users/${username}/events/public?per_page=100`, this.token); return Array.isArray(events) ? events : []; } async ensureRepo(name) { try { return await gh(`/repos/${(await this.getViewer()).login}/${name}`, this.token); } catch (e) { if (e.status !== 404) throw e; return gh('/user/repos', this.token, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description: 'Профильный README ✨ создан через ReadmeForge', auto_init: true, private: false, }), }); } } } module.exports = GitHubProvider;