Skip to content

Conversation

@DeveloperAmrit
Copy link

@DeveloperAmrit DeveloperAmrit commented Feb 1, 2026

Description

This Pull Request introduces Gitcord, a specialized Discord bot that bridges the gap between GitHub organizations and their Discord communities. Gitcord automates the synchronization of user identities, tracks development activity in real-time, and incentivizes contributions through a gamified scoring and role-management system.

Key features

  1. Secure Social Identity Verification:
  • Users link Discord accounts to GitHub via the /link command.
  • Verification is performed by checking the user's GitHub Social Accounts profile, providing a lightweight yet secure alternative to full OAuth flows.
  1. Real-Time GitHub Activity Tracking:
  • Leverages GitHub's Event API with ETag-based caching for efficient, rate-limit-conscious polling.
  • Automatically monitors Pull Requests, Issues (creation/assignment), and Commits.
  1. Gamified Scoring System:
  • Assigns points to contributors based on their open-source activity (Configurable via config.yaml).
  • Supports awarding points for PR merges, issue resolution, and more.
  1. Interactive Role Management:
  • Automatically promotes users to Discord roles like Contributor or Maintainer once they reach specific point thresholds.
  1. Admin Dashboard & Repo Management:
  • Slash commands for administrators to manage monitored repositories (/addrepo, /removerepo) and manually adjust user scores.
  1. Automated Task Matching:
    Notifies potential contributors of good-first-issue tasks tailored to their activity level.

Commands

  1. Repo management (admin only)
  • /repo add <repo_urls>
  • /repo remove <repo_url>
    • Description: Unlinks a specific repository from the current channel, stopping activity tracking for it.
  1. Maintainer management (admin only)
  • /maintainer add <repo_url>
    • Designates a specific Discord user as a "Maintainer" for a repository.
    • Note: The user must have already linked their account using /link.
  • /maintainer remove <repo_url>
    • Description: Removes the maintainer status from a user for a specific repository.
  1. User linking (all memebers)
    -/link <github_username>
    • Description: Links your Discord account to your GitHub profile.
    • Verification: Requires you to add https://discord.com/users/<your_discord_id> to the Social Accounts section of your GitHub profile before running the command.

Video

My.Movie.mp4

Summary by CodeRabbit

Release Notes

  • New Features

    • Link your Discord account to your GitHub profile for activity tracking
    • Admin commands to manage repository connections and project maintainers
    • Automatic GitHub event monitoring with activity-based scoring
    • Discord notifications for repository issues, pull requests, and reviews
  • Documentation

    • Updated README with command syntax and usage instructions
  • Chores

    • Added project dependencies and configuration structure

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

📝 Walkthrough

Walkthrough

A new Discord bot project is introduced with GitHub integration, featuring configuration loading, SQLite database persistence, three Discord cogs for user verification and admin management, a GitHub API client supporting GraphQL and REST endpoints, and centralized error handling for bot commands and interactions.

Changes

Cohort / File(s) Summary
Configuration & Metadata
.gitignore, README.md, requirements.txt
Gitignore updated with config.yaml and venv/; README rewritten as concise instruction block; new requirements file specifies discord.py, PyYAML, and requests dependencies.
Core Bot Infrastructure
src/main.py
Bot initialization with configuration loading from config.yaml, database setup, cog loading (verification, events, admin), application command sync, and dual error handlers for app commands and prefix commands with admin-specific error details.
Database Layer
src/database.py
SQLite database module with CRUD operations for users, repositories, maintainers, and activity tracking; includes etag management for GitHub events, event deduplication, and user score updates.
GitHub API Integration
src/github_client.py
Client class for GitHub interactions using GraphQL (identity verification) and REST API (repo events, user activity, issue searches); implements etag-based caching and error handling.
Discord Cogs
src/cogs/verification.py, src/cogs/admin.py, src/cogs/events.py
Three cogs: Verification handles Discord-GitHub user linking via ownership verification; Admin manages repository and maintainer associations per channel; Events processes GitHub activities and updates user scores with point distribution based on event type.

Sequence Diagram(s)

sequenceDiagram
    participant User as Discord User
    participant Verification as Verification Cog
    participant GitHub as GitHub API
    participant Database as Database
    participant Discord as Discord Bot

    User->>Verification: /link github_username
    Verification->>GitHub: verify_identity(github_username, discord_id)
    GitHub-->>Verification: Check social accounts
    Verification->>Database: get_user_by_discord(discord_id)
    Database-->>Verification: User exists or null
    alt User already linked
        Verification->>Discord: Show existing GitHub username
    else New user
        Verification->>Discord: "Verifying ownership…"
        GitHub-->>Verification: Match found or not found
        alt Identity verified
            Verification->>Database: add_user(discord_id, github_username)
            Database-->>Verification: Success
            Verification->>Discord: Confirm linking
        else Verification failed
            Verification->>Discord: Update profile with Discord ID
        end
    end
Loading
sequenceDiagram
    participant Events as Events Cog
    participant GitHub as GitHub API
    participant Database as Database
    participant Discord as Discord API

    Events->>Database: get_repos()
    Database-->>Events: All repositories
    loop For each repo
        Events->>GitHub: get_repo_events(owner, name, etag)
        GitHub-->>Events: New events + updated etag
        loop For each event
            Events->>Database: is_event_processed(event_id)
            alt Not processed
                Events->>Database: get_discord_from_github(actor)
                Database-->>Events: discord_id, current_score
                Events->>Database: update_score(discord_id, points)
                Events->>Discord: Post event notification/review summary
                Events->>Database: mark_event_processed(event_id)
            end
        end
        Events->>Database: update_repo_etag(repo_id, new_etag)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Poem

🐰 A bot that hops through Discord streams,
Linking users to GitHub dreams,
Events processed with fuzzy precision,
Admins reign with authority's vision,
Now scores spring forth—hop, hop, hooray!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Added basic bot' is vague and generic; it lacks specificity about what the bot does or its key features. Consider a more descriptive title that reflects the main functionality, such as 'Introduce Gitcord Discord bot with GitHub integration' or 'Add GitHub activity tracker and gamified scoring bot'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size/XL and removed size/XL labels Feb 1, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In @.gitignore:
- Around line 1-3: Add an ignore rule for the SQLite DB created by
src/database.py (gitcord.db) to prevent committing user data: update .gitignore
to include a root-scoped entry like /gitcord.db (or a broader pattern such as
*.db if preferred) so the file is ignored, and if gitcord.db is already tracked
remove it from the index with git rm --cached gitcord.db and commit the
.gitignore change.

In `@README.md`:
- Around line 19-31: Update README.md so the usage matches the implemented slash
command: replace the `!link <github_username>` example with the registered
`/link` slash command syntax and any subcommand or option names used by the bot.
Clarify the ambiguous config creation step: instruct users to copy the provided
example file (e.g., config.example.yaml or template), rename to config.yaml, and
fill in Token, Guild ID, and Org Name (and list required GitHub token scopes:
read:org, read:user, repo). Ensure the README explicitly states that config.yaml
is gitignored and must be created from the example template.

In `@src/cogs/admin.py`:
- Around line 77-86: Add a repo linkage check and fix two minor issues: before
calling add_maintainer(user.id, repo_url) query the repos table (e.g., lookup by
repo_url via the existing repo access function or a new get_repo_by_url) and if
no repo record exists respond with an ephemeral message telling the user the
repo isn't linked; correct the indentation of the db_user not-found block so it
uses 8 spaces to match surrounding code (the line with await
interaction.response.send_message) and remove the extraneous f prefix on the
final await interaction.response.send_message that has no placeholders so it is
a plain string.

In `@src/cogs/events.py`:
- Around line 45-60: The current loop updates update_repo_etag(repo_id,
new_etag) even when any event processing fails and also skips storing the ETag
when events is empty; change the logic so that you track success across the
reversed(events) loop (e.g., a boolean like all_processed=True), only call
mark_event_processed(event['id']) after a successful await
self.process_event(...) and set all_processed=False if an exception occurs (log
the error), and then call update_repo_etag(repo_id, new_etag) only when new_etag
is set and all_processed is True OR when events is empty (so empty responses
still update the ETag). Ensure is_event_processed(event['id']) remains to skip
already-processed events.
- Around line 1-6: The synchronous requests.get call in get_repo_events is being
invoked directly from the async task sync_events and can block the event loop;
change the call so the HTTP request runs in a threadpool (e.g., wrap
requests.get in asyncio.to_thread or loop.run_in_executor) inside
get_repo_events or when calling it from sync_events, await the threaded
execution and propagate the result, ensuring get_repo_events and sync_events
remain async-compatible and do not perform blocking I/O on the main loop.

In `@src/cogs/verification.py`:
- Around line 1-8: The async command handler verify_identity currently calls
requests.post synchronously (requests.post), blocking the event loop; update
verify_identity to offload that blocking HTTP call by wrapping the requests.post
invocation in asyncio.to_thread (or otherwise run it in a worker thread) and
await the result, so the handler remains non-blocking; ensure you import asyncio
at top and replace direct requests.post usage in the verify_identity function
with an awaited asyncio.to_thread call that passes the same arguments and
handles the returned response exactly as before.

In `@src/database.py`:
- Around line 6-9: The get_connection() function currently opens SQLite
connections without enabling foreign key enforcement; update get_connection() to
execute the PRAGMA foreign_keys=ON (e.g., run a statement on the returned
sqlite3.Connection) immediately after sqlite3.connect(DB_PATH) and before
returning so that constraints on the maintainers and activity_log tables are
enforced for add_maintainer(), log_activity(), and other DB operations.
- Around line 16-22: The users table schema needs the github_username UNIQUE
constraint to be case-insensitive: change the CREATE TABLE users column
definition from "github_username TEXT UNIQUE" to "github_username TEXT COLLATE
NOCASE UNIQUE" so inserts via add_user() will be prevented from creating
case-variant duplicates (reads in get_discord_from_github() already use COLLATE
NOCASE); for existing databases include a migration to rebuild the users
table/constraint.

In `@src/github_client.py`:
- Around line 35-120: All HTTP calls in this module (the Discord verification
loop and the methods get_user_activity, get_repo_events, and
get_open_issues_with_label) must include a timeout and tighten exception
handling: add timeout=10 to every requests.get/post call, replace broad "except
Exception" blocks with "except (requests.RequestException, ValueError)" and use
logging.exception(...) inside those handlers to capture the traceback; also keep
existing return behavior (False or [] as appropriate) after logging. Use the
method names get_user_activity, get_repo_events, get_open_issues_with_label and
the verification function around the socialAccounts loop to locate and update
each call.

In `@src/main.py`:
- Around line 27-41: The on_ready handler currently calls
bot.load_extension('cogs.verification'/'cogs.events'/'cogs.admin'), which will
raise ExtensionAlreadyLoaded on reconnect; move cog loading out of on_ready by
either loading them once before bot.run() or implementing an async setup hook
(define bot.setup_hook and call await bot.load_extension(...) for each cog
there), and keep the bot.tree.sync call in on_ready (or move it into setup_hook)
so load_extension is not invoked on every reconnect; alternatively, if you must
keep it in on_ready, wrap each load_extension call in a try/except catching
discord.ext.commands.errors.ExtensionAlreadyLoaded to skip already-loaded cogs.
🧹 Nitpick comments (10)
requirements.txt (1)

1-3: Consider pinning dependency versions for reproducible deploys.

Unpinned discord.py, PyYAML, and requests can introduce breaking changes; please pin or add a constraints/lock file, and ensure the selected discord.py version supports discord.app_commands.

src/github_client.py (1)

75-77: Use params for search queries to avoid manual URL encoding.

Building the URL with params={"q": query} avoids edge cases with escaping and is more idiomatic for requests.

♻️ Suggested refactor
-        url = f"{self.rest_url}/search/issues?q={query}"
+        url = f"{self.rest_url}/search/issues"
@@
-            response = requests.get(url, headers=self.headers, timeout=10)
+            response = requests.get(url, headers=self.headers, params={"q": query}, timeout=10)
@@
-        url = f"{self.rest_url}/search/issues?q={query}"
+        url = f"{self.rest_url}/search/issues"
@@
-            response = requests.get(url, headers=self.headers, timeout=10)
+            response = requests.get(url, headers=self.headers, params={"q": query}, timeout=10)

Also applies to: 112-114

src/cogs/events.py (1)

12-18: Avoid bare except on config load and fail fast with a clear error.

The fallback config lacks required github keys, so the bot will crash later with a KeyError. Catch specific exceptions and surface a clear error; also ensure scoring defaults are present.

🛠️ Suggested fix
 try:
     with open(CONFIG_PATH, 'r') as f:
-        config = yaml.safe_load(f)
-except:
-    config = {'scoring': {'points': {}}}
+        config = yaml.safe_load(f) or {}
+except (FileNotFoundError, yaml.YAMLError) as e:
+    raise RuntimeError("config.yaml missing or invalid") from e
+
+config.setdefault("scoring", {}).setdefault("points", {})
src/cogs/verification.py (1)

10-12: Handle missing/invalid config.yaml with a clear error.

A missing or malformed config will crash the module at import time without context. Catch specific exceptions and raise a clear error message.

🛠️ Suggested fix
 CONFIG_PATH = os.path.join(os.path.dirname(__file__), '../../config.yaml')
-with open(CONFIG_PATH, 'r') as f:
-    config = yaml.safe_load(f)
+try:
+    with open(CONFIG_PATH, 'r') as f:
+        config = yaml.safe_load(f) or {}
+except (FileNotFoundError, yaml.YAMLError) as e:
+    raise RuntimeError("config.yaml missing or invalid") from e
src/cogs/admin.py (3)

11-12: Dead code with typo — remove or fix and use consistently.

The class-level GITHUB_REPO_REGEX contains a typo (space before /) making it invalid, but it's never used anyway — lines 31 and 73 define inline regex instead. Either remove this constant or fix it and reference self.GITHUB_REPO_REGEX throughout the class to avoid duplication.

♻️ Proposed fix: Extract and reuse a correct regex constant
-    # Regex for basic GitHub repo URL validation
-    GITHUB_REPO_REGEX = r"^https?://github\.com/[\w\-\.]+ /[\w\-\.]+(/)?$"
+    # Regex for basic GitHub repo URL validation
+    GITHUB_REPO_REGEX = re.compile(r"^https?://github\.com/[\w\-\.]+/[\w\-\.]+$")

Then update usages at lines 31 and 73:

-            if not re.match(r"^https?://github\.com/[\w\-\.]+/[\w\-\.]+$", url):
+            if not self.GITHUB_REPO_REGEX.match(url):

28-28: Style: Avoid multiple statements on one line.

Static analysis flagged this line. Split across two lines for readability.

✨ Suggested fix
-            if not url: continue
+            if not url:
+                continue

88-95: Fix indentation and remove extraneous f-prefix.

Line 95 has inconsistent indentation and an f-string with no placeholders.

✨ Suggested fix
         if remove_maintainer(user.id, repo_url):
             await interaction.response.send_message(f"✅ Removed {user.mention} from maintainers of {repo_url}")
         else:
-             await interaction.response.send_message(f"❌ Failed to remove. Check repo URL and if they are a maintainer.", ephemeral=True)
+            await interaction.response.send_message("❌ Failed to remove. Check repo URL and if they are a maintainer.", ephemeral=True)
src/main.py (3)

9-14: Consider handling malformed YAML.

If config.yaml exists but contains invalid YAML syntax, yaml.safe_load raises yaml.YAMLError, which would crash with an unhelpful traceback.

✨ Suggested enhancement
 try:
     with open(CONFIG_PATH, 'r') as f:
         config = yaml.safe_load(f)
 except FileNotFoundError:
     print("config.yaml not found!")
     sys.exit(1)
+except yaml.YAMLError as e:
+    print(f"Error parsing config.yaml: {e}")
+    sys.exit(1)

52-66: Remove extraneous f-prefixes on static strings.

Lines 53, 59, and 64 use f-strings without placeholders. Line 66 can use !s conversion flag instead of explicit str().

✨ Suggested fixes
     if isinstance(error, discord.app_commands.errors.MissingPermissions):
-        await interaction.response.send_message(f"❌ You don't have the required permissions to run this command.", ephemeral=True)
+        await interaction.response.send_message("❌ You don't have the required permissions to run this command.", ephemeral=True)
         return
     elif isinstance(error, discord.app_commands.errors.CommandOnCooldown):
         await interaction.response.send_message(f"⌛ Command is on cooldown. Try again in {error.retry_after:.2f}s.", ephemeral=True)
         return
     elif isinstance(error, discord.app_commands.errors.CheckFailure):
-        await interaction.response.send_message(f"❌ You are not authorized to use this command.", ephemeral=True)
+        await interaction.response.send_message("❌ You are not authorized to use this command.", ephemeral=True)
         return

     # Ultimate error handler
     print(f"Unhandled command error: {error}")
-    error_msg = f"❌ something went wrong"
+    error_msg = "❌ Something went wrong"
     if is_admin:
-        error_msg += f", Error: `{str(error)}`"
+        error_msg += f", Error: `{error!s}`"

86-91: Same f-string cleanup needed here.

✨ Suggested fix
     print(f"Prefix command error: {error}")
-    error_msg = f"❌ something went wrong"
+    error_msg = "❌ Something went wrong"
     if is_admin:
-        error_msg += f", Error: `{str(error)}`"
+        error_msg += f", Error: `{error!s}`"

Comment on lines +1 to +3
## critical
config.yaml

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ignore the SQLite DB to prevent committing user data.

src/database.py writes gitcord.db at repo root and it contains Discord IDs; it should be ignored.

🛡️ Proposed fix
 ## critical
 config.yaml
+gitcord.db
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## critical
config.yaml
## critical
config.yaml
gitcord.db
🤖 Prompt for AI Agents
In @.gitignore around lines 1 - 3, Add an ignore rule for the SQLite DB created
by src/database.py (gitcord.db) to prevent committing user data: update
.gitignore to include a root-scoped entry like /gitcord.db (or a broader pattern
such as *.db if preferred) so the file is ignored, and if gitcord.db is already
tracked remove it from the index with git rm --cached gitcord.db and commit the
.gitignore change.

Comment on lines +19 to +31
2. **Configuration**:
* Rename `config.yaml` and fill in your Token, Guild ID, and Org Name.
* **GitHub Token**: Needs `read:org`, `read:user`, `repo` scopes.

<!-- Organization/Project Social Handles -->
<p align="center">
<!-- Telegram -->
<a href="https://t.me/StabilityNexus">
<img src="https://img.shields.io/badge/Telegram-black?style=flat&logo=telegram&logoColor=white&logoSize=auto&color=24A1DE" alt="Telegram Badge"/></a>
&nbsp;&nbsp;
<!-- X (formerly Twitter) -->
<a href="https://x.com/aossie_org">
<img src="https://img.shields.io/twitter/follow/aossie_org" alt="X (formerly Twitter) Badge"/></a>
&nbsp;&nbsp;
<!-- Discord -->
<a href="https://discord.gg/hjUhu33uAn">
<img src="https://img.shields.io/discord/1022871757289422898?style=flat&logo=discord&logoColor=white&logoSize=auto&label=Discord&labelColor=5865F2&color=57F287" alt="Discord Badge"/></a>
&nbsp;&nbsp;
<!-- Medium -->
<a href="https://news.stability.nexus/">
<img src="https://img.shields.io/badge/Medium-black?style=flat&logo=medium&logoColor=black&logoSize=auto&color=white" alt="Medium Badge"></a>
&nbsp;&nbsp;
<!-- LinkedIn -->
<a href="https://www.linkedin.com/company/aossie/">
<img src="https://img.shields.io/badge/LinkedIn-black?style=flat&logo=LinkedIn&logoColor=white&logoSize=auto&color=0A66C2" alt="LinkedIn Badge"></a>
&nbsp;&nbsp;
<!-- Youtube -->
<a href="https://www.youtube.com/@StabilityNexus">
<img src="https://img.shields.io/youtube/channel/subscribers/UCZOG4YhFQdlGaLugr_e5BKw?style=flat&logo=youtube&logoColor=white&logoSize=auto&labelColor=FF0000&color=FF0000" alt="Youtube Badge"></a>
</p>
3. **Run**:
```bash
python src/main.py
```

---
## Usage

<div align="center">
<h1>TODO: Project Name</h1>
</div>

[TODO](https://TODO.stability.nexus/) is a ... TODO: Project Description.

---

## 🚀 Features

TODO: List your main features here:

- **Feature 1**: Description
- **Feature 2**: Description
- **Feature 3**: Description
- **Feature 4**: Description

---

## 💻 Tech Stack

TODO: Update based on your project

### Frontend
- React / Next.js / Flutter / React Native
- TypeScript
- TailwindCSS

### Backend
- Flask / FastAPI / Node.js / Supabase
- Database: PostgreSQL / SQLite / MongoDB

### AI/ML (if applicable)
- LangChain / LangGraph / LlamaIndex
- Google Gemini / OpenAI / Anthropic Claude
- Vector Database: Weaviate / Pinecone / Chroma
- RAG / Prompt Engineering / Agent Frameworks

### Blockchain (if applicable)
- Solidity / solana / cardano / ergo Smart Contracts
- Hardhat / Truffle / foundry
- Web3.js / Ethers.js / Wagmi
- OpenZeppelin / alchemy / Infura

---

## ✅ Project Checklist

TODO: Complete applicable items based on your project type

- [ ] **The protocol** (if applicable):
- [ ] has been described and formally specified in a paper.
- [ ] has had its main properties mathematically proven.
- [ ] has been formally verified.
- [ ] **The smart contracts** (if applicable):
- [ ] were thoroughly reviewed by at least two knights of The Stable Order.
- [ ] were deployed to: [Add deployment details]
- [ ] **The mobile app** (if applicable):
- [ ] has an _About_ page containing the Stability Nexus's logo and pointing to the social media accounts of the Stability Nexus.
- [ ] is available for download as a release in this repo.
- [ ] is available in the relevant app stores.
- [ ] **The AI/ML components** (if applicable):
- [ ] LLM/model selection and configuration are documented.
- [ ] Prompts and system instructions are version-controlled.
- [ ] Content safety and moderation mechanisms are implemented.
- [ ] API keys and rate limits are properly managed.

---

## 🔗 Repository Links

TODO: Update with your repository structure

1. [Main Repository](https://github.com/AOSSIE-Org/TODO)
2. [Frontend](https://github.com/AOSSIE-Org/TODO/tree/main/frontend) (if separate)
3. [Backend](https://github.com/AOSSIE-Org/TODO/tree/main/backend) (if separate)

---

## 🏗️ Architecture Diagram

TODO: Add your system architecture diagram here

```
[Architecture Diagram Placeholder]
```

You can create architecture diagrams using:
- [Draw.io](https://draw.io)
- [Excalidraw](https://excalidraw.com)
- [Lucidchart](https://lucidchart.com)
- [Mermaid](https://mermaid.js.org) (for code-based diagrams)

Example structure to include:
- Frontend components
- Backend services
- Database architecture
- External APIs/services
- Data flow between components

---

## 🔄 User Flow

TODO: Add user flow diagrams showing how users interact with your application

```
[User Flow Diagram Placeholder]
```

### Key User Journeys

TODO: Document main user flows:

1. **User Journey 1**: Description
- Step 1
- Step 2
- Step 3

2. **User Journey 2**: Description
- Step 1
- Step 2
- Step 3

3. **User Journey 3**: Description
- Step 1
- Step 2
- Step 3

---

## �🍀 Getting Started

### Prerequisites

TODO: List what developers need installed

- Node.js 18+ / Python 3.9+ / Flutter SDK
- npm / yarn / pnpm
- [Any specific tools or accounts needed]

### Installation

TODO: Provide detailed setup instructions

#### 1. Clone the Repository

```bash
git clone https://github.com/AOSSIE-Org/TODO.git
cd TODO
```

#### 2. Install Dependencies

```bash
npm install
# or
yarn install
# or
pnpm install
```

#### 3. Configure Environment Variables(.env.example)

Create a `.env` file in the root directory:

```env
# Add your environment variables here
API_KEY=your_api_key
DATABASE_URL=your_database_url
```

#### 4. Run the Development Server

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```

#### 5. Open your Browser

Navigate to [http://localhost:3000](http://localhost:3000) to see the application.

For detailed setup instructions, please refer to our [Installation Guide](./docs/INSTALL_GUIDE.md) (if you have one).

---

## 📱 App Screenshots

TODO: Add screenshots showcasing your application

| | | |
|---|---|---|
| Screenshot 1 | Screenshot 2 | Screenshot 3 |

---

## 🙌 Contributing

⭐ Don't forget to star this repository if you find it useful! ⭐

Thank you for considering contributing to this project! Contributions are highly appreciated and welcomed. To ensure smooth collaboration, please refer to our [Contribution Guidelines](./CONTRIBUTING.md).

---

## ✨ Maintainers

TODO: Add maintainer information

- [Maintainer Name](https://github.com/username)
- [Maintainer Name](https://github.com/username)

---

## 📍 License

This project is licensed under the GNU General Public License v3.0.
See the [LICENSE](LICENSE) file for details.

---

## 💪 Thanks To All Contributors

Thanks a lot for spending your time helping TODO grow. Keep rocking 🥂

[![Contributors](https://contrib.rocks/image?repo=AOSSIE-Org/TODO)](https://github.com/AOSSIE-Org/TODO/graphs/contributors)

© 2025 AOSSIE
* **User**: `!link <github_username>` in Discord.
* *Prerequisite*: User must add their Discord profile link (`https://discord.com/users/<ID>`) to their GitHub Profile -> Social Accounts.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Docs list !link, but the bot registers /link.

The README usage doesn’t match the implemented slash command, and “Rename config.yaml” is ambiguous since it’s gitignored. Please align the docs with the current command and clarify config creation.

📝 Suggested edit
-2.  **Configuration**:
-    *   Rename `config.yaml` and fill in your Token, Guild ID, and Org Name.
+2.  **Configuration**:
+    *   Create `config.yaml` (gitignored) and fill in your Token, Guild ID, and Org Name.
@@
-*   **User**: `!link <github_username>` in Discord.
+*   **User**: `/link <github_username>` in Discord.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
2. **Configuration**:
* Rename `config.yaml` and fill in your Token, Guild ID, and Org Name.
* **GitHub Token**: Needs `read:org`, `read:user`, `repo` scopes.
<!-- Organization/Project Social Handles -->
<p align="center">
<!-- Telegram -->
<a href="https://t.me/StabilityNexus">
<img src="https://img.shields.io/badge/Telegram-black?style=flat&logo=telegram&logoColor=white&logoSize=auto&color=24A1DE" alt="Telegram Badge"/></a>
&nbsp;&nbsp;
<!-- X (formerly Twitter) -->
<a href="https://x.com/aossie_org">
<img src="https://img.shields.io/twitter/follow/aossie_org" alt="X (formerly Twitter) Badge"/></a>
&nbsp;&nbsp;
<!-- Discord -->
<a href="https://discord.gg/hjUhu33uAn">
<img src="https://img.shields.io/discord/1022871757289422898?style=flat&logo=discord&logoColor=white&logoSize=auto&label=Discord&labelColor=5865F2&color=57F287" alt="Discord Badge"/></a>
&nbsp;&nbsp;
<!-- Medium -->
<a href="https://news.stability.nexus/">
<img src="https://img.shields.io/badge/Medium-black?style=flat&logo=medium&logoColor=black&logoSize=auto&color=white" alt="Medium Badge"></a>
&nbsp;&nbsp;
<!-- LinkedIn -->
<a href="https://www.linkedin.com/company/aossie/">
<img src="https://img.shields.io/badge/LinkedIn-black?style=flat&logo=LinkedIn&logoColor=white&logoSize=auto&color=0A66C2" alt="LinkedIn Badge"></a>
&nbsp;&nbsp;
<!-- Youtube -->
<a href="https://www.youtube.com/@StabilityNexus">
<img src="https://img.shields.io/youtube/channel/subscribers/UCZOG4YhFQdlGaLugr_e5BKw?style=flat&logo=youtube&logoColor=white&logoSize=auto&labelColor=FF0000&color=FF0000" alt="Youtube Badge"></a>
</p>
3. **Run**:
```bash
python src/main.py
```
---
## Usage
<div align="center">
<h1>TODO: Project Name</h1>
</div>
[TODO](https://TODO.stability.nexus/) is a ... TODO: Project Description.
---
## 🚀 Features
TODO: List your main features here:
- **Feature 1**: Description
- **Feature 2**: Description
- **Feature 3**: Description
- **Feature 4**: Description
---
## 💻 Tech Stack
TODO: Update based on your project
### Frontend
- React / Next.js / Flutter / React Native
- TypeScript
- TailwindCSS
### Backend
- Flask / FastAPI / Node.js / Supabase
- Database: PostgreSQL / SQLite / MongoDB
### AI/ML (if applicable)
- LangChain / LangGraph / LlamaIndex
- Google Gemini / OpenAI / Anthropic Claude
- Vector Database: Weaviate / Pinecone / Chroma
- RAG / Prompt Engineering / Agent Frameworks
### Blockchain (if applicable)
- Solidity / solana / cardano / ergo Smart Contracts
- Hardhat / Truffle / foundry
- Web3.js / Ethers.js / Wagmi
- OpenZeppelin / alchemy / Infura
---
## ✅ Project Checklist
TODO: Complete applicable items based on your project type
- [ ] **The protocol** (if applicable):
- [ ] has been described and formally specified in a paper.
- [ ] has had its main properties mathematically proven.
- [ ] has been formally verified.
- [ ] **The smart contracts** (if applicable):
- [ ] were thoroughly reviewed by at least two knights of The Stable Order.
- [ ] were deployed to: [Add deployment details]
- [ ] **The mobile app** (if applicable):
- [ ] has an _About_ page containing the Stability Nexus's logo and pointing to the social media accounts of the Stability Nexus.
- [ ] is available for download as a release in this repo.
- [ ] is available in the relevant app stores.
- [ ] **The AI/ML components** (if applicable):
- [ ] LLM/model selection and configuration are documented.
- [ ] Prompts and system instructions are version-controlled.
- [ ] Content safety and moderation mechanisms are implemented.
- [ ] API keys and rate limits are properly managed.
---
## 🔗 Repository Links
TODO: Update with your repository structure
1. [Main Repository](https://github.com/AOSSIE-Org/TODO)
2. [Frontend](https://github.com/AOSSIE-Org/TODO/tree/main/frontend) (if separate)
3. [Backend](https://github.com/AOSSIE-Org/TODO/tree/main/backend) (if separate)
---
## 🏗️ Architecture Diagram
TODO: Add your system architecture diagram here
```
[Architecture Diagram Placeholder]
```
You can create architecture diagrams using:
- [Draw.io](https://draw.io)
- [Excalidraw](https://excalidraw.com)
- [Lucidchart](https://lucidchart.com)
- [Mermaid](https://mermaid.js.org) (for code-based diagrams)
Example structure to include:
- Frontend components
- Backend services
- Database architecture
- External APIs/services
- Data flow between components
---
## 🔄 User Flow
TODO: Add user flow diagrams showing how users interact with your application
```
[User Flow Diagram Placeholder]
```
### Key User Journeys
TODO: Document main user flows:
1. **User Journey 1**: Description
- Step 1
- Step 2
- Step 3
2. **User Journey 2**: Description
- Step 1
- Step 2
- Step 3
3. **User Journey 3**: Description
- Step 1
- Step 2
- Step 3
---
## �🍀 Getting Started
### Prerequisites
TODO: List what developers need installed
- Node.js 18+ / Python 3.9+ / Flutter SDK
- npm / yarn / pnpm
- [Any specific tools or accounts needed]
### Installation
TODO: Provide detailed setup instructions
#### 1. Clone the Repository
```bash
git clone https://github.com/AOSSIE-Org/TODO.git
cd TODO
```
#### 2. Install Dependencies
```bash
npm install
# or
yarn install
# or
pnpm install
```
#### 3. Configure Environment Variables(.env.example)
Create a `.env` file in the root directory:
```env
# Add your environment variables here
API_KEY=your_api_key
DATABASE_URL=your_database_url
```
#### 4. Run the Development Server
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
#### 5. Open your Browser
Navigate to [http://localhost:3000](http://localhost:3000) to see the application.
For detailed setup instructions, please refer to our [Installation Guide](./docs/INSTALL_GUIDE.md) (if you have one).
---
## 📱 App Screenshots
TODO: Add screenshots showcasing your application
| | | |
|---|---|---|
| Screenshot 1 | Screenshot 2 | Screenshot 3 |
---
## 🙌 Contributing
⭐ Don't forget to star this repository if you find it useful! ⭐
Thank you for considering contributing to this project! Contributions are highly appreciated and welcomed. To ensure smooth collaboration, please refer to our [Contribution Guidelines](./CONTRIBUTING.md).
---
## ✨ Maintainers
TODO: Add maintainer information
- [Maintainer Name](https://github.com/username)
- [Maintainer Name](https://github.com/username)
---
## 📍 License
This project is licensed under the GNU General Public License v3.0.
See the [LICENSE](LICENSE) file for details.
---
## 💪 Thanks To All Contributors
Thanks a lot for spending your time helping TODO grow. Keep rocking 🥂
[![Contributors](https://contrib.rocks/image?repo=AOSSIE-Org/TODO)](https://github.com/AOSSIE-Org/TODO/graphs/contributors)
© 2025 AOSSIE
* **User**: `!link <github_username>` in Discord.
* *Prerequisite*: User must add their Discord profile link (`https://discord.com/users/<ID>`) to their GitHub Profile -> Social Accounts.
2. **Configuration**:
* Create `config.yaml` (gitignored) and fill in your Token, Guild ID, and Org Name.
* **GitHub Token**: Needs `read:org`, `read:user`, `repo` scopes.
3. **Run**:
🧰 Tools
🪛 LanguageTool

[uncategorized] ~30-~30: The official name of this software platform is spelled with a capital “H”.
Context: ...ain.py ``` ## Usage * User: !link <github_username> in Discord. * *Prerequ...

(GITHUB)

🪛 markdownlint-cli2 (0.20.0)

[warning] 31-31: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🤖 Prompt for AI Agents
In `@README.md` around lines 19 - 31, Update README.md so the usage matches the
implemented slash command: replace the `!link <github_username>` example with
the registered `/link` slash command syntax and any subcommand or option names
used by the bot. Clarify the ambiguous config creation step: instruct users to
copy the provided example file (e.g., config.example.yaml or template), rename
to config.yaml, and fill in Token, Guild ID, and Org Name (and list required
GitHub token scopes: read:org, read:user, repo). Ensure the README explicitly
states that config.yaml is gitignored and must be created from the example
template.

Comment on lines +77 to +86
# Check if user is linked first
db_user = get_user_by_discord(user.id)
if not db_user:
await interaction.response.send_message(f"❌ {user.mention} is not linked! They must use `/link` first.", ephemeral=True)
return

if add_maintainer(user.id, repo_url):
await interaction.response.send_message(f"✅ Added {user.mention} as maintainer for {repo_url}")
else:
await interaction.response.send_message(f"❌ Failed. Check if they are already a maintainer or if the repo is valid.", ephemeral=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing repo linkage check and minor issues.

  1. No validation that repo is linked: add_maintainer succeeds even if the repo isn't linked to any channel, creating orphaned maintainer records.
  2. Line 80: Extra indentation (should be 8 spaces, has 9).
  3. Line 86: Remove extraneous f prefix (no placeholders).
🛠️ Proposed fix
         # Check if user is linked first
         db_user = get_user_by_discord(user.id)
         if not db_user:
-             await interaction.response.send_message(f"❌ {user.mention} is not linked! They must use `/link` first.", ephemeral=True)
-             return
+            await interaction.response.send_message(f"❌ {user.mention} is not linked! They must use `/link` first.", ephemeral=True)
+            return
 
         if add_maintainer(user.id, repo_url):
             await interaction.response.send_message(f"✅ Added {user.mention} as maintainer for {repo_url}")
         else:
-            await interaction.response.send_message(f"❌ Failed. Check if they are already a maintainer or if the repo is valid.", ephemeral=True)
+            await interaction.response.send_message("❌ Failed. Check if they are already a maintainer or if the repo is valid.", ephemeral=True)

Consider adding a check that the repo is linked (e.g., query repos table by repo_url) before inserting into maintainers.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check if user is linked first
db_user = get_user_by_discord(user.id)
if not db_user:
await interaction.response.send_message(f"❌ {user.mention} is not linked! They must use `/link` first.", ephemeral=True)
return
if add_maintainer(user.id, repo_url):
await interaction.response.send_message(f"✅ Added {user.mention} as maintainer for {repo_url}")
else:
await interaction.response.send_message(f"❌ Failed. Check if they are already a maintainer or if the repo is valid.", ephemeral=True)
# Check if user is linked first
db_user = get_user_by_discord(user.id)
if not db_user:
await interaction.response.send_message(f"❌ {user.mention} is not linked! They must use `/link` first.", ephemeral=True)
return
if add_maintainer(user.id, repo_url):
await interaction.response.send_message(f"✅ Added {user.mention} as maintainer for {repo_url}")
else:
await interaction.response.send_message("❌ Failed. Check if they are already a maintainer or if the repo is valid.", ephemeral=True)
🧰 Tools
🪛 Ruff (0.14.14)

[error] 86-86: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
In `@src/cogs/admin.py` around lines 77 - 86, Add a repo linkage check and fix two
minor issues: before calling add_maintainer(user.id, repo_url) query the repos
table (e.g., lookup by repo_url via the existing repo access function or a new
get_repo_by_url) and if no repo record exists respond with an ephemeral message
telling the user the repo isn't linked; correct the indentation of the db_user
not-found block so it uses 8 spaces to match surrounding code (the line with
await interaction.response.send_message) and remove the extraneous f prefix on
the final await interaction.response.send_message that has no placeholders so it
is a plain string.

Comment on lines +1 to +6
import discord
from discord.ext import commands, tasks
import yaml
import os
import random
import logging
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file to understand its structure
head -50 src/cogs/events.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 1725


🏁 Script executed:

# Let's see more context around line 43-44 and verify the blocking call
sed -n '40,50p' src/cogs/events.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 474


🏁 Script executed:

# Let's check the github_client to confirm get_repo_events is synchronous
cat -n src/github_client.py | head -50

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 1887


🏁 Script executed:

# Let's find the get_repo_events method definition
rg -A 20 "def get_repo_events" src/github_client.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 872


🏁 Script executed:

# Let's also check if asyncio is already imported in events.py
grep -n "import asyncio" src/cogs/events.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 61


Wrap synchronous HTTP call in asyncio.to_thread to avoid blocking the event loop.

get_repo_events uses requests.get() (synchronous I/O), but it's called directly inside the async sync_events task. This blocks the event loop and can stall all bot events. Move the call to a thread pool.

Proposed fix
-import random
-import logging
+import random
+import logging
+import asyncio

At line 43:

-            events, new_etag = self.gh_client.get_repo_events(owner, name, etag)
+            events, new_etag = await asyncio.to_thread(
+                self.gh_client.get_repo_events, owner, name, etag
+            )
🤖 Prompt for AI Agents
In `@src/cogs/events.py` around lines 1 - 6, The synchronous requests.get call in
get_repo_events is being invoked directly from the async task sync_events and
can block the event loop; change the call so the HTTP request runs in a
threadpool (e.g., wrap requests.get in asyncio.to_thread or
loop.run_in_executor) inside get_repo_events or when calling it from
sync_events, await the threaded execution and propagate the result, ensuring
get_repo_events and sync_events remain async-compatible and do not perform
blocking I/O on the main loop.

Comment on lines +45 to +60
if not events:
continue

# Process oldest first (reverse of API response) to maintain narrative flow
for event in reversed(events):
if is_event_processed(event['id']):
continue

try:
await self.process_event(channel, event, repo_url)
mark_event_processed(event['id'])
except Exception as e:
print(f"Error processing event {event['id']}: {e}")

if new_etag:
update_repo_etag(repo_id, new_etag)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t advance the ETag when event processing fails.

If a processing error occurs, updating the ETag can permanently skip that event. Also, the current early continue prevents storing ETags for empty responses, causing redundant polling. Track failures and only update on success (including empty event lists).

🛡️ Proposed fix
-            if not events:
-                continue
-            
-            # Process oldest first (reverse of API response) to maintain narrative flow
-            for event in reversed(events):
-                if is_event_processed(event['id']):
-                    continue
-                
-                try:
-                    await self.process_event(channel, event, repo_url)
-                    mark_event_processed(event['id'])
-                except Exception as e:
-                    print(f"Error processing event {event['id']}: {e}")
-            
-            if new_etag:
-                update_repo_etag(repo_id, new_etag)
+            had_error = False
+
+            # Process oldest first (reverse of API response) to maintain narrative flow
+            for event in reversed(events):
+                if is_event_processed(event['id']):
+                    continue
+                try:
+                    await self.process_event(channel, event, repo_url)
+                    mark_event_processed(event['id'])
+                except Exception:
+                    logging.exception("Error processing event %s", event.get("id"))
+                    had_error = True
+
+            if new_etag and not had_error:
+                update_repo_etag(repo_id, new_etag)
🧰 Tools
🪛 Ruff (0.14.14)

[warning] 56-56: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@src/cogs/events.py` around lines 45 - 60, The current loop updates
update_repo_etag(repo_id, new_etag) even when any event processing fails and
also skips storing the ETag when events is empty; change the logic so that you
track success across the reversed(events) loop (e.g., a boolean like
all_processed=True), only call mark_event_processed(event['id']) after a
successful await self.process_event(...) and set all_processed=False if an
exception occurs (log the error), and then call update_repo_etag(repo_id,
new_etag) only when new_etag is set and all_processed is True OR when events is
empty (so empty responses still update the ETag). Ensure
is_event_processed(event['id']) remains to skip already-processed events.

Comment on lines +1 to +8
import discord
from discord import app_commands
from discord.ext import commands
import yaml
import os
import re
from database import add_user, get_user_by_discord
from github_client import GitHubClient
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the verification.py file to see the verify_identity calls
wc -l src/cogs/verification.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 106


🏁 Script executed:

# Read the verification.py file to identify verify_identity calls
cat -n src/cogs/verification.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 2738


🏁 Script executed:

# Find the github_client module to examine verify_identity implementation
fd github_client

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 145


🏁 Script executed:

# Check the implementation of verify_identity
cat -n src/github_client.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 5336


Offload synchronous HTTP to avoid blocking the event loop in async command handler.

verify_identity at line 37 uses requests.post() (synchronous HTTP) directly in the async command handler, which blocks the event loop. Use asyncio.to_thread() to offload the blocking call to a worker thread.

🛡️ Proposed fix
+import asyncio
 import discord
 from discord import app_commands
 from discord.ext import commands
 import yaml
 import os
 import re
 from database import add_user, get_user_by_discord
 from github_client import GitHubClient
@@
-        is_verified = self.gh_client.verify_identity(github_username, interaction.user.id)
+        is_verified = await asyncio.to_thread(
+            self.gh_client.verify_identity,
+            github_username,
+            interaction.user.id,
+        )
🤖 Prompt for AI Agents
In `@src/cogs/verification.py` around lines 1 - 8, The async command handler
verify_identity currently calls requests.post synchronously (requests.post),
blocking the event loop; update verify_identity to offload that blocking HTTP
call by wrapping the requests.post invocation in asyncio.to_thread (or otherwise
run it in a worker thread) and await the result, so the handler remains
non-blocking; ensure you import asyncio at top and replace direct requests.post
usage in the verify_identity function with an awaited asyncio.to_thread call
that passes the same arguments and handles the returned response exactly as
before.

Comment on lines +6 to +9
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "database.py" -type f | head -20

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 96


🏁 Script executed:

cat -n src/database.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 8108


🏁 Script executed:

rg "foreign_keys|PRAGMA|init_db|maintainers|activity_log" src/database.py -A 2 -B 2

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 1588


Enable SQLite foreign key enforcement in get_connection().

Foreign key constraints are defined on the maintainers and activity_log tables but are disabled by default in SQLite. Without enforcement, operations like add_maintainer() and log_activity() can insert invalid discord_ids, creating orphaned records.

Proposed fix
 def get_connection():
     conn = sqlite3.connect(DB_PATH)
+    conn.execute("PRAGMA foreign_keys = ON")
     conn.row_factory = sqlite3.Row
     return conn
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def get_connection():
conn = sqlite3.connect(DB_PATH)
conn.execute("PRAGMA foreign_keys = ON")
conn.row_factory = sqlite3.Row
return conn
🤖 Prompt for AI Agents
In `@src/database.py` around lines 6 - 9, The get_connection() function currently
opens SQLite connections without enabling foreign key enforcement; update
get_connection() to execute the PRAGMA foreign_keys=ON (e.g., run a statement on
the returned sqlite3.Connection) immediately after sqlite3.connect(DB_PATH) and
before returning so that constraints on the maintainers and activity_log tables
are enforced for add_maintainer(), log_activity(), and other DB operations.

Comment on lines +16 to +22
c.execute('''
CREATE TABLE IF NOT EXISTS users (
discord_id INTEGER PRIMARY KEY,
github_username TEXT UNIQUE,
score INTEGER DEFAULT 0,
last_synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and check the relevant lines
if [ -f "src/database.py" ]; then
  echo "=== File exists, checking lines 16-22 ==="
  sed -n '1,30p' src/database.py | cat -n
else
  echo "File src/database.py not found, searching for database files..."
  find . -name "*.py" -type f | head -20
fi

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 1099


🏁 Script executed:

# Look for any GitHub username handling or migrations
rg "github_username|COLLATE|migration" --type py -A 2 -B 2

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 4781


🏁 Script executed:

# Check if there's any documentation about GitHub usernames in the repo
rg -i "github" --type md README.md 2>/dev/null | head -20

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 551


🏁 Script executed:

# Check for migration files or version handling
find . -type f \( -name "*migrat*" -o -name "*schema*" -o -name "*version*" \) | grep -E "\.(py|sql|txt)$"

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 61


🏁 Script executed:

# Check all usages of github_username to see full scope
rg "github_username" --type py -B 1 -A 1

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 3591


🏁 Script executed:

# Verify SQLite COLLATE NOCASE is the correct approach
# Check SQLite version requirement
rg "sqlite3|version" src/database.py --type py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 339


Fix GitHub username uniqueness to be case-insensitive at the database constraint level.

The current TEXT UNIQUE constraint is case-sensitive in SQLite, allowing both User and user to exist as separate entries in the database. This breaks identity guarantees since GitHub treats usernames as case-insensitive. The existing COLLATE NOCASE in get_discord_from_github() is a partial workaround for reads, but the add_user() insert can still create duplicates.

Add COLLATE NOCASE to the column constraint:

Proposed fix
-            github_username TEXT UNIQUE,
+            github_username TEXT COLLATE NOCASE UNIQUE,

Note: For existing databases, a migration will be needed to rebuild the constraint. For fresh deployments, this schema change is sufficient.

🤖 Prompt for AI Agents
In `@src/database.py` around lines 16 - 22, The users table schema needs the
github_username UNIQUE constraint to be case-insensitive: change the CREATE
TABLE users column definition from "github_username TEXT UNIQUE" to
"github_username TEXT COLLATE NOCASE UNIQUE" so inserts via add_user() will be
prevented from creating case-variant duplicates (reads in
get_discord_from_github() already use COLLATE NOCASE); for existing databases
include a migration to rebuild the users table/constraint.

Comment on lines +35 to +120
response = requests.post(
self.graphql_url,
json={"query": query, "variables": variables},
headers=self.headers
)
response.raise_for_status()
data = response.json()

if "errors" in data:
logging.error(f"GraphQL Error: {data['errors']}")
return False

user_data = data.get("data", {}).get("user")
if not user_data:
return False

socials = user_data.get("socialAccounts", {}).get("nodes", [])
target_url = f"https://discord.com/users/{discord_id}"

for account in socials:
# Some users might just put the ID, but the requirement is the full URL or check provider
# The prompt asks: "passes ONLY if one of the links is exactly https://discord.com/users/<DISCORD_USER_ID>"
if account["url"] == target_url:
return True

return False

except Exception as e:
logging.error(f"Verification failed: {e}")
return False

def get_user_activity(self, github_username, since_date=None):
"""
Fetches recent activity for a user in the organization.
For simplicity, we query issues and PRs created by the user in the org repositories.
Using REST API search for broader reach or Events API.

Using Search API to find Issues and PRs within the Org.
"""
# Search for Issues and PRs created by author in org
query = f"org:{self.org_name} author:{github_username} created:>{since_date if since_date else '2020-01-01'}"
url = f"{self.rest_url}/search/issues?q={query}"

try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json().get("items", [])
except Exception as e:
logging.error(f"Failed to fetch activity for {github_username}: {e}")
return []

def get_repo_events(self, owner, name, etag=None):
"""
Fetches events for a repository.
Uses ETag to check for updates efficiently.
"""
url = f"{self.rest_url}/repos/{owner}/{name}/events"
headers = self.headers.copy()
if etag:
headers['If-None-Match'] = etag

try:
response = requests.get(url, headers=headers)
if response.status_code == 304:
return [], etag # No new events

response.raise_for_status()
new_etag = response.headers.get('ETag')
return response.json(), new_etag
except Exception as e:
logging.error(f"Failed to fetch events for {owner}/{name}: {e}")
return [], etag

def get_open_issues_with_label(self, label):
"""
Finds open issues in the org with a specific label.
"""
query = f"org:{self.org_name} is:issue is:open label:\"{label}\""
url = f"{self.rest_url}/search/issues?q={query}"

try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json().get("items", [])
except Exception as e:
logging.error(f"Failed to fetch issues with label {label}: {e}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l src/github_client.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 103


🏁 Script executed:

head -120 src/github_client.py | tail -n +35

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 3528


🏁 Script executed:

head -40 src/github_client.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 1243


🏁 Script executed:

tail -1 src/github_client.py

Repository: AOSSIE-Org/Gitcord-GithubDiscordBot

Length of output: 100


🌐 Web query:

requests library RequestException ValueError timeout best practices Python

💡 Result:

Best practices for requests timeouts + exception handling

  1. Always set a timeout (Requests does not time out by default; calls can hang). Use either a single float or a 2-tuple (connect, read). The single value applies to both connect and read. Also note: connect/read timeouts are not wall-clock limits, and connect can effectively take longer if multiple IPs are tried. [1]
  2. Handle exceptions at the right granularity
    • Use requests.exceptions.Timeout (base for timeout-related errors) when you just care “it timed out”. [2]
    • Use specific subclasses (e.g., ConnectTimeout, ReadTimeout) if you need different behavior per case. (They’re part of the Requests exception hierarchy.) [2][3]
    • Catch requests.exceptions.RequestException last as the “anything Requests raised” umbrella. [2][3]

Why you might see ValueError related to timeouts

Timeout validation ultimately comes from urllib3: a timeout must be an int/float/None, not boolean, and must be > 0; otherwise urllib3 raises ValueError with messages like “Timeout value connect was X, but it must be an int, float or None.” [4]
Common causes:

  • Passing the wrong shape/type, e.g. timeout=((3, 10), 30) or timeout=True
  • Passing 0 or negative values, e.g. timeout=0 or timeout=(-1, 5) [4]

A solid pattern (minimal + production-friendly)

import requests
from requests.exceptions import ConnectTimeout, ReadTimeout, Timeout, HTTPError, RequestException

TIMEOUT = (3.05, 30)  # (connect, read)

try:
    r = requests.get("https://example.com", timeout=TIMEOUT)
    r.raise_for_status()
except (ConnectTimeout, ReadTimeout, Timeout):
    # retry / fallback / mark as transient failure
    raise
except HTTPError:
    # non-2xx response (after raise_for_status)
    raise
except RequestException:
    # DNS errors, refused connections, TLS issues, etc.
    raise

If you need a hard “wall clock” cutoff, Requests’ connect/read timeouts won’t guarantee that by themselves (they measure socket phases / gaps between bytes), so you’ll need an external enforcement mechanism (e.g., a watchdog) rather than relying on timeout= alone. [1][4]

Sources:
[1] Requests docs — Timeouts (Advanced Usage)
[2] Requests docs — Quickstart (Timeouts + “all exceptions inherit from RequestException”)
[3] Requests source — requests.exceptions.RequestException base class
[4] urllib3 source — timeout validation rules (_validate_timeout)


Add timeouts and narrow exception handling for all HTTP calls.

Network calls without timeouts can stall the bot indefinitely. All four HTTP calls in this file lack timeout parameters. Additionally, using broad except Exception patterns loses diagnostic information. Use timeout=10 on all requests calls and replace broad exception handlers with (requests.RequestException, ValueError), using logging.exception() to capture full tracebacks.

🛡️ Proposed fix
             response = requests.post(
                 self.graphql_url,
                 json={"query": query, "variables": variables},
-                headers=self.headers
+                headers=self.headers,
+                timeout=10,
             )
@@
-        except Exception as e:
-            logging.error(f"Verification failed: {e}")
+        except (requests.RequestException, ValueError) as e:
+            logging.exception("Verification failed: %s", e)
             return False
@@
-            response = requests.get(url, headers=self.headers)
+            response = requests.get(url, headers=self.headers, timeout=10)
             response.raise_for_status()
             return response.json().get("items", [])
-        except Exception as e:
-            logging.error(f"Failed to fetch activity for {github_username}: {e}")
+        except (requests.RequestException, ValueError):
+            logging.exception("Failed to fetch activity for %s", github_username)
             return []
@@
-            response = requests.get(url, headers=headers)
+            response = requests.get(url, headers=headers, timeout=10)
             if response.status_code == 304:
                 return [], etag # No new events
@@
-        except Exception as e:
-            logging.error(f"Failed to fetch events for {owner}/{name}: {e}")
+        except (requests.RequestException, ValueError):
+            logging.exception("Failed to fetch events for %s/%s", owner, name)
             return [], etag
@@
-            response = requests.get(url, headers=self.headers)
+            response = requests.get(url, headers=self.headers, timeout=10)
             response.raise_for_status()
             return response.json().get("items", [])
-        except Exception as e:
-            logging.error(f"Failed to fetch issues with label {label}: {e}")
+        except (requests.RequestException, ValueError):
+            logging.exception("Failed to fetch issues with label %s", label)
             return []
🧰 Tools
🪛 Ruff (0.14.14)

[error] 35-35: Probable use of requests call without timeout

(S113)


[warning] 60-60: Consider moving this statement to an else block

(TRY300)


[warning] 62-62: Do not catch blind exception: Exception

(BLE001)


[warning] 63-63: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[error] 79-79: Probable use of requests call without timeout

(S113)


[warning] 82-82: Do not catch blind exception: Exception

(BLE001)


[warning] 83-83: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[error] 97-97: Probable use of requests call without timeout

(S113)


[warning] 104-104: Do not catch blind exception: Exception

(BLE001)


[warning] 105-105: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[error] 116-116: Probable use of requests call without timeout

(S113)


[warning] 119-119: Do not catch blind exception: Exception

(BLE001)


[warning] 120-120: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In `@src/github_client.py` around lines 35 - 120, All HTTP calls in this module
(the Discord verification loop and the methods get_user_activity,
get_repo_events, and get_open_issues_with_label) must include a timeout and
tighten exception handling: add timeout=10 to every requests.get/post call,
replace broad "except Exception" blocks with "except (requests.RequestException,
ValueError)" and use logging.exception(...) inside those handlers to capture the
traceback; also keep existing return behavior (False or [] as appropriate) after
logging. Use the method names get_user_activity, get_repo_events,
get_open_issues_with_label and the verification function around the
socialAccounts loop to locate and update each call.

Comment on lines +27 to +41
@bot.event
async def on_ready():
print(f'Logged in as {bot.user} (ID: {bot.user.id})')
print('------')
# Load Cogs
await bot.load_extension('cogs.verification')
await bot.load_extension('cogs.events')
await bot.load_extension('cogs.admin')

# Sync Slash Commands
try:
synced = await bot.tree.sync()
print(f"Synced {len(synced)} command(s)")
except Exception as e:
print(f"Failed to sync commands: {e}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Cog loading in on_ready will fail on reconnect.

on_ready fires each time the bot (re)connects. Calling load_extension again raises ExtensionAlreadyLoaded. Either load cogs once before bot.run() or guard against reloads.

🐛 Recommended fix: Load cogs before bot.run()

Move cog loading to an async setup hook or load them synchronously before bot.run():

+async def load_cogs():
+    await bot.load_extension('cogs.verification')
+    await bot.load_extension('cogs.events')
+    await bot.load_extension('cogs.admin')
+
 `@bot.event`
 async def on_ready():
     print(f'Logged in as {bot.user} (ID: {bot.user.id})')
     print('------')
-    # Load Cogs
-    await bot.load_extension('cogs.verification')
-    await bot.load_extension('cogs.events')
-    await bot.load_extension('cogs.admin')
-    
     # Sync Slash Commands
     try:
         synced = await bot.tree.sync()
         print(f"Synced {len(synced)} command(s)")
     except Exception as e:
         print(f"Failed to sync commands: {e}")

 if __name__ == '__main__':
     token = config['discord']['token']
     if token == "YOUR_DISCORD_BOT_TOKEN":
         print("❌ Error: You must set the 'discord.token' in config.yaml")
         sys.exit(1)
         
     try:
+        import asyncio
+        asyncio.get_event_loop().run_until_complete(load_cogs())
         bot.run(token)

Or use setup_hook:

`@bot.event`
async def setup_hook():
    await bot.load_extension('cogs.verification')
    await bot.load_extension('cogs.events')
    await bot.load_extension('cogs.admin')
🧰 Tools
🪛 Ruff (0.14.14)

[warning] 40-40: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@src/main.py` around lines 27 - 41, The on_ready handler currently calls
bot.load_extension('cogs.verification'/'cogs.events'/'cogs.admin'), which will
raise ExtensionAlreadyLoaded on reconnect; move cog loading out of on_ready by
either loading them once before bot.run() or implementing an async setup hook
(define bot.setup_hook and call await bot.load_extension(...) for each cog
there), and keep the bot.tree.sync call in on_ready (or move it into setup_hook)
so load_extension is not invoked on every reconnect; alternatively, if you must
keep it in on_ready, wrap each load_extension call in a try/except catching
discord.ext.commands.errors.ExtensionAlreadyLoaded to skip already-loaded cogs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant