Skip to content

Help with Application cleanup #150

@ewels

Description

@ewels

Hi folks,

When creating my own application, I saw that there were a lot of open application issues, and a lot were obviously invalid. I had a quick sit down with Cursor / claude and put together a script to help clean those up for you:

check_stars.py
#!/usr/bin/env python3
import subprocess
import json
import re
import sys
import os
from rich.console import Console
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn
from rich.table import Table
from rich.markdown import Markdown

console = Console()

def run_gh_command(command, handle_pagination=False):
    """Run a GitHub CLI command and return the output as JSON."""
    try:
        if handle_pagination and "api" in command:
            # For API calls that might need pagination
            all_results = []
            page = 1
            per_page = 100

            while True:
                # Add pagination parameters
                paginated_command = command.copy()
                if "?" in paginated_command[2]:
                    paginated_command[2] += f"&page={page}&per_page={per_page}"
                else:
                    paginated_command[2] += f"?page={page}&per_page={per_page}"

                result = subprocess.run(paginated_command, capture_output=True, text=True, check=True)

                if not result.stdout.strip():
                    break

                page_results = json.loads(result.stdout)

                if not page_results or (isinstance(page_results, list) and len(page_results) == 0):
                    break

                if isinstance(page_results, list):
                    all_results.extend(page_results)
                else:
                    # If it's not a list, just return the result
                    return page_results

                # If we got fewer results than per_page, we've reached the end
                if len(page_results) < per_page:
                    break

                page += 1

            return all_results
        else:
            # For non-paginated commands
            result = subprocess.run(command, capture_output=True, text=True, check=True)
            if not result.stdout.strip():
                return None
            return json.loads(result.stdout)
    except subprocess.CalledProcessError as e:
        # For repository not found errors, return None instead of exiting
        if "Not Found (HTTP 404)" in e.stderr and "repos/" in command[2]:
            return None
        console.print(f"[red]Error running command:[/red] {' '.join(command)}")
        console.print(f"[red]Error message:[/red] {e.stderr}")
        return None
    except json.JSONDecodeError:
        console.print(f"[red]Error parsing JSON from command:[/red] {' '.join(command)}")
        console.print(f"[red]Output:[/red] {result.stdout}")
        return None

def extract_repo_url(comment_body):
    """Extract GitHub repository URL from comment body."""
    if not comment_body:
        return None
    pattern = r"Repository URL to your project\s*\n\s*(https://github\.com/[^\s/]+/[^\s/]+)"
    match = re.search(pattern, comment_body)
    if match:
        return match.group(1)
    return None

def get_repo_stars(repo_url):
    """Get the number of stars for a GitHub repository."""
    if not repo_url:
        return None

    # Extract owner and repo name from URL
    parts = repo_url.strip('/').split('/')
    if len(parts) < 5:
        return None

    owner = parts[-2]
    repo = parts[-1]

    command = ["gh", "api", f"repos/{owner}/{repo}"]
    try:
        repo_info = run_gh_command(command)
        if repo_info:
            return repo_info.get("stargazers_count")
        return None
    except Exception as e:
        console.print(f"[red]Error fetching stars for {repo_url}: {e}[/red]")
        return None

def generate_markdown_table(results):
    """Generate a markdown table from the results."""
    markdown = "# CORS.sh Application Status\n\n"
    markdown += "## Repository Star Analysis Results\n\n"

    # Table header
    markdown += "| Issue # | Title | Repository | Stars | Valid | Reason |\n"
    markdown += "|---------|-------|------------|-------|-------|--------|\n"

    # Sort by validity and stars
    sorted_results = sorted(
        results.values(),
        key=lambda x: (
            x["valid"],
            x.get("stars", 0) if isinstance(x.get("stars", 0), int) else -1
        ),
        reverse=True
    )

    # Table rows
    for result in sorted_results:
        stars_display = str(result.get("stars", "N/A"))
        valid_display = "✅" if result["valid"] else "❌"

        # Escape pipe characters in markdown table
        title = result['issue_title'].replace('|', '\\|')

        # Fix for NoneType error - ensure repo_url is a string
        repo_url = result.get('repo_url', 'N/A')
        if repo_url is None:
            repo_url = 'N/A'
        else:
            repo_url = repo_url.replace('|', '\\|')

        reason = result['reason'].replace('|', '\\|')

        markdown += f"| #{result['issue_number']} | {title} | {repo_url} | {stars_display} | {valid_display} | {reason} |\n"

    # Summary stats
    valid_count = sum(1 for r in results.values() if r["valid"])
    markdown += f"\n## Summary\n\n{valid_count} valid repositories out of {len(results)} issues\n"

    return markdown

