-
Notifications
You must be signed in to change notification settings - Fork 9
Added basic bot #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughA 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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, andrequestscan introduce breaking changes; please pin or add a constraints/lock file, and ensure the selecteddiscord.pyversion supportsdiscord.app_commands.src/github_client.py (1)
75-77: Useparamsfor search queries to avoid manual URL encoding.Building the URL with
params={"q": query}avoids edge cases with escaping and is more idiomatic forrequests.♻️ 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 bareexcepton config load and fail fast with a clear error.The fallback config lacks required
githubkeys, so the bot will crash later with aKeyError. 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/invalidconfig.yamlwith 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 esrc/cogs/admin.py (3)
11-12: Dead code with typo — remove or fix and use consistently.The class-level
GITHUB_REPO_REGEXcontains 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 referenceself.GITHUB_REPO_REGEXthroughout 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.yamlexists but contains invalid YAML syntax,yaml.safe_loadraisesyaml.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
!sconversion flag instead of explicitstr().✨ 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}`"
| ## critical | ||
| config.yaml | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| ## 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.
| 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> | ||
| | ||
| <!-- 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> | ||
| | ||
| <!-- 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> | ||
| | ||
| <!-- 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> | ||
| | ||
| <!-- 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> | ||
| | ||
| <!-- 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 🥂 | ||
|
|
||
| [](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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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> | |
| | |
| <!-- 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> | |
| | |
| <!-- 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> | |
| | |
| <!-- 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> | |
| | |
| <!-- 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> | |
| | |
| <!-- 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 🥂 | |
| [](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.
| # 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing repo linkage check and minor issues.
- No validation that repo is linked:
add_maintainersucceeds even if the repo isn't linked to any channel, creating orphaned maintainer records. - Line 80: Extra indentation (should be 8 spaces, has 9).
- Line 86: Remove extraneous
fprefix (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.
| # 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.
| import discord | ||
| from discord.ext import commands, tasks | ||
| import yaml | ||
| import os | ||
| import random | ||
| import logging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file to understand its structure
head -50 src/cogs/events.pyRepository: 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.pyRepository: 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 -50Repository: 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.pyRepository: 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.pyRepository: 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 asyncioAt 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.
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the verification.py file to see the verify_identity calls
wc -l src/cogs/verification.pyRepository: 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.pyRepository: AOSSIE-Org/Gitcord-GithubDiscordBot
Length of output: 2738
🏁 Script executed:
# Find the github_client module to examine verify_identity implementation
fd github_clientRepository: AOSSIE-Org/Gitcord-GithubDiscordBot
Length of output: 145
🏁 Script executed:
# Check the implementation of verify_identity
cat -n src/github_client.pyRepository: 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.
| def get_connection(): | ||
| conn = sqlite3.connect(DB_PATH) | ||
| conn.row_factory = sqlite3.Row | ||
| return conn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "database.py" -type f | head -20Repository: AOSSIE-Org/Gitcord-GithubDiscordBot
Length of output: 96
🏁 Script executed:
cat -n src/database.pyRepository: 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 2Repository: 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.
| 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.
| 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 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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
fiRepository: 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 2Repository: 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 -20Repository: 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 1Repository: 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 pyRepository: 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.
| 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}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
wc -l src/github_client.pyRepository: AOSSIE-Org/Gitcord-GithubDiscordBot
Length of output: 103
🏁 Script executed:
head -120 src/github_client.py | tail -n +35Repository: AOSSIE-Org/Gitcord-GithubDiscordBot
Length of output: 3528
🏁 Script executed:
head -40 src/github_client.pyRepository: AOSSIE-Org/Gitcord-GithubDiscordBot
Length of output: 1243
🏁 Script executed:
tail -1 src/github_client.pyRepository: 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
- 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] - 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.RequestExceptionlast as the “anything Requests raised” umbrella. [2][3]
- Use
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)ortimeout=True - Passing 0 or negative values, e.g.
timeout=0ortimeout=(-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.
raiseIf 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.
| @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}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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
Notifies potential contributors of good-first-issue tasks tailored to their activity level.
Commands
-/link <github_username>
Video
My.Movie.mp4
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.