| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- # .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
|