def generate_close_issues_script(results):
    """Generate a shell script to close invalid issues."""
    script = "#!/bin/bash\n\n"
    script += "# Script to close invalid issues\n\n"
    script += "# This script was automatically generated\n\n"

    # Filter for invalid issues
    invalid_issues = [r for r in results.values() if not r["valid"]]

    script += f"echo 'Closing {len(invalid_issues)} invalid issues...'\n\n"

    for issue in invalid_issues:
        issue_number = issue["issue_number"]
        reason = issue["reason"]

        # Create a comment explaining why the issue is being closed
        comment = f"This application is being closed because: {reason}."
        if "stars" in reason and "need 10+" in reason:
            comment += " Please reapply when your repository has at least 10 stars."
        elif "Repository not found" in reason:
            comment += " The repository URL provided is not accessible or does not exist."
        elif "No repository URL found" in reason:
            comment += " No valid GitHub repository URL was found in the issue or comments."

        # Escape quotes in the comment for shell
        escaped_comment = comment.replace('"', '\\"')

        # Add the command to close the issue with a comment
        script += f'echo "Closing issue #{issue_number}..."\n'
        script += f'gh issue close gridaco/cors.sh/{issue_number} --comment "{escaped_comment}"\n'
        script += 'echo "Done"\n\n'

    script += "echo 'All invalid issues have been closed.'\n"

    return script

def get_all_issues_with_label(repo, label):
    """Get all issues with a specific label, handling pagination."""
    console.print(f"Fetching all issues with '{label}' label from {repo}...")

    # Use the API directly to get all issues with the label
    issues = run_gh_command([
        "gh", "api",
        f"repos/{repo}/issues?state=open&labels={label}&per_page=100"
    ], handle_pagination=True)

    if not issues:
        return []

    # Extract the fields we need
    return [{"number": issue["number"], "title": issue["title"]} for issue in issues]

def main():
    # Check if gh CLI is installed
    try:
        subprocess.run(["gh", "--version"], capture_output=True, check=True)
    except (subprocess.CalledProcessError, FileNotFoundError):
        console.print("[red]GitHub CLI (gh) is not installed or not working properly.[/red]")
        console.print("Please install it from https://cli.github.com/")
        sys.exit(1)

    # Check if user is authenticated with gh
    try:
        subprocess.run(["gh", "auth", "status"], capture_output=True, check=True)
    except subprocess.CalledProcessError:
        console.print("[red]You are not authenticated with GitHub CLI.[/red]")
        console.print("Please run 'gh auth login' first.")
        sys.exit(1)

    # Get all open issues with the "application" label
    issues = get_all_issues_with_label("gridaco/cors.sh", "application")

    if not issues:
        console.print("[yellow]No open issues with 'application' label found.[/yellow]")
        return

    console.print(f"Found [green]{len(issues)}[/green] open issues with 'application' label. Analyzing...")

    results = {}

    with Progress(
        TextColumn("[progress.description]{task.description}"),
        BarColumn(),
        TaskProgressColumn(),
        console=console
    ) as progress:
        task = progress.add_task("[cyan]Processing issues...", total=len(issues))

        for issue in issues:
            issue_number = issue["number"]
            issue_title = issue["title"]

            progress.update(task, description=f"[cyan]Processing issue #{issue_number}[/cyan]")

            # Get all comments for this issue
            all_comments = run_gh_command(["gh", "api", f"repos/gridaco/cors.sh/issues/{issue_number}/comments"], handle_pagination=True)

            # Check if there are any comments
            first_comment_body = None
            if all_comments and len(all_comments) > 0:
                first_comment_body = all_comments[0].get("body", "")

            # Get the issue body
            issue_data = run_gh_command(["gh", "api", f"repos/gridaco/cors.sh/issues/{issue_number}"])
            issue_body = issue_data.get("body", "") if issue_data else ""

            # Try to find repo URL in first comment, then in issue body
            repo_url = None
            if first_comment_body:
                repo_url = extract_repo_url(first_comment_body)

            if not repo_url and issue_body:
                repo_url = extract_repo_url(issue_body)

            # Initialize status dict for this issue
            status = {
                "issue_number": issue_number,
                "issue_title": issue_title,
                "repo_url": repo_url,
                "valid": False,
                "reason": "No repository URL found"
            }

            if repo_url:
                stars = get_repo_stars(repo_url)
                status["stars"] = stars

                if stars is None:
                    status["reason"] = "Repository not found"
                elif isinstance(stars, int):
                    if stars >= 10:
                        status["valid"] = True
                        status["reason"] = f"{stars} stars (valid)"
                    else:
                        status["reason"] = f"Only {stars} stars (need 10+)"
                else:
                    status["reason"] = "Unknown star count"

            results[issue_number] = status
            progress.advance(task)

    # Print summary table to console
    table = Table(title="Repository Star Analysis Results")

    table.add_column("Issue #", justify="right", style="cyan")
    table.add_column("Title", style="magenta")
    table.add_column("Repository", style="blue")
    table.add_column("Stars", justify="right")
    table.add_column("Valid", justify="center")
    table.add_column("Reason", style="green")

    # Sort by validity and stars
    sorted_results = sorted(
        results.values(),
        key=lambda x: (
            x["valid"],
            x.get("stars", 0) if isinstance(x.get("stars", 0), int) else -1
        ),
        reverse=True
    )

    for result in sorted_results:
        stars_display = str(result.get("stars", "N/A"))
        valid_display = "✅" if result["valid"] else "❌"

        # Ensure repo_url is a string for display
        repo_url_display = result.get("repo_url", "N/A")
        if repo_url_display is None:
            repo_url_display = "N/A"

        table.add_row(
            f"#{result['issue_number']}",
            result["issue_title"],
            repo_url_display,
            stars_display,
            valid_display,
            result["reason"]
        )

    console.print(table)

    # Print summary stats
    valid_count = sum(1 for r in results.values() if r["valid"])
    console.print(f"\n[bold]Summary:[/bold] {valid_count} valid repositories out of {len(results)} issues")

    # Generate and save markdown table
    markdown_content = generate_markdown_table(results)
    with open("application_statuses.md", "w") as f:
        f.write(markdown_content)

    console.print(f"\n[green]Markdown table saved to[/green] application_statuses.md")

    # Generate and save close issues shell script
    close_issues_script = generate_close_issues_script(results)
    with open("close_issues.sh", "w") as f:
        f.write(close_issues_script)

    # Make the script executable
    os.chmod("close_issues.sh", 0o755)

    console.print(f"[green]Close issues script saved to[/green] close_issues.sh")
    console.print("[yellow]Run ./close_issues.sh to close invalid issues[/yellow]")

if __name__ == "__main__":
    main()

I also got it to generate a bash script with GitHub CLI commands to close the invalid issues with a reason, so that a maintainer can do that in a single command:

close_issues.sh
#!/bin/bash

# Script to close invalid issues

# This script was automatically generated

echo 'Closing 33 invalid issues...'

echo "Closing issue #146..."
gh issue close gridaco/cors.sh/146 --comment "This application is being closed because: Repository not found. The repository URL provided is not accessible or does not exist."
echo "Done"

echo "Closing issue #143..."
gh issue close gridaco/cors.sh/143 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #140..."
gh issue close gridaco/cors.sh/140 --comment "This application is being closed because: Only 0 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #133..."
gh issue close gridaco/cors.sh/133 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #126..."
gh issue close gridaco/cors.sh/126 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #125..."
gh issue close gridaco/cors.sh/125 --comment "This application is being closed because: Only 1 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #124..."
gh issue close gridaco/cors.sh/124 --comment "This application is being closed because: Repository not found. The repository URL provided is not accessible or does not exist."
echo "Done"

echo "Closing issue #110..."
gh issue close gridaco/cors.sh/110 --comment "This application is being closed because: Only 8 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #95..."
gh issue close gridaco/cors.sh/95 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #87..."
gh issue close gridaco/cors.sh/87 --comment "This application is being closed because: Only 4 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #84..."
gh issue close gridaco/cors.sh/84 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #82..."
gh issue close gridaco/cors.sh/82 --comment "This application is being closed because: Only 0 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #79..."
gh issue close gridaco/cors.sh/79 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #78..."
gh issue close gridaco/cors.sh/78 --comment "This application is being closed because: Only 0 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #72..."
gh issue close gridaco/cors.sh/72 --comment "This application is being closed because: Repository not found. The repository URL provided is not accessible or does not exist."
echo "Done"

echo "Closing issue #71..."
gh issue close gridaco/cors.sh/71 --comment "This application is being closed because: Repository not found. The repository URL provided is not accessible or does not exist."
echo "Done"

echo "Closing issue #70..."
gh issue close gridaco/cors.sh/70 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #69..."
gh issue close gridaco/cors.sh/69 --comment "This application is being closed because: Only 2 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #59..."
gh issue close gridaco/cors.sh/59 --comment "This application is being closed because: Only 0 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #58..."
gh issue close gridaco/cors.sh/58 --comment "This application is being closed because: Only 1 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #57..."
gh issue close gridaco/cors.sh/57 --comment "This application is being closed because: Repository not found. The repository URL provided is not accessible or does not exist."
echo "Done"

echo "Closing issue #56..."
gh issue close gridaco/cors.sh/56 --comment "This application is being closed because: Only 7 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #37..."
gh issue close gridaco/cors.sh/37 --comment "This application is being closed because: Only 5 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #36..."
gh issue close gridaco/cors.sh/36 --comment "This application is being closed because: Only 7 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #33..."
gh issue close gridaco/cors.sh/33 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #32..."
gh issue close gridaco/cors.sh/32 --comment "This application is being closed because: Only 0 stars (need 10+). Please reapply when your repository has at least 10 stars."
echo "Done"

echo "Closing issue #29..."
gh issue close gridaco/cors.sh/29 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #23..."
gh issue close gridaco/cors.sh/23 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #21..."
gh issue close gridaco/cors.sh/21 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #17..."
gh issue close gridaco/cors.sh/17 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #16..."
gh issue close gridaco/cors.sh/16 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #11..."
gh issue close gridaco/cors.sh/11 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo "Closing issue #9..."
gh issue close gridaco/cors.sh/9 --comment "This application is being closed because: No repository URL found. No valid GitHub repository URL was found in the issue or comments."
echo "Done"

echo 'All invalid issues have been closed.'

The results are below: 18 valid repositories out of 51 issues

Note that it's kind of strict about following the issue template, and also expecting a GitHub repo and only that. So maybe handle with some care 😅

Hope this helps a little!

Issue # Title Repository Stars Valid Reason
#94 Apply for OSS Program: RSSHub Radar https://github.com/DIYgod/RSSHub-Radar 6177 6177 stars (valid)
#109 Github HTML Preview https://github.com/htmlpreview/htmlpreview.github.com 1595 1595 stars (valid)
#137 Food Ordering App Open Source Project https://github.com/kubesre/docker-registry-mirrors 1384 1384 stars (valid)
#149 [Application] for my OSS Project: sra-explorer.info https://github.com/ewels/sra-explorer 213 213 stars (valid)
#77 Mastertex https://github.com/gridaco/cors.sh 209 209 stars (valid)
#142 Syncify https://github.com/panoply/syncify 145 145 stars (valid)
#89 Nujan IDE https://github.com/nujan-io/nujan-ide 145 145 stars (valid)
#76 Application for OSS program for Lyrixed https://github.com/Nuzair46/Lyrixed 123 123 stars (valid)
#128 Syntithenai Chat https://github.com/syntithenai/hermod 94 94 stars (valid)
#129 seasick/openscad-web-gui https://github.com/seasick/openscad-web-gui 64 64 stars (valid)
#138 tipitaka.lk https://github.com/pathnirvana/tipitaka.lk 48 48 stars (valid)
#74 NBN Availability Extension https://github.com/LukePrior/nbn-availability-extension 24 24 stars (valid)
#66 Apply for OSS Program - Schedule Helper [Open-Source Club @ University of Florida] https://github.com/ufosc/Schedule_Helper 22 22 stars (valid)
#103 TTV Tools https://github.com/ephellon/twitch-tools 21 21 stars (valid)
#73 NostrOP OSS application https://github.com/franzos/nostr-ts 21 21 stars (valid)
#116 Food Ordering App Open Source Project https://github.com/jain-rithik/Zaika 17 17 stars (valid)
#144 AuthLeu https://github.com/jlcvp/AuthLeu 13 13 stars (valid)
#123 Free request https://github.com/BradPerbs/GPT-Rewriter 13 13 stars (valid)
#110 BZ-Next WebGL BZFlag tools and client https://github.com/bz-next/bz-next 8 Only 8 stars (need 10+)
#56 OOS Application for osu-cyberball https://github.com/CuddleBunny/osu-cyberball 7 Only 7 stars (need 10+)
#36 GhotBypasser [OSS Project Appication] https://github.com/megz15/ghotbypasser 7 Only 7 stars (need 10+)
#37 CORS for https://github.com/TomRadford/shootdrop 5 Only 5 stars (need 10+)
#87 NFTJukebox https://github.com/Martibis/nft-jukebox-frontend 4 Only 4 stars (need 10+)
#69 Application for aceess to free pro-plan of cors.sh https://github.com/InventorsDev/Team-Orange 2 Only 2 stars (need 10+)
#125 So Many Current Players https://github.com/infntyrepeating/soManyCurrentPlayers 1 Only 1 stars (need 10+)
#58 Requesting a proxy https://github.com/godjooyoung/sailing-bamboo 1 Only 1 stars (need 10+)
#143 odigo N/A N/A No repository URL found
#140 MyPort https://github.com/cpxdev-frontend/cp-myport 0 Only 0 stars (need 10+)
#133 Suggest keyword N/A N/A No repository URL found
#126 q N/A N/A No repository URL found
#95 [Application] for my OSS Project N/A N/A No repository URL found
#84 GD Web N/A N/A No repository URL found
#82 need cros proxy https://github.com/Alaminpramanik/Eco 0 Only 0 stars (need 10+)
#79 Teste Request Cors Broken N/A N/A No repository URL found
#78 News summary https://github.com/truongduongmau/tin-tuc 0 Only 0 stars (need 10+)
#70 zone N/A N/A No repository URL found
#59 mini-project https://github.com/soyunhan/Together_TeamProject 0 Only 0 stars (need 10+)
#33 OSS program application N/A N/A No repository URL found
#32 [Application] for my OSS Project https://github.com/lawrenceshava/roadside 0 Only 0 stars (need 10+)
#29 Hytale Tracker N/A N/A No repository URL found
#23 Open Source Application N/A N/A No repository URL found
#21 george.barbu.cc N/A N/A No repository URL found
#17 Application for my OSS Project N/A N/A No repository URL found
#16 Personal portfolio - https://lukebottle.github.io/luke-milner/ N/A N/A No repository URL found
#11 FFO Application: https://sketchy-bot.github.io/sketch/ N/A N/A No repository URL found
#9 FFO Application: api.enime.moe N/A N/A No repository URL found
#146 Filter https://github.com/SaurabhBhatiaodesk/filtersync None Repository not found
#124 web based china "vpn" https://github.com/techniixcom/wbcvpn.git None Repository not found
#72 Kothar Dashboard https://github.com/rbdiwash/KotharDashboard None Repository not found
#71 keywordtool https://github.com/Gemy-Dev/keywordtool.github.io None Repository not found
#57 Al Yamamah web app sponsorship https://github.com/YazeedAlKhalaf/alyamamah None Repository not found

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions