Repository navigation
feat: add rank command - #46
Conversation
Summary by BeetleThis PR introduces a complete rank card feature for the Discord bot, enabling users to view their position on the leaderboard with a visually appealing card. The implementation spans three new components: a utility function to calculate user rankings, a visual renderer to generate rank cards as PNG images, and a Discord command to display them. A bug fix ensures the leaderboard can return all entries when needed. 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 3 files changed, +335 additions, -1 deletion 🗺️ Walkthrough:graph TD
A["User runs $rank command"] --> B{"User mentioned?"}
B -->|No mention| C["Get command author's rank"]
B -->|Yes mention| D["Get mentioned user's rank"]
C --> E["Query getUserRank"]
D --> E
E --> F["Fetch full leaderboard"]
F --> G["Find user position"]
G --> H{"User found?"}
H -->|No| I["Reply: No AI usage recorded"]
H -->|Yes| J["Build UserRank object"]
J --> K["Load user avatar"]
K --> L["Render rank card"]
L --> M["Generate PNG buffer"]
M --> N["Send card as Discord attachment"]
I --> O["Command complete"]
N --> O
🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
| }; | ||
|
|
||
| function roundRectPath( | ||
| ctx: any, |
There was a problem hiding this comment.
The ctx parameter is typed as any, which removes TypeScript's type safety. This should be properly typed as CanvasRenderingContext2D to catch potential errors at compile time and improve IDE support.
Confidence: 5/5
Suggested Fix
| ctx: any, | |
| function roundRectPath( | |
| ctx: CanvasRenderingContext2D, | |
| x: number, | |
| y: number, | |
| w: number, | |
| h: number, | |
| r: number, | |
| ) { | |
Also apply this fix to the truncate function on line 84:
| ctx: any, | |
| function truncate(ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string { | |
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/visuals/rankCard.ts, replace the `any` type for the `ctx` parameter in the `roundRectPath` function (line 47) and `truncate` function (line 84) with the proper `CanvasRenderingContext2D` type to enable TypeScript type checking and improve IDE support.
| async function loadAvatar(url?: string | null) { | ||
| if (!url) return null; | ||
| try { | ||
| const pngUrl = url.replace(/\.(webp|gif)(\?|$)/i, ".png$2"); | ||
| const res = await fetch(pngUrl); | ||
| if (!res.ok) return null; | ||
| return await loadImage(Buffer.from(await res.arrayBuffer())); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
The loadAvatar function fetches images from user-provided URLs without validation. This creates potential security and performance risks:
- SSRF vulnerability: No URL origin validation allows fetching from internal/private networks
- Memory exhaustion: No size limits on downloaded images could cause OOM errors
- Timeout issues: No timeout configuration for slow/hanging requests
Consider adding URL validation, size limits, and request timeouts.
Confidence: 4/5
Suggested Fix
| async function loadAvatar(url?: string | null) { | |
| if (!url) return null; | |
| try { | |
| const pngUrl = url.replace(/\.(webp|gif)(\?|$)/i, ".png$2"); | |
| const res = await fetch(pngUrl); | |
| if (!res.ok) return null; | |
| return await loadImage(Buffer.from(await res.arrayBuffer())); | |
| } catch { | |
| return null; | |
| } | |
| } | |
| async function loadAvatar(url?: string | null) { | |
| if (!url) return null; | |
| try { | |
| // Validate URL is from Discord CDN only | |
| const urlObj = new URL(url); | |
| if (!urlObj.hostname.includes('discord')) { | |
| return null; | |
| } | |
| const pngUrl = url.replace(/\.(webp|gif)(\?|$)/i, ".png$2"); | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout | |
| const res = await fetch(pngUrl, { | |
| signal: controller.signal, | |
| headers: { 'User-Agent': 'DiscordBot' } | |
| }); | |
| clearTimeout(timeoutId); | |
| if (!res.ok) return null; | |
| // Check content length before downloading | |
| const contentLength = res.headers.get('content-length'); | |
| if (contentLength && parseInt(contentLength) > 5 * 1024 * 1024) { // 5MB limit | |
| return null; | |
| } | |
| return await loadImage(Buffer.from(await res.arrayBuffer())); | |
| } catch { | |
| return null; | |
| } | |
Add URL validation to ensure only Discord CDN URLs are fetched, implement a 5-second timeout to prevent hanging requests, and check content-length headers to prevent downloading oversized images.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/visuals/rankCard.ts, enhance the loadAvatar function (lines 72-82) to add security and performance protections: (1) validate that URLs are from Discord CDN only, (2) add a 5-second timeout using AbortController, (3) check content-length headers to enforce a 5MB size limit before downloading, and (4) return null for any validation failures to gracefully handle invalid avatars.
📍 This suggestion applies to lines 72-82
|
|
||
| try { | ||
| const hasMention = message.mentions?.users?.first(); | ||
| const hasArgs = args.length > 0; |
There was a problem hiding this comment.
The code accesses args.length without verifying that args is defined. If the args parameter is undefined or null (which could happen if CommandCallbackOpts doesn't guarantee it), this will throw a runtime error: "Cannot read property 'length' of undefined".
Confidence: 4/5
Suggested Fix
| const hasArgs = args.length > 0; | |
| const hasArgs = args && args.length > 0; | |
Add a null/undefined check before accessing the length property to prevent runtime errors.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
In src/commands/ai/rank.ts line 15, add a null/undefined check for the args parameter before accessing its length property: change `args.length > 0` to `args && args.length > 0` to prevent potential runtime errors if args is not defined.
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
|
doing some fixs |
Summary by BeetleThis PR improves type safety in the rank card visualization module by replacing loose 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 1 file changed, +12 additions, -3 deletions 🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
✅ You're good to merge this PR! No issues found. Great job! Settings⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
Summary by BeetleThis PR enhances the security and reliability of the 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 1 file changed, +17 additions, -1 deletion 🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
✅ You're good to merge this PR! No issues found. Great job! Settings⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
Summary by BeetleThis PR addresses a null safety bug in the rank command by adding a defensive null check for the 📁 File Changes Summary (Consolidated across all commits):
Total Changes: 1 file changed, +1 additions, -1 deletions 🎯 Key Changes:
📊 Impact Assessment:
⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
✅ You're good to merge this PR! No issues found. Great job! Settings⚙️ SettingsSeverity Threshold: 📖 User Guide
|
|
Note Linting checks passed successfully 🎉 All formatting and code quality checks are clean. You're good to merge 🚀 |
|
Thanks for contributing! |
:3 |
Summary
Adds a rank command that shows a user's position on the leaderboard with a visual card.
Changes
Usage
— shows your rank
@user — shows that user's rank
— alias for
Visuals