Update Download Stats #74
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update Download Stats | |
| on: | |
| schedule: | |
| - cron: "0 10 * * 5" | |
| workflow_dispatch: | |
| jobs: | |
| stats: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.x' | |
| - name: Install dependencies | |
| run: pip install matplotlib pandas | |
| - name: Ensure stats directory exists | |
| run: mkdir -p stats | |
| - name: Fetch release downloads | |
| run: | | |
| RESPONSE=$(curl -s -H "Accept: application/vnd.github.v3+json" \ | |
| https://api.github.com/repos/${{ github.repository }}/releases) | |
| if echo "$RESPONSE" | jq -e '. | type == "array"' > /dev/null 2>&1; then | |
| if [ "$(echo "$RESPONSE" | jq '. | length')" -eq 0 ]; then | |
| echo "⚠️ No releases found." | |
| echo '[]' > stats/downloads.json | |
| else | |
| echo "$RESPONSE" | jq ' | |
| [ | |
| .[] | | |
| { | |
| tag: .tag_name, | |
| total: ((.assets | map(.download_count) | add) // 0) | |
| } | |
| ] | |
| ' > stats/downloads.json | |
| fi | |
| else | |
| echo "❌ Error: Invalid API response" | |
| echo "$RESPONSE" | |
| exit 1 | |
| fi | |
| - name: Fix existing history file (if needed) | |
| run: | | |
| if [ -f stats/history.csv ]; then | |
| # Check if first line looks like a header | |
| FIRST_LINE=$(head -n 1 stats/history.csv) | |
| if [[ "$FIRST_LINE" != "Date,Downloads" ]] && [[ "$FIRST_LINE" != "Date,Total Downloads" ]]; then | |
| echo "🔧 Adding header to existing history.csv..." | |
| # Create temp file with header | |
| echo "Date,Downloads" > stats/history.csv.tmp | |
| cat stats/history.csv >> stats/history.csv.tmp | |
| mv stats/history.csv.tmp stats/history.csv | |
| echo "✅ Header added" | |
| else | |
| echo "✅ Header already exists" | |
| fi | |
| fi | |
| - name: Append history | |
| run: | | |
| TOTAL=$(jq '[.[].total] | add // 0' stats/downloads.json) | |
| DATE=$(date -u +"%Y-%m-%d %H:%M") | |
| # Create history.csv with header if it doesn't exist | |
| if [ ! -f stats/history.csv ]; then | |
| echo "Date,Downloads" > stats/history.csv | |
| fi | |
| echo "$DATE,$TOTAL" >> stats/history.csv | |
| echo "📊 Total downloads: $TOTAL" | |
| - name: Generate chart (SVG) | |
| run: | | |
| python3 << 'PYTHON_SCRIPT' | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| import sys | |
| try: | |
| # Read CSV - it should have header now | |
| df = pd.read_csv('stats/history.csv') | |
| # Strip any whitespace from column names | |
| df.columns = df.columns.str.strip() | |
| print(f"📋 Columns found: {list(df.columns)}") | |
| print(f"📊 Data points: {len(df)}") | |
| print(f"📅 First row: {df.head(1).to_dict('records')}") | |
| if len(df) < 1: | |
| print("⚠️ Not enough data points") | |
| sys.exit(0) | |
| # Get column names (handle different naming) | |
| date_col = None | |
| downloads_col = None | |
| for col in df.columns: | |
| if 'date' in col.lower(): | |
| date_col = col | |
| elif 'download' in col.lower(): | |
| downloads_col = col | |
| if not date_col or not downloads_col: | |
| print(f"❌ Could not find date or downloads column") | |
| print(f"Available columns: {list(df.columns)}") | |
| sys.exit(1) | |
| print(f"✅ Using columns: Date='{date_col}', Downloads='{downloads_col}'") | |
| # Parse dates | |
| df[date_col] = pd.to_datetime(df[date_col]) | |
| # Ensure downloads is numeric | |
| df[downloads_col] = pd.to_numeric(df[downloads_col], errors='coerce') | |
| # Create figure with dark background | |
| plt.style.use('dark_background') | |
| fig, ax = plt.subplots(figsize=(10, 5)) | |
| # Plot | |
| ax.plot(df[date_col], df[downloads_col], | |
| marker='o', linewidth=2.5, markersize=6, | |
| color='#00d4aa', label='Downloads') | |
| # Styling | |
| ax.set_title('📊 Total Downloads Over Time', fontsize=16, pad=20) | |
| ax.set_xlabel('Date', fontsize=11) | |
| ax.set_ylabel('Total Downloads', fontsize=11) | |
| ax.grid(True, alpha=0.3) | |
| ax.legend() | |
| # Format x-axis | |
| plt.xticks(rotation=45) | |
| plt.tight_layout() | |
| # Save | |
| plt.savefig('stats/chart.svg', format='svg', | |
| facecolor='#0d1117', edgecolor='none') | |
| print("✅ Chart generated successfully") | |
| except Exception as e: | |
| print(f"❌ Error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| sys.exit(1) | |
| PYTHON_SCRIPT | |
| - name: Check for changes | |
| id: changes | |
| run: | | |
| git add stats/ | |
| if git diff --cached --quiet; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create branch and PR | |
| if: steps.changes.outputs.has_changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| BRANCH="chore/update-stats-$(date +%Y%m%d-%H%M%S)" | |
| git checkout -b "$BRANCH" | |
| git commit -m "chore: update download stats [skip ci]" | |
| git push origin "$BRANCH" | |
| PR_URL=$(gh pr create \ | |
| --title "chore: update download stats" \ | |
| --body "🤖 Automated download statistics update | |
| This PR was created by the scheduled workflow. | |
| --- | |
| _This PR will be auto-merged._" \ | |
| --base main \ | |
| --head "$BRANCH") | |
| echo "Created PR: $PR_URL" | |
| sleep 5 | |
| if gh pr merge "$PR_URL" --squash --delete-branch; then | |
| echo "✅ PR merged successfully!" | |
| else | |
| echo "⏳ Enabling auto-merge..." | |
| gh pr merge "$PR_URL" --auto --squash --delete-branch || \ | |
| echo "⚠️ Auto-merge setup may have failed" | |
| fi |