Skip to content

feat: add rank command - #46

Merged
calebephrem merged 8 commits into
open-devhub:mainfrom
goldstac:feat/rank
Sep 2, 2026
Merged

calebephrem merged 8 commits into
open-devhub:mainfrom
goldstac:feat/rank

Conversation

@goldstac

@goldstac goldstac commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a rank command that shows a user's position on the leaderboard with a visual card.

Changes

  • src/utils/stats.ts — Added getUserRank() function that returns a user's rank, tokens behind/ahead, streak, and total users
  • src/utils/stats.ts — Fixed getLeaderboard(0) to return all entries instead of an empty array
  • src/visuals/rankCard.ts — New canvas-based rank card with large rank number, avatar, name, tokens, streak, and gap info
  • src/commands/ai/rank.ts — New command with alias that supports or @user

Usage

— shows your rank
@user — shows that user's rank
— alias for

Visuals

@devhub-bot devhub-bot Bot added feat New feature fix Bug fix labels Sep 2, 2026
@beetle-ai

beetle-ai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary by Beetle

This 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):

File Status Changes Description
src/utils/stats.ts Modified +44/-1 Added UserRank interface and getUserRank() function to calculate a user's rank position, including rank number, total users, token gaps to adjacent ranks, and streak data. Fixed getLeaderboard(0) to return all entries instead of none.
src/visuals/rankCard.ts Added +237/-0 New visual renderer that generates a 1000x350px PNG rank card using canvas. Features include: font registration, rounded corners, rank-colored display (#1 blue, #2 silver, #3 bronze), avatar loading/rendering, user stats display, token gap information, and brand footer.
src/commands/ai/rank.ts Added +54/-0 New Discord command ($rank / $myrank) that fetches user rank data and renders it as a visual card. Supports mentioning other users, validates arguments, and provides helpful error messages for users with no AI usage history.

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
Loading

🎯 Key Changes:

📊 Impact Assessment:

  • Security: Low risk. The command validates user input and rejects invalid arguments. Avatar loading includes error handling and URL sanitization (webp/gif conversion to png). No sensitive data exposure.
  • Performance: Moderate consideration. getUserRank() fetches the entire leaderboard on each call, which could be expensive at scale. Consider caching the leaderboard or implementing pagination for large user bases. Canvas rendering is synchronous but acceptable for single-image generation.
  • Maintainability: High quality. Code is well-structured with clear separation of concerns (stats utility, visual renderer, command handler). Font registration is cached to avoid repeated I/O. Error handling is comprehensive with try-catch blocks and fallbacks.
  • Testing: No test files added. Recommend adding tests for: rank calculation edge cases (rank Enhance AskAI with per-user context and memory window update #1, last rank, missing users), canvas rendering with various data sizes, command argument validation, and avatar loading failures.
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

Comment thread src/visuals/rankCard.ts Outdated
};

function roundRectPath(
ctx: any,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Suggested change
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:

Suggested change
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.

Comment thread src/visuals/rankCard.ts
Comment on lines +72 to +82
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;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Suggested change
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

Comment thread src/commands/ai/rank.ts Outdated

try {
const hasMention = message.mentions?.users?.first();
const hasArgs = args.length > 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Suggested change
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.

@devhub-bot

devhub-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Note

Linting checks passed successfully 🎉

All formatting and code quality checks are clean.

You're good to merge 🚀

@goldstac

goldstac commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

doing some fixs

@beetle-ai

beetle-ai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary by Beetle

This PR improves type safety in the rank card visualization module by replacing loose any type annotations with the proper CanvasRenderingContext2D type from the @napi-rs/canvas library. This is a focused type-safety improvement that eliminates TypeScript type warnings and enhances code maintainability without changing any runtime behavior.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
src/visuals/rankCard.ts Modified +12/-3 Replaced any type annotations with proper CanvasRenderingContext2D type in the roundRectPath() and truncate() functions. Added explicit type import from @napi-rs/canvas library.

Total Changes: 1 file changed, +12 additions, -3 deletions

🎯 Key Changes:

  • Type Import Addition: Added explicit import of CanvasRenderingContext2D type from @napi-rs/canvas library, improving code clarity and enabling IDE support
  • Function Parameter Type Fixes:
  • roundRectPath() function: Changed ctx: any to ctx: CanvasRenderingContext2D
  • truncate() function: Changed ctx: any to ctx: CanvasRenderingContext2D
  • Code Formatting: Improved import statement formatting with multi-line structure for better readability
  • Zero Runtime Impact: All changes are purely type-level; no functional behavior is modified

📊 Impact Assessment:

  • Security: ✅ Positive Impact - Stronger type checking prevents potential runtime errors related to canvas context operations. Type safety reduces the attack surface for type-confusion vulnerabilities.
  • Performance: ✅ No Impact - This is a compile-time type annotation change with zero runtime overhead. No performance implications.
  • Maintainability: ✅ Significant Improvement - Replacing any types with proper CanvasRenderingContext2D types dramatically improves code maintainability:
  • IDEs can now provide accurate autocomplete and type hints for canvas context methods
  • Future developers can immediately understand what type of object is expected
  • TypeScript compiler can catch type-related errors at compile time rather than runtime
  • Reduces technical debt and improves code documentation through types
  • Testing: ✅ No Impact - No test changes required as this is a type-only modification. Existing tests remain valid and no new test coverage is needed.
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

@beetle-ai

beetle-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ You're good to merge this PR! No issues found. Great job!

Settings
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

@devhub-bot

devhub-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Note

Linting checks passed successfully 🎉

All formatting and code quality checks are clean.

You're good to merge 🚀

@beetle-ai

beetle-ai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary by Beetle

This PR enhances the security and reliability of the loadAvatar function in the rank card visualization module by implementing critical safeguards against malicious URLs, network timeouts, and oversized payloads. The changes prevent potential denial-of-service attacks, resource exhaustion, and unauthorized data fetching by adding URL validation, request timeouts, and file size limits.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
src/visuals/rankCard.ts Modified +17/-1 Added URL validation with hostname whitelist, request timeout handling (5s), and file size limit (5MB) to the loadAvatar function to prevent security vulnerabilities and resource exhaustion attacks

Total Changes: 1 file changed, +17 additions, -1 deletion

🎯 Key Changes:

  • URL Validation & Whitelist: Implemented hostname validation restricting avatar loads to Discord's official CDN domains (cdn.discordapp.com, media.discordapp.net), preventing SSRF (Server-Side Request Forgery) attacks
  • Request Timeout: Added 5-second timeout using AbortController to prevent hanging requests and resource exhaustion from slow/unresponsive servers
  • File Size Limit: Enforced 5MB maximum file size check via content-length header to prevent memory exhaustion from oversized payloads
  • Constants Extraction: Defined security parameters as named constants (MAX_AVATAR_SIZE, AVATAR_TIMEOUT, ALLOWED_HOSTS) for maintainability and easy configuration

📊 Impact Assessment:

  • Security: ✅ Significant Improvement
  • Prevents SSRF attacks by restricting to whitelisted Discord CDN domains
  • Blocks potential DoS attacks via oversized files or slow-loris techniques
  • Validates URL structure before processing
  • Reduces attack surface for malicious avatar URLs
  • Performance: ✅ Positive Impact
  • Timeout prevents indefinite hanging and resource leaks
  • Early rejection of oversized files avoids unnecessary memory allocation
  • Hostname validation fails fast for non-whitelisted domains
  • No performance degradation for legitimate Discord avatar URLs
  • Maintainability: ✅ Improved
  • Security parameters extracted as named constants for clarity and easy adjustment
  • Clear intent through descriptive constant names
  • Centralized configuration point for security policies
  • Well-structured error handling with early returns
  • Testing: ⚠️ Requires Attention
  • Should add unit tests for URL validation (valid/invalid hostnames)
  • Should test timeout behavior with slow/hanging requests
  • Should verify file size limit enforcement
  • Should test edge cases (null URLs, malformed URLs, missing content-length header)
  • Consider integration tests with actual Discord CDN URLs
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

@beetle-ai

beetle-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ You're good to merge this PR! No issues found. Great job!

Settings
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

@devhub-bot

devhub-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Note

Linting checks passed successfully 🎉

All formatting and code quality checks are clean.

You're good to merge 🚀

@beetle-ai

beetle-ai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary by Beetle

This PR addresses a null safety bug in the rank command by adding a defensive null check for the args parameter. The fix prevents potential runtime errors that could occur when args is undefined or null before attempting to access its .length property. This is a critical defensive programming improvement that ensures the command handles edge cases gracefully.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
src/commands/ai/rank.ts Modified +1/-1 Added null check for args parameter before accessing .length property to prevent potential TypeError when args is undefined or null

Total Changes: 1 file changed, +1 additions, -1 deletions

🎯 Key Changes:

  • Null Safety Fix: Changed const hasArgs = args.length > 0; to const hasArgs = args && args.length > 0; to safely check if args exists before accessing its length property
  • Defensive Programming: Implements a guard clause pattern that prevents runtime errors in edge cases where the args parameter might be undefined or null
  • Minimal Impact: Single-line change with zero risk of introducing new bugs while fixing a potential crash scenario

📊 Impact Assessment:

  • Security: ✅ Low Risk - This is a defensive null check that improves robustness. No security vulnerabilities introduced or fixed.
  • Performance: ✅ Negligible Impact - The additional null check adds minimal overhead (a single boolean evaluation) and is a best practice that has no measurable performance penalty.
  • Maintainability: ✅ Improved - The explicit null check makes the code's intent clearer and follows defensive programming best practices. Future maintainers will understand that args can be null/undefined and the code handles it appropriately.
  • Testing: ⚠️ Recommendation - Consider adding test cases for edge scenarios:
  • Command invoked with args = null
  • Command invoked with args = undefined
  • Command invoked with args = [] (empty array)
  • Command invoked with args containing values
    These tests would ensure the fix works as intended and prevent regression.
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

@beetle-ai

beetle-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ You're good to merge this PR! No issues found. Great job!

Settings
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only. — Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

@devhub-bot

devhub-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Note

Linting checks passed successfully 🎉

All formatting and code quality checks are clean.

You're good to merge 🚀

@calebephrem
calebephrem merged commit 069c5fc into open-devhub:main Sep 2, 2026
4 checks passed
@calebephrem

Copy link
Copy Markdown
Member

Thanks for contributing!

@goldstac

goldstac commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for contributing!

:3

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

Labels

feat New feature fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants