# .github/workflows/github-metrics-notify.yml name: GitHub Metrics Notification # Grants specific permissions to the GITHUB_TOKEN permissions: contents: write # Allows pushing changes to the repository issues: read # Optional: if you're interacting with issues pull-requests: write # Optional: if you're interacting with pull requests # Triggers the workflow on specific GitHub events and schedules it as a backup on: # Trigger on repository star events watch: types: [started, deleted] # 'started' for star, 'deleted' for unstar # Trigger on repository forks fork: # Trigger on issue events issues: types: [opened, closed, reopened, edited] # Trigger on pull request events pull_request: types: [opened, closed, reopened, edited] # Trigger on release events release: types: [published, edited, prereleased, released] # Trigger on push events to the main branch push: branches: - main # Scheduled backup trigger every hour for followers/subscribers schedule: - cron: '0 */1 * * *' # Every hour at minute 0 # Allows manual triggering workflow_dispatch: jobs: notify_metrics: runs-on: ubuntu-latest steps: # Step 1: Checkout the repository - name: Checkout Repository uses: actions/checkout@v3 with: persist-credentials: true # Enables Git commands to use GITHUB_TOKEN fetch-depth: 0 # Fetch all history for accurate metric tracking # Step 2: Set Up Python Environment - name: Set Up Python uses: actions/setup-python@v4 with: python-version: '3.x' # Specify the Python version # Step 3: Install Python Dependencies - name: Install Dependencies run: | python -m pip install --upgrade pip pip install requests # Step 4: Fetch and Compare Metrics - name: Fetch and Compare Metrics id: fetch_metrics env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Built-in secret provided by GitHub Actions DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} # Your Discord webhook URL GITHUB_EVENT_NAME: ${{ github.event_name }} # To determine if run is manual or triggered by an event GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} # Dynamic repository owner run: | python3 - <<'EOF' > fetch_metrics.out import os import requests import json from datetime import datetime # Configuration REPO_OWNER = os.getenv('GITHUB_REPOSITORY_OWNER') REPO_NAME = os.getenv('GITHUB_REPOSITORY').split('/')[-1] METRICS_FILE = ".github/metrics.json" # Ensure .github directory exists os.makedirs(os.path.dirname(METRICS_FILE), exist_ok=True) # GitHub API Headers headers = { "Authorization": f"token {os.getenv('GITHUB_TOKEN')}", "Accept": "application/vnd.github.v3+json" } # Function to fetch closed issues count using GitHub Search API def fetch_closed_issues(owner, repo, headers): search_api = f"https://api.github.com/search/issues?q=repo:{owner}/{repo}+type:issue+state:closed" try: response = requests.get(search_api, headers=headers) response.raise_for_status() data = response.json() return data.get('total_count', 0) except requests.exceptions.RequestException as e: print(f"Error fetching closed issues count: {e}") return 0 # Fetch current metrics from GitHub API repo_api = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}" try: response = requests.get(repo_api, headers=headers) response.raise_for_status() repo_data = response.json() stars = repo_data.get('stargazers_count', 0) forks = repo_data.get('forks_count', 0) followers = repo_data.get('subscribers_count', 0) open_issues = repo_data.get('open_issues_count', 0) closed_issues = fetch_closed_issues(REPO_OWNER, REPO_NAME, headers) except requests.exceptions.RequestException as e: print(f"Error fetching repository data: {e}") exit(1) # Function to load previous metrics with error handling def load_previous_metrics(file_path): try: with open(file_path, 'r') as file: return json.load(file) except json.decoder.JSONDecodeError: print("metrics.json is corrupted or empty. Reinitializing.") return { "stars": 0, "forks": 0, "followers": 0, "open_issues": 0, "closed_issues": 0 } except FileNotFoundError: return { "stars": 0, "forks": 0, "followers": 0, "open_issues": 0, "closed_issues": 0 } # Load previous metrics prev_metrics = load_previous_metrics(METRICS_FILE) is_initial_run = not os.path.exists(METRICS_FILE) # Determine changes (both increases and decreases) changes = {} metrics = ["stars", "forks", "followers", "open_issues", "closed_issues"] current_metrics = { "stars": stars, "forks": forks, "followers": followers, "open_issues": open_issues, "closed_issues": closed_issues } for metric in metrics: current = current_metrics.get(metric, 0) previous = prev_metrics.get(metric, 0) if current != previous: changes[metric] = current - previous # Update metrics file with open(METRICS_FILE, 'w') as file: json.dump(current_metrics, file) # Determine if a notification should be sent event_name = os.getenv('GITHUB_EVENT_NAME') send_notification = False no_changes = False initial_setup = False if is_initial_run: if event_name == 'workflow_dispatch': # Manual run: Send notification for initial setup send_notification = True initial_setup = True elif event_name == 'watch' and changes.get('stars'): # Initial run triggered by a star event: Send notification send_notification = True else: # Event-triggered runs: Do not send notification on initial setup send_notification = False else: if event_name == 'workflow_dispatch': # Manual run: Always send notification send_notification = True if not changes: no_changes = True elif event_name == 'watch': # Star event: Send notification only if stars changed if changes.get('stars'): send_notification = True else: # Scheduled run or other events: Send notification only if there are changes if changes: send_notification = True if send_notification: triggering_actor = os.getenv('GITHUB_ACTOR', 'Unknown') # Prepare Discord notification payload payload = { "embeds": [ { "title": "📈 GitHub Repository Metrics Updated", "url": f"https://github.com/{REPO_OWNER}/{REPO_NAME}", # Link back to the repository "color": 0x7289DA, # Discord blurple color "thumbnail": { "url": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" # GitHub logo }, "fields": [ { "name": "📂 Repository", "value": f"[{REPO_OWNER}/{REPO_NAME}](https://github.com/{REPO_OWNER}/{REPO_NAME})", "inline": False }, { "name": "⭐ Stars", "value": f"{stars}", "inline": True }, { "name": "🍴 Forks", "value": f"{forks}", "inline": True }, { "name": "👥 Followers", "value": f"{followers}", "inline": True }, { "name": "🐛 Open Issues", "value": f"{open_issues}", "inline": True }, { "name": "🔒 Closed Issues", "value": f"{closed_issues}", "inline": True }, { "name": "👤 Triggered By", "value": triggering_actor, "inline": False }, ], "footer": { "text": "GitHub Metrics Monitor", "icon_url": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" # GitHub logo }, "timestamp": datetime.utcnow().isoformat() # Adds a timestamp to the embed } ] } if initial_setup: # Add a field indicating initial setup payload["embeds"][0]["fields"].append({ "name": "⚙️ Initial Setup", "value": "Metrics tracking has been initialized.", "inline": False }) elif changes: # Add fields for each updated metric for metric, count in changes.items(): emoji = { "stars": "⭐", "forks": "🍴", "followers": "👥", "open_issues": "🐛", "closed_issues": "🔒" }.get(metric, "") change_symbol = "+" if count > 0 else "" payload["embeds"][0]["fields"].append({ "name": f"{emoji} {metric.replace('_', ' ').capitalize()} (Change)", "value": f"{change_symbol}{count}", "inline": True }) elif no_changes: # Indicate that there were no changes during a manual run payload["embeds"][0]["fields"].append({ "name": "✅ No Changes", "value": "No updates to metrics since the last check.", "inline": False }) # Save payload to a temporary file for the next step with open('payload.json', 'w') as f: json.dump(payload, f) # Output whether to send notification if initial_setup or changes or no_changes: print("SEND_NOTIFICATION=true") else: print("SEND_NOTIFICATION=false") else: print("SEND_NOTIFICATION=false") EOF # Step 5: Ensure .gitignore Ignores Temporary Files - name: Ensure .gitignore Ignores Temporary Files run: | # Check if .gitignore exists; if not, create it if [ ! -f .gitignore ]; then touch .gitignore fi # Add 'fetch_metrics.out' if not present if ! grep -Fxq "fetch_metrics.out" .gitignore; then echo "fetch_metrics.out" >> .gitignore echo "Added 'fetch_metrics.out' to .gitignore" else echo "'fetch_metrics.out' already present in .gitignore" fi # Add 'payload.json' if not present if ! grep -Fxq "payload.json" .gitignore; then echo "payload.json" >> .gitignore echo "Added 'payload.json' to .gitignore" else echo "'payload.json' already present in .gitignore" fi # Step 6: Decide Whether to Send Notification - name: Check if Notification Should Be Sent id: decide_notification run: | if grep -q "SEND_NOTIFICATION=true" fetch_metrics.out; then echo "send=true" >> $GITHUB_OUTPUT else echo "send=false" >> $GITHUB_OUTPUT fi shell: bash # Step 7: Send Discord Notification using curl - name: Send Discord Notification if: steps.decide_notification.outputs.send == 'true' run: | curl -H "Content-Type: application/json" -d @payload.json ${{ secrets.DISCORD_WEBHOOK_URL }} # Step 8: Commit and Push Updated metrics.json and .gitignore - name: Commit and Push Changes if: steps.decide_notification.outputs.send == 'true' run: | git config --global user.name "GitHub Actions" git config --global user.email "actions@github.com" # Stage metrics.json git add .github/metrics.json # Stage .gitignore only if it was modified if git diff --name-only | grep -q "^\.gitignore$"; then git add .gitignore else echo ".gitignore not modified" fi # Commit changes if there are any git commit -m "Update metrics.json and ensure temporary files are ignored [skip ci]" || echo "No changes to commit" # Push changes to the main branch git push origin main # Replace 'main' with your default branch if different # Step 9: Clean Up Temporary Files - name: Clean Up Temporary Files if: always() run: | rm -f fetch_metrics.out payload.json