#!/usr/bin/env python3 """ GitHub Traffic Statistics Script Author: Roman Tsisyk Date: 2025-02-16 Description: This script collects detailed traffic statistics for all your GitHub repositories. It uses the GitHub API to gather data on: - Views (last 14 days), including daily breakdown and unique visitors. - Clones (last 14 days), including daily breakdown and unique cloners. - Releases and downloads (if available). The script generates: - A text report saved to "github_traffic_report.txt". - An HTML dashboard ("dashboard.html") with interactive charts powered by Chart.js. - A modern UI with a professional look. Usage: 1. Copy your GitHub Personal Access Token (PAT) to your clipboard. 2. Run this script and simply press Enter when prompted. 3. Enjoy your report and interactive dashboard! ================================================================================ Enter your GitHub PAT (or press Enter to use clipboard): """ import os import requests import json from collections import defaultdict import getpass # Optional: try to import pyperclip for clipboard support. try: import pyperclip except ImportError: print("pyperclip not found. Install it with 'pip install pyperclip' for clipboard support.") pyperclip = None # --- Display Script Information --- print("\n" + "=" * 80) print("GitHub Traffic Statistics Script") print("Author: Roman Tsisyk") print("Date: 2025-02-16\n") print("Description:") print(" This script collects detailed traffic statistics for all your GitHub repositories.") print(" It uses the GitHub API to gather data on:") print(" - Views (last 14 days), including daily breakdown and unique visitors.") print(" - Clones (last 14 days), including daily breakdown and unique cloners.") print(" - Releases and downloads (if available).\n") print(" The script generates:") print(' - A text report saved to "github_traffic_report.txt".') print(' - An HTML dashboard ("dashboard.html") with interactive charts powered by Chart.js.\n') print("Usage:") print(" 1. Copy your GitHub Personal Access Token (PAT) to your clipboard.") print(" 2. Run this script and simply press Enter when prompted.") print(" 3. Enjoy your report and interactive dashboard!") print("=" * 80 + "\n") # --- Configuration via Console Input --- token_input = getpass.getpass("Enter your GitHub PAT (or press Enter to use clipboard): ").strip() if not token_input and pyperclip: token_input = pyperclip.paste().strip() if not token_input: raise ValueError("No token provided. Please copy your GitHub PAT into the clipboard and try again.") GITHUB_TOKEN = token_input # Auto-detect GitHub username using the API (no manual input) BASE_API_URL = "https://api.github.com" HEADERS = { "Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json" } try: resp = requests.get(f"{BASE_API_URL}/user", headers=HEADERS) resp.raise_for_status() user_info = resp.json() GITHUB_USER = user_info["login"] print(f"\nDetected GitHub username: {GITHUB_USER}\n") except Exception as e: print(f"Error detecting user: {e}") exit(1) # --- Helper Functions for Report Generation --- def save_report_to_file(report, filename="github_traffic_report.txt"): """Saves the full report to a file (append mode).""" with open(filename, "a", encoding="utf-8") as file: file.write(report) file.write("\n\n") print(f"Report saved to '{filename}'.") def get_user_repos(user, per_page=100): """Retrieves all repositories for the given user using simple pagination.""" all_repos = [] page = 1 while True: url = f"{BASE_API_URL}/users/{user}/repos" params = {"per_page": per_page, "page": page} resp = requests.get(url, headers=HEADERS, params=params) resp.raise_for_status() repos = resp.json() if not repos: break all_repos.extend(repos) page += 1 return all_repos def get_repo_traffic_views(owner, repo): """Retrieves view statistics (last 14 days) for a repository.""" url = f"{BASE_API_URL}/repos/{owner}/{repo}/traffic/views" resp = requests.get(url, headers=HEADERS) resp.raise_for_status() return resp.json() def get_repo_traffic_clones(owner, repo): """Retrieves clone statistics (last 14 days) for a repository.""" url = f"{BASE_API_URL}/repos/{owner}/{repo}/traffic/clones" resp = requests.get(url, headers=HEADERS) resp.raise_for_status() return resp.json() def get_repo_releases(owner, repo): """Retrieves the list of releases for a repository.""" url = f"{BASE_API_URL}/repos/{owner}/{repo}/releases" resp = requests.get(url, headers=HEADERS) if resp.status_code == 404: return [] resp.raise_for_status() return resp.json() def print_views_details(views_data): """Returns a string with daily view details (removes 'T00:00:00Z').""" report = "" for view in views_data.get("views", []): date = view["timestamp"].replace("T00:00:00Z", "") daily_views = view["count"] report += f" * {date}: {daily_views} views\n" if not report: report = " (No data available)\n" return report def print_clones_details(clones_data): """Returns a string with daily clone details (removes 'T00:00:00Z').""" report = "" for clone in clones_data.get("clones", []): date = clone["timestamp"].replace("T00:00:00Z", "") daily_clones = clone["count"] report += f" * {date}: {daily_clones} clones\n" if not report: report = " (No data available)\n" return report # --- Formatting for Console Output using Rich --- from rich.console import Console from rich.panel import Panel from rich.progress import track from rich.box import ROUNDED from rich.text import Text console = Console() def format_repo_panel(repo_name, panel_text): """Formats a repository panel with rounded borders, word wrapping, and consistent padding.""" wrapped_text = Text(panel_text, overflow="fold", no_wrap=False) return Panel(wrapped_text, title=f"[bold blue]{repo_name}[/bold blue]", border_style="bright_blue", padding=(1, 2), expand=False, box=ROUNDED) # --- Main Function --- def main(): console.rule("[bold blue]GitHub Traffic Report[/bold blue]") console.print(f"[bold]User:[/bold] {GITHUB_USER}\n", style="green") repos = get_user_repos(GITHUB_USER) if not repos: console.print("[red]No repositories found.[/red]") input("Press Enter to exit...") return full_report = "" # Containers for HTML dashboard data views_by_repo = {} # { repo_name: { date: view_count } } clones_by_repo = {} # { repo_name: { date: clone_count } } downloads_by_repo = {} # { repo_name: total downloads } for r in track(repos, description="Processing repositories..."): repo_name = r["name"] full_name = r["full_name"] panel_text = f"[bold]Repository:[/bold] {full_name}\n" views_by_repo[repo_name] = {} clones_by_repo[repo_name] = {} downloads_by_repo[repo_name] = 0 try: views_data = get_repo_traffic_views(GITHUB_USER, repo_name) clones_data = get_repo_traffic_clones(GITHUB_USER, repo_name) total_views = views_data.get("count", 0) unique_visitors = views_data.get("uniques", 0) total_clones = clones_data.get("count", 0) unique_cloners = clones_data.get("uniques", 0) panel_text += f"[cyan]Views (last 14 days):[/cyan] {total_views}\n" panel_text += f"[cyan]Unique Visitors:[/cyan] {unique_visitors}\n" panel_text += f"[magenta]Clones (last 14 days):[/magenta] {total_clones}\n" panel_text += f"[magenta]Unique Cloners:[/magenta] {unique_cloners}\n\n" panel_text += "[bold]Daily Views:[/bold]\n" + print_views_details(views_data) panel_text += "\n[bold]Daily Clones:[/bold]\n" + print_clones_details(clones_data) for view in views_data.get("views", []): date = view["timestamp"].replace("T00:00:00Z", "") views_by_repo[repo_name][date] = view["count"] for clone in clones_data.get("clones", []): date = clone["timestamp"].replace("T00:00:00Z", "") clones_by_repo[repo_name][date] = clone["count"] except requests.exceptions.HTTPError as e: panel_text += f"[red]Error retrieving traffic data: {e}[/red]\n" try: releases = get_repo_releases(GITHUB_USER, repo_name) if releases: panel_text += f"\n[bold]Releases:[/bold] {len(releases)}\n" for rel in releases: rel_name = rel.get("name") or rel.get("tag_name") assets = rel.get("assets", []) if assets: for asset in assets: download_count = asset["download_count"] panel_text += f" * {rel_name} — Downloads: {download_count}\n" downloads_by_repo[repo_name] += download_count else: panel_text += f" * {rel_name} (no assets)\n" else: panel_text += "\n[italic]No releases or releases are hidden (404).[/italic]\n" except requests.exceptions.HTTPError as e: panel_text += f"[red]Could not retrieve releases: {e}[/red]\n" full_report += panel_text + "\n" # Insert a newline before printing the panel so the progress bar output is separated. console.print("") console.print(format_repo_panel(repo_name, panel_text)) save_report_to_file(full_report) console.rule("[bold blue]Report Complete[/bold blue]") # --- Prepare Data for HTML Dashboard --- # Process Views Data all_view_dates = set() for repo_data in views_by_repo.values(): all_view_dates.update(repo_data.keys()) all_view_dates = sorted(all_view_dates) views_chart_data = {} for repo, data in views_by_repo.items(): views_chart_data[repo] = [data.get(date, 0) for date in all_view_dates] overall_views = [0] * len(all_view_dates) for repo_data in views_chart_data.values(): for i, count in enumerate(repo_data): overall_views[i] += count # Process Clones Data all_clone_dates = set() for repo_data in clones_by_repo.values(): all_clone_dates.update(repo_data.keys()) all_clone_dates = sorted(all_clone_dates) clones_chart_data = {} for repo, data in clones_by_repo.items(): clones_chart_data[repo] = [data.get(date, 0) for date in all_clone_dates] overall_clones = [0] * len(all_clone_dates) for repo_data in clones_chart_data.values(): for i, count in enumerate(repo_data): overall_clones[i] += count # --- Generate HTML Dashboard --- html_template = f""" GitHub Traffic Dashboard

GitHub Traffic Dashboard

User: {GITHUB_USER}

Daily Views per Repository

Overall Daily Views

Daily Clones per Repository

Overall Daily Clones

""" try: with open("dashboard.html", "w", encoding="utf-8") as html_file: html_file.write(html_template) print("Dashboard generated as 'dashboard.html'.") except Exception as e: print(f"Error generating dashboard: {e}") if __name__ == "__main__": try: main() except Exception as err: print(f"Unexpected error: {err}") input("Press Enter to exit...")