# 🌐 API リファレンス GitHub Power ScouterのAPI使用方法とリファレンス。GitHub REST API v3の効率的な活用方法を詳しく解説します。 ## 🚀 API概要 ### GitHub REST API v3 基本情報 | 項目 | 詳細 | |------|------| | **ベースURL** | `https://api.github.com` | | **認証** | Personal Access Token | | **レート制限** | 未認証: 60req/h, 認証済み: 5000req/h | | **ドキュメント** | [GitHub API Docs](https://docs.github.com/en/rest) | ### 使用エンドポイント ```mermaid graph TB A[GitHub API] --> B[Users API] A --> C[Repositories API] A --> D[Events API] A --> E[Rate Limit API] B --> F[/users/{username}] C --> G[/users/{username}/repos] C --> H[/repos/{owner}/{repo}/languages] D --> I[/users/{username}/events/public] E --> J[/rate_limit] ``` ## 🔧 API Client実装 ### 基本的なAPIクライアント ```typescript export class GitHubApiClient { private baseUrl = 'https://api.github.com'; private token?: string; constructor(token?: string) { this.token = token; } private getHeaders(): HeadersInit { const headers: HeadersInit = { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'GitHub-Power-Scouter/2.0', }; if (this.token) { headers['Authorization'] = `token ${this.token}`; } return headers; } private async request(endpoint: string): Promise { const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { headers: this.getHeaders(), }); if (!response.ok) { throw new ApiError( `API request failed: ${response.status} ${response.statusText}`, response.status, await response.json() ); } return response.json(); } // レート制限情報を取得 async getRateLimit(): Promise { return this.request('/rate_limit'); } // ユーザー情報を取得 async getUser(username: string): Promise { return this.request(`/users/${username}`); } // ユーザーのリポジトリ一覧を取得 async getUserRepos(username: string, options?: RepoOptions): Promise { const params = new URLSearchParams({ per_page: '100', sort: 'stars', direction: 'desc', ...options, }); return this.request(`/users/${username}/repos?${params}`); } // リポジトリの言語統計を取得 async getRepoLanguages(owner: string, repo: string): Promise { return this.request(`/repos/${owner}/${repo}/languages`); } // ユーザーの公開イベントを取得 async getUserEvents(username: string): Promise { return this.request(`/users/${username}/events/public?per_page=100`); } // リポジトリのコンテンツを取得 async getRepoContents(owner: string, repo: string, path = ''): Promise { return this.request(`/repos/${owner}/${repo}/contents/${path}`); } } ``` ### エラーハンドリング ```typescript export class ApiError extends Error { constructor( message: string, public status: number, public response?: any ) { super(message); this.name = 'ApiError'; } // GitHub API固有エラーの判定 isRateLimitError(): boolean { return this.status === 403 && this.response?.message?.includes('rate limit'); } isNotFoundError(): boolean { return this.status === 404; } isUnauthorizedError(): boolean { return this.status === 401; } // ユーザーフレンドリーなエラーメッセージ getUserMessage(): string { switch (this.status) { case 404: return 'ユーザーまたはリポジトリが見つかりません'; case 401: return 'GitHubトークンが無効です'; case 403: if (this.isRateLimitError()) { return 'API制限に達しました。しばらく待ってから再試行してください'; } return 'アクセスが拒否されました'; case 500: return 'GitHubサーバーエラーが発生しました'; default: return `予期しないエラーが発生しました (${this.status})`; } } } ``` ## 📊 データ処理・変換 ### パワーレベル計算 ```typescript interface PowerLevelCalculator { calculateBasePower(userData: GitHubUser): number; calculateRepoBonus(repos: GitHubRepo[]): number; calculateLanguageBonus(languages: ProcessedLanguage[]): number; calculateActivityBonus(activity: ActivityData): number; calculateTotalPower( userData: GitHubUser, repos: GitHubRepo[], languages: ProcessedLanguage[], activity: ActivityData ): PowerLevelResult; } export class PowerLevelCalculator implements PowerLevelCalculator { // 基本パワーの計算 calculateBasePower(userData: GitHubUser): number { const accountAge = this.getAccountAge(userData.created_at); return ( userData.public_repos * 100 + // リポジトリ数 userData.followers * 30 + // フォロワー数 userData.following * 5 + // フォロー数 userData.public_gists * 50 + // Gist数 accountAge * 1000 + // アカウント年齢 (userData.bio ? 500 : 0) + // プロフィール記入 (userData.blog ? 500 : 0) + // ブログ記入 (userData.company ? 1000 : 0) + // 会社情報 (userData.location ? 300 : 0) + // 場所情報 (userData.hireable ? 2000 : 0) // 採用可能 ); } // リポジトリボーナスの計算 calculateRepoBonus(repos: GitHubRepo[]): number { let totalStars = 0; let totalForks = 0; let totalWatchers = 0; let originalRepos = 0; repos.forEach(repo => { if (!repo.fork) { originalRepos++; totalStars += repo.stargazers_count; totalForks += repo.forks_count; totalWatchers += repo.watchers_count; } }); return ( originalRepos * 200 + // オリジナルリポジトリ totalStars * 50 + // 獲得スター数 totalForks * 40 + // フォーク数 totalWatchers * 20 // ウォッチャー数 ); } // 言語ボーナスの計算 calculateLanguageBonus(languages: ProcessedLanguage[]): number { const diversityBonus = languages.length * 1000; const dominanceBonus = languages.length > 0 ? (languages[0].percentage > 80 ? 2000 : 0) : 0; return diversityBonus + dominanceBonus; } // 活動ボーナスの計算 calculateActivityBonus(activity: ActivityData): number { return ( activity.commits * 10 + // コミット数 activity.pullRequests * 100 + // PR数 activity.issues * 50 + // イシュー数 activity.recentContributions * 20 // 最近の貢献 ); } // 総パワーレベル計算 calculateTotalPower( userData: GitHubUser, repos: GitHubRepo[], languages: ProcessedLanguage[], activity: ActivityData ): PowerLevelResult { const basePower = this.calculateBasePower(userData); const repoBonus = this.calculateRepoBonus(repos); const languageBonus = this.calculateLanguageBonus(languages); const activityBonus = this.calculateActivityBonus(activity); const totalPower = basePower + repoBonus + languageBonus + activityBonus; return { power: Math.floor(totalPower), breakdown: { base: basePower, repo: repoBonus, language: languageBonus, activity: activityBonus, }, stats: { repos: userData.public_repos, originalRepos: repos.filter(r => !r.fork).length, stars: repos.reduce((sum, r) => sum + r.stargazers_count, 0), forks: repos.reduce((sum, r) => sum + r.forks_count, 0), followers: userData.followers, following: userData.following, accountAge: this.getAccountAge(userData.created_at), languages, activity, gists: userData.public_gists, }, }; } private getAccountAge(createdAt: string): number { const created = new Date(createdAt); const now = new Date(); return Math.floor((now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24 * 365)); } } ``` ### 言語統計処理 ```typescript export class LanguageAnalyzer { async analyzeUserLanguages( repos: GitHubRepo[], apiClient: GitHubApiClient ): Promise { const languageStats: Record = {}; let totalBytes = 0; // 上位リポジトリの言語統計を取得 const topRepos = repos .filter(repo => !repo.fork) .sort((a, b) => b.stargazers_count - a.stargazers_count) .slice(0, 10); for (const repo of topRepos) { try { const languages = await apiClient.getRepoLanguages( repo.owner.login, repo.name ); Object.entries(languages).forEach(([lang, bytes]) => { languageStats[lang] = (languageStats[lang] || 0) + bytes; totalBytes += bytes; }); } catch (error) { console.warn(`Failed to fetch languages for ${repo.full_name}:`, error); } } // 言語統計を処理 return Object.entries(languageStats) .map(([name, bytes]) => ({ name, bytes, percentage: Math.round((bytes / totalBytes) * 100), })) .sort((a, b) => b.bytes - a.bytes) .slice(0, 5); // 上位5言語 } // 言語別の熟練度を推定 estimateProficiency(language: ProcessedLanguage): 'beginner' | 'intermediate' | 'advanced' { if (language.percentage >= 50) return 'advanced'; if (language.percentage >= 20) return 'intermediate'; return 'beginner'; } // 人気言語かどうかを判定 isPopularLanguage(languageName: string): boolean { const popularLanguages = [ 'JavaScript', 'Python', 'Java', 'TypeScript', 'C#', 'C++', 'PHP', 'Shell', 'C', 'Ruby', 'Go', 'Rust', 'Swift', 'Kotlin', 'Scala', 'Dart', 'R', 'Matlab', ]; return popularLanguages.includes(languageName); } } ``` ### 技術スタック分析 ```typescript export class TechStackAnalyzer { async analyzeTechStack( repos: GitHubRepo[], apiClient: GitHubApiClient ): Promise { const techStack: TechStack = { languages: {}, frameworks: new Set(), devops: new Set(), testing: new Set(), databases: new Set(), infrastructure: new Set(), }; // 上位リポジトリを分析 const topRepos = repos .filter(repo => !repo.fork) .sort((a, b) => b.stargazers_count - a.stargazers_count) .slice(0, 20); for (const repo of topRepos) { try { const contents = await apiClient.getRepoContents( repo.owner.login, repo.name ); await this.analyzeRepoContents(contents, techStack, apiClient, repo); } catch (error) { console.warn(`Failed to analyze ${repo.full_name}:`, error); } } return techStack; } private async analyzeRepoContents( contents: RepoContent[], techStack: TechStack, apiClient: GitHubApiClient, repo: GitHubRepo ): Promise { for (const item of contents) { const fileName = item.name.toLowerCase(); // 設定ファイルから技術スタックを推定 await this.analyzeConfigFiles(fileName, techStack, apiClient, repo); // ディレクトリ構造から技術スタックを推定 if (item.type === 'dir') { this.analyzeDirectoryStructure(fileName, techStack); } } } private async analyzeConfigFiles( fileName: string, techStack: TechStack, apiClient: GitHubApiClient, repo: GitHubRepo ): Promise { const configAnalyzers = { 'package.json': () => this.analyzePackageJson(apiClient, repo, techStack), 'requirements.txt': () => techStack.frameworks.add('Python'), 'pyproject.toml': () => techStack.frameworks.add('Python (Modern)'), 'composer.json': () => techStack.frameworks.add('PHP/Composer'), 'pom.xml': () => techStack.frameworks.add('Java/Maven'), 'build.gradle': () => techStack.frameworks.add('Java/Gradle'), 'cargo.toml': () => techStack.frameworks.add('Rust'), 'go.mod': () => techStack.frameworks.add('Go'), 'gemfile': () => techStack.frameworks.add('Ruby'), 'dockerfile': () => techStack.devops.add('Docker'), }; const analyzer = configAnalyzers[fileName]; if (analyzer) { await analyzer(); } // YAML/YMLファイルの分析 if (fileName.endsWith('.yml') || fileName.endsWith('.yaml')) { this.analyzeYamlFile(fileName, techStack); } } private async analyzePackageJson( apiClient: GitHubApiClient, repo: GitHubRepo, techStack: TechStack ): Promise { try { const content = await apiClient.getRepoContents( repo.owner.login, repo.name, 'package.json' ); if (Array.isArray(content)) return; const packageData = JSON.parse(atob(content.content)); const allDeps = { ...packageData.dependencies, ...packageData.devDependencies, }; // フレームワーク検出 const frameworks = { 'react': 'React', 'vue': 'Vue.js', '@angular/core': 'Angular', 'next': 'Next.js', 'nuxt': 'Nuxt.js', 'express': 'Express.js', 'fastify': 'Fastify', 'nestjs': 'NestJS', 'svelte': 'Svelte', 'ember-source': 'Ember.js', }; Object.entries(frameworks).forEach(([dep, name]) => { if (allDeps[dep]) { techStack.frameworks.add(name); } }); // テストツール検出 const testTools = { 'jest': 'Jest', 'mocha': 'Mocha', 'cypress': 'Cypress', 'playwright': 'Playwright', 'vitest': 'Vitest', 'testing-library': 'Testing Library', }; Object.entries(testTools).forEach(([dep, name]) => { if (allDeps[dep] || Object.keys(allDeps).some(key => key.includes(dep))) { techStack.testing.add(name); } }); // ビルドツール検出 const buildTools = { 'webpack': 'Webpack', 'vite': 'Vite', 'rollup': 'Rollup', 'esbuild': 'ESBuild', 'parcel': 'Parcel', 'turbo': 'Turbo', }; Object.entries(buildTools).forEach(([dep, name]) => { if (allDeps[dep]) { techStack.devops.add(name); } }); } catch (error) { console.warn('Failed to analyze package.json:', error); } } private analyzeYamlFile(fileName: string, techStack: TechStack): void { if (fileName.includes('github')) { techStack.devops.add('GitHub Actions'); } else if (fileName.includes('gitlab')) { techStack.devops.add('GitLab CI'); } else if (fileName.includes('circle')) { techStack.devops.add('CircleCI'); } else if (fileName.includes('travis')) { techStack.devops.add('Travis CI'); } else if (fileName.includes('docker-compose')) { techStack.devops.add('Docker Compose'); } else if (fileName.includes('kubernetes') || fileName.includes('k8s')) { techStack.infrastructure.add('Kubernetes'); } } private analyzeDirectoryStructure(dirName: string, techStack: TechStack): void { const directories = { 'test': () => techStack.testing.add('Test-Driven Development'), 'tests': () => techStack.testing.add('Test-Driven Development'), 'spec': () => techStack.testing.add('Test-Driven Development'), '__tests__': () => techStack.testing.add('Test-Driven Development'), 'cypress': () => techStack.testing.add('Cypress'), 'e2e': () => techStack.testing.add('End-to-End Testing'), 'docker': () => techStack.devops.add('Docker'), 'kubernetes': () => techStack.infrastructure.add('Kubernetes'), 'terraform': () => techStack.infrastructure.add('Terraform'), 'ansible': () => techStack.infrastructure.add('Ansible'), }; const analyzer = directories[dirName]; if (analyzer) { analyzer(); } } } ``` ## 🔄 レート制限管理 ### レート制限ハンドリング ```typescript export class RateLimitManager { private lastRequestTime = 0; private requestCount = 0; private resetTime = 0; private remaining = 0; async checkRateLimit(apiClient: GitHubApiClient): Promise { try { const rateLimit = await apiClient.getRateLimit(); this.remaining = rateLimit.rate.remaining; this.resetTime = rateLimit.rate.reset * 1000; if (this.remaining === 0) { const waitTime = this.resetTime - Date.now(); if (waitTime > 0) { throw new ApiError( `Rate limit exceeded. Reset in ${Math.ceil(waitTime / 1000)}s`, 429, { reset_at: new Date(this.resetTime) } ); } } } catch (error) { console.warn('Failed to check rate limit:', error); } } async throttleRequest( request: () => Promise, minInterval = 100 ): Promise { const now = Date.now(); const timeSinceLastRequest = now - this.lastRequestTime; if (timeSinceLastRequest < minInterval) { await new Promise(resolve => setTimeout(resolve, minInterval - timeSinceLastRequest) ); } this.lastRequestTime = Date.now(); this.requestCount++; try { return await request(); } catch (error) { if (error instanceof ApiError && error.isRateLimitError()) { // 指数バックオフで再試行 await this.exponentialBackoff(); return this.throttleRequest(request, minInterval); } throw error; } } private async exponentialBackoff(attempt = 1): Promise { const delay = Math.min(1000 * Math.pow(2, attempt), 30000); await new Promise(resolve => setTimeout(resolve, delay)); } getRemainingRequests(): number { return this.remaining; } getResetTime(): Date { return new Date(this.resetTime); } } ``` ## 🧪 テスト用モック ### API レスポンスモック ```typescript export class MockGitHubApiClient implements GitHubApiClient { async getRateLimit(): Promise { return { rate: { limit: 5000, remaining: 4999, reset: Math.floor(Date.now() / 1000) + 3600, used: 1, }, search: { limit: 30, remaining: 30, reset: Math.floor(Date.now() / 1000) + 3600, used: 0, }, }; } async getUser(username: string): Promise { return { login: username, id: 1, avatar_url: `https://github.com/${username}.png`, html_url: `https://github.com/${username}`, name: `${username} (Mock)`, company: 'Mock Company', blog: 'https://example.com', location: 'Mock Location', email: null, hireable: true, bio: 'Mock user for testing', public_repos: 50, public_gists: 10, followers: 1000, following: 100, created_at: '2010-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }; } async getUserRepos(username: string): Promise { return [ { id: 1, name: 'awesome-project', full_name: `${username}/awesome-project`, private: false, owner: { login: username, id: 1, avatar_url: `https://github.com/${username}.png`, }, html_url: `https://github.com/${username}/awesome-project`, description: 'An awesome project', fork: false, url: `https://api.github.com/repos/${username}/awesome-project`, languages_url: `https://api.github.com/repos/${username}/awesome-project/languages`, stargazers_count: 100, watchers_count: 50, forks_count: 25, open_issues_count: 5, created_at: '2022-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', pushed_at: '2024-01-01T00:00:00Z', language: 'TypeScript', size: 1024, }, ]; } } ``` ## 📚 使用例 ### 完全な使用例 ```typescript // メインアプリケーションでの使用 const useGitHubScouter = (token?: string) => { const apiClient = useMemo(() => new GitHubApiClient(token), [token]); const rateLimitManager = useMemo(() => new RateLimitManager(), []); const powerCalculator = useMemo(() => new PowerLevelCalculator(), []); const languageAnalyzer = useMemo(() => new LanguageAnalyzer(), []); const techStackAnalyzer = useMemo(() => new TechStackAnalyzer(), []); const scanUser = useCallback(async (username: string) => { try { // レート制限チェック await rateLimitManager.checkRateLimit(apiClient); // 並行でデータ取得 const [userData, repos] = await Promise.all([ rateLimitManager.throttleRequest(() => apiClient.getUser(username)), rateLimitManager.throttleRequest(() => apiClient.getUserRepos(username)), ]); // 詳細分析 const [languages, events, techStack] = await Promise.all([ languageAnalyzer.analyzeUserLanguages(repos, apiClient), rateLimitManager.throttleRequest(() => apiClient.getUserEvents(username)), techStackAnalyzer.analyzeTechStack(repos, apiClient), ]); // 活動データ処理 const activity = processUserEvents(events); // パワーレベル計算 const powerLevel = powerCalculator.calculateTotalPower( userData, repos, languages, activity ); return { userData, repos, languages, techStack, activity, powerLevel, }; } catch (error) { if (error instanceof ApiError) { throw error; } throw new Error(`Scan failed: ${error.message}`); } }, [apiClient, rateLimitManager, powerCalculator, languageAnalyzer, techStackAnalyzer]); return { scanUser }; }; ``` --- **🎯 このAPIリファレンスを参考に、効率的なGitHub APIの活用を行いましょう!** 次は[コンポーネントリファレンス](Component-Reference.md)でReactコンポーネントの詳細を確認できます。