Skip to content

Refactor: discord.py 2.x migration, async DB, OAuth, and test suite#544

Merged
cheran-senthil merged 24 commits intomasterfrom
claude-refactor
Feb 16, 2026
Merged

Refactor: discord.py 2.x migration, async DB, OAuth, and test suite#544
cheran-senthil merged 24 commits intomasterfrom
claude-refactor

Conversation

@cheran-senthil
Copy link
Owner

@cheran-senthil cheran-senthil commented Feb 16, 2026

Summary

  • discord.py 2.x migration: Slash command support, button-based pagination/duel UI, auto-reply for legacy prefix commands
  • Async database layer: Modernized DB access with SQLite WAL mode and reduced fsync overhead
  • Codeforces OAuth: Replaced compile-error identification flow with OAuth-based handle verification
  • Architecture refactoring: Eliminated cf_common globals, split monolithic cache_system2.py into focused modules under tle/util/cache/
  • Test suite: Added 129 tests (unit, component, integration) covering API parsing, cache, DB, events, tasks, paginator, and cog integration
  • Operational improvements: Docker resource limits, log rotation, ANSI color support for tables, CJK fonts baked into Docker image
  • Cleanup: Removed dead CSES code, font downloader, handle pretty command, and Pillow dependency

Test plan

  • Verify bot starts and connects to Discord
  • Test slash commands and legacy prefix command auto-replies
  • Test handle identification via OAuth flow
  • Test duel accept/decline/withdraw buttons
  • Test pagination buttons on long outputs
  • Run pytest to validate test suite passes
  • Verify Docker build and resource limits

🤖 Generated with Claude Code

Mukundan314 and others added 24 commits February 14, 2026 21:45
Delete the deactivated CSES cog and its scraper utility — both were
unused dead code with a known bug (unclosed module-level aiohttp
session). Update README to remove CSES references.

Add ARCHITECTURE.md, AUDIT.md, ROADMAP.md, and TESTING_PLAN.md as
part of the effort to modernize and revive the project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The restart command relied on a shell script wrapper (exit code 42) that
no longer exists after the Docker migration. The kill command now uses
bot.close() + sys.exit() instead of os._exit() for proper cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add timeout=10 to subprocess communicate() in git_history() (SEC-03)
- Replace bare except with except Exception in logging cog (CQ-04)
- Remove inner while True loop in presence task; let task waiter
  control the 10-minute repeat interval instead (CQ-07)
- Change ALL_DIRS from generator expression to tuple so it survives
  multiple iterations (constants bug)
- Fix docstring typo "corouting asuch" -> "coroutine such" (CQ-06)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pin all dependencies to compatible version ranges
- Bump requires-python to >= 3.10 (code uses str | int syntax)
- Add python-dotenv and call load_dotenv() for .env support
- Update CI actions to checkout@v4 and setup-python@v5
- Pin ruff==0.9.7 in lint CI for reproducible builds
- Create .dockerignore to exclude .git, .env, data/, logs/, etc.
- Multi-stage Dockerfile: builder with build tools, runtime with
  only libraries, running as non-root botuser
- Remove stale E722 per-file-ignore for logging.py in ruff.toml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate entire database layer from synchronous sqlite3 to aiosqlite,
eliminating event loop blocking on every DB call. This is the single
highest-impact change for bot responsiveness.

Key changes:
- Replace sqlite3 with aiosqlite in both user_db_conn.py and cache_db_conn.py
- Two-step init pattern: __init__ stores path, async connect() opens connection
- All ~80 DB methods converted to async, all ~150 call sites updated to await
- Add asyncio.Event-based wait_for_initialize() to prevent cog on_ready race
  condition (workaround for discord.py 1.x; remove in Step 6d with setup_hook)
- Close both DB connections on bot shutdown via bot.close override
- Security: parameterized SQL queries replacing f-string interpolation (SEC-01)
- Security: allowlist validation for table/column names in _insert_one/_insert_many (SEC-02)
- Fix: cursor-level row_factory instead of mutating conn.row_factory (DB-04)
- Fix: namedtuple_factory raises ValueError on non-identifier columns (DB-03)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Migrate tle/constants.py from os.path to pathlib.Path
- Replace temp file I/O with io.BytesIO in graph_common.py, add plt.close()
- Split 850-line cache_system2.py into tle/util/cache/ package with focused modules
- Move user_db, cf_cache, event_sys onto bot instance via initialize(bot, nodb)
- Update ~175 call sites in cogs to use self.bot.* instead of cf_common.* globals
- Rename cache2 to cf_cache throughout for clarity
- Replace sequential API calls with asyncio.gather() in get_visited_contests()
- Delete unused cache_system2.py backward-compat shim

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Install fonts-noto-cjk as a system package in the Docker image instead
of downloading ~40MB of fonts from GitHub on first startup. This removes
startup latency, an external download dependency, and unnecessary code.

- Dockerfile: add fonts-noto-cjk package, remove FONTCONFIG_FILE env var
- tle/constants.py: point font paths to system location, remove ASSETS_DIR/FONTS_DIR
- tle/__main__.py: remove font_downloader import and call
- Delete tle/util/font_downloader.py and extra/fonts.conf
- Update ARCHITECTURE.md to reflect changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pretty command duplicated functionality already available via
;handle list. Removing it also eliminates the Pillow dependency
entirely (and its native libjpeg/zlib build deps from Docker).
Updates ARCHITECTURE.md to reflect recent audit changes: async
database layer (aiosqlite), modular cache package, service
attachment to bot instance, and multi-stage Docker build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 226 tests (unit + component) covering pure functions and async
database operations as a safety net before the discord.py 2.x migration.

- pytest/pytest-asyncio/pytest-cov/pytest-mock in pyproject.toml
- Unit tests for table, handledict, codeforces_api, codeforces_common,
  and rating_calculator
- Component tests for user_db and cache_db (in-memory aiosqlite)
- CI test workflow (Python 3.10/3.11/3.12) and pip-audit lint job
- Move fix_urls to codeforces_api.py (belongs with User type it
  operates on), breaking circular import chain architecturally
- Fix SQL queries using non-identifier column names (SELECT 1 → AS x,
  SELECT COUNT(*) → AS cnt) that broke namedtuple_factory from Step 4
- Update ROADMAP.md and TESTING_PLAN.md to reflect completion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate from discord.py 1.7.3 to 2.x, the largest breaking-API change
in the modernization roadmap. Key changes:

- Update discord.py dependency to >=2.3,<3 and remove aiohttp pin
- Create TLEBot subclass with setup_hook() and proper close() override
- Add intents.message_content for prefix command support in 2.x
- Rename avatar_url → display_avatar.url, Embed.Empty → None,
  guild.icon_url → guild.icon.url
- Add get_role()/has_role() helpers supporting both name and ID lookups
- Convert all 9 cog setup functions to async
- Remove wait_for_initialize pattern (replaced by setup_hook guarantees)
- Remove obsolete discriminator #0 stripping hack
- Update ARCHITECTURE.md, ROADMAP.md, and TESTING_PLAN.md

All 226 tests pass. No ruff regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace direct cf_common.user_db/cf_common.cf_cache access with
self.bot.user_db/self.bot.cf_cache in cog code. Move module-level
functions into their cog classes and convert @staticmethod helpers
to instance methods where they accessed globals:

- __main__: TLEBot.close() uses self attributes via getattr
- duel: move get_cf_user/complete_duel into Dueling cog as methods
- contests: move _get_ongoing_vc_participants into cog; convert
  _get_remind_role and _make_vc_rating_changes_embed to methods
- handles: convert _make_rankup_embeds to instance method
- cache/problemset: use self.cache_master.contest_cache, drop
  cf_common import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, and cog integration (Steps 7-10)

Brings total from 226 to 355 passing tests. Covers events.py (25 tests),
tasks.py (28 tests), paginator.py (18 tests), API response parsing with
fixture JSON files (18 tests), cache sub-system data management (20 tests),
and first cog integration tests for Codeforces cog (8 tests). Adds shared
fixtures (make_member, make_party, make_submission, event_system, mock_ctx)
and patch_cf_common helper to conftest.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert commands to hybrid_command/hybrid_group for dual prefix+slash support,
add TLEContext that makes prefix command responses reply to the invoking message,
and sync command tree on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the legacy Paginated class (emoji reactions + bot.wait_for) with
PaginatorView (discord.ui.Button) for modern UX and no manage_messages
permission requirement. Makes paginate() async and removes the bot parameter
from all 13 callers across 4 cog files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the blocking asyncio.sleep(300) in the challenge command with a
DuelChallengeView that provides interactive Accept, Decline, and Withdraw
buttons. Buttons are restricted so only the correct user can click each
one. Standalone text commands kept as fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The old ;handle identify required submitting a compilation error and
waiting 60 seconds. This replaces it with Codeforces OpenID Connect
(OAuth 2.0), giving users a one-click authorization link instead.

- Add tle/util/oauth.py: OAuthStateStore (5-min TTL, single-use),
  OAuthServer (aiohttp /callback), token exchange + HS256 JWT decode
- Refactor _set into _set_from_oauth (guild arg instead of ctx) so
  the OAuth callback can call it directly
- Rewrite identify: sends ephemeral link button (slash) or DM (prefix),
  revokes previous state on re-invocation, falls back gracefully when
  DMs are disabled
- Integrate OAuthServer lifecycle in TLEBot (setup_hook / close)
- Add OAuth constants, PyJWT dependency, docker port, env docs
- Add 17 unit tests for state store, auth URL, JWT decode, exchange
- Update ARCHITECTURE.md and ROADMAP.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive type annotations across 32 files covering all util
modules and cogs. Modernize Optional[X] to X | None (PEP 604), fix
invalid annotations like [str] to list[str], and add mypy config to
pyproject.toml. Update ROADMAP.md to reflect progress.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simplify startup banner to a single message, skip command/jump URL
when not available, and use ANSI code blocks for color-coded log
levels (yellow=warning, red=error, bold red=critical, green=info).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…odes

Extract ANSI escape constants into a shared tle/util/ansi module, colorize
handle list rows by Codeforces rank, and extend table.Data with per-cell
color rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…stion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add explicit package include for setuptools to resolve the
"Multiple top-level packages" build error, and fix all 23 ruff
violations (E501 line length, F401 unused import, I001 import sorting).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cheran-senthil cheran-senthil merged commit dfa452e into master Feb 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants