Conversation
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughMultiple command modules switch from static JSON imports to lazy runtime loading via Bun.file().json() with module-level caching. The f1Standings parser is rewritten to use cheerio instead of jsdom. Token counting is simplified to a character-length heuristic. Several Genshin/HSR data files had schema and content updates. ChangesRuntime data loading, parser migration, and tokenizer simplification
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
commands/gacha.ts (2)
73-80:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftEarly return makes the entire roll flow — including the new lazy-load — unreachable.
run()always replies with "gacha is not ready yet" and returns at line 75, sogetPools()(line 77) and everything below it (DB reads/writes, embed/button rendering, collector) is dead code, suppressed only by/* eslint-disable no-unreachable */at line 1. Two consequences:
- The lazy-loading change in this PR is not actually exercised for
gacha— it does not save RAM here because it never runs, and it also never gets validated.- When the early return is eventually removed, several latent bugs become live (see follow-up comment about
customIdmismatch, andthis.characterPool.some((c) => c.name === ...)at line 129 which references a non-existent field on the pool entries —AvatarName.Hashis what they expose).If gacha is intentionally disabled, consider either gating it at the command-registration level or short-circuiting before reaching code that you intend to keep dormant, and dropping the
eslint-disableso future regressions are caught.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/gacha.ts` around lines 73 - 80, The run() method early-returns before the new lazy-load and entire roll flow run; remove or relocate the "gacha is not ready yet" early return so getPools() executes (or gate-disable the command at registration time instead of returning inside run()), and drop the top-level "eslint-disable no-unreachable" so unreachable code is detected; ensure the lazy-load populates this.namesData, this.characterPool, and this.lightconePool by calling getPools() before any DB/read/write or collector logic, and fix all field accesses that assume character entries have a "name" property (e.g., code using this.characterPool.some(c => c.name === ...)) to use the actual AvatarName.Hash (or the correct property exposed by getPools()), and verify any customId comparisons match the IDs generated elsewhere.
149-194:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
customIdmismatch — buttons will be inert once gacha is enabled.Buttons are registered with
nextRoll(lines 149, 172) andskipResults(lines 154, 176), but the collector at lines 186 and 191 compares againstnext_rollandskip_results. Neither branch will ever fire, so "Next" and "Skip to Results" do nothing and the collector only ends on timeout.🛠 Proposed fix
- new ButtonBuilder() - .setCustomId('nextRoll') + new ButtonBuilder() + .setCustomId('next_roll') .setLabel('➡️ Next') .setStyle(ButtonStyle.Primary) .setDisabled(currentIndex === results.length - 1), new ButtonBuilder() - .setCustomId('skipResults') + .setCustomId('skip_results') .setLabel('Skip to Results') .setStyle(ButtonStyle.Danger),Apply the same rename to the
initialRowbuilders at lines 172 and 176.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/gacha.ts` around lines 149 - 194, The collector's i.customId checks use snake_case ('next_roll', 'skip_results') but the buttons are created with camelCase IDs ('nextRoll', 'skipResults'), so the branches never run; fix by making the IDs consistent — either change the setCustomId calls on the ButtonBuilder instances (currently 'nextRoll' and 'skipResults') to 'next_roll' and 'skip_results' or update the collector logic to check for 'nextRoll' and 'skipResults' (references: the ButtonBuilder.setCustomId entries and the collector.on('collect', async (i) => { if (i.customId === 'next_roll' ...) } )). Ensure both the initialRow and updateMessage button builders use the exact same IDs the collector expects.commands/f1Standings.ts (1)
112-117:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winMissing
returnafter the invalid-year reply causes a doubleeditReply.After calling
interaction.editReply(...)with the validation error, execution falls through to thefetchcall at line 123. If the request succeeds,interaction.editReplyis called a second time at line 128. Discord.js throws on the secondeditReply, producing an unhandled rejection and a broken UX.🐛 Proposed fix
if (year > currentYear || year < minYear) { - interaction.editReply({ + await interaction.editReply({ content: `Invalid year for ${type} standings. Must be between ${minYear} and ${currentYear}.`, ephemeral: true, }); + return; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/f1Standings.ts` around lines 112 - 117, The invalid-year branch in the interaction handler calls interaction.editReply(...) but does not stop execution, allowing fetch and a second interaction.editReply(...) later; update the validation in the handler (the block that checks "if (year > currentYear || year < minYear)" using variables year, currentYear, minYear, and type) to return immediately after sending the ephemeral error reply so the subsequent fetch and the later interaction.editReply are not executed.
🧹 Nitpick comments (3)
commands/hsrProfile.ts (1)
46-48: 💤 Low valueOptional: move
await loadHsrData()inside the try block.If any of the four JSON reads ever fail (corruption, missing file, permission), the rejection will bypass the existing
catchat line 133-136 and surface as an unhandled rejection on the interaction. Moving the call insidetrywould route the failure through the same user-facing error reply path used for fetch failures.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/hsrProfile.ts` around lines 46 - 48, Move the await loadHsrData() call inside the existing try block so any rejection from reading avatarData, characterData, namesData, or lightconeData is handled by the existing catch that sends the user-facing error reply; specifically, replace the top-level const { avatarData, characterData, namesData, lightconeData } = await loadHsrData() with the same await call executed inside the try that surrounds the fetch/processing logic so failures from loadHsrData() flow into the same catch path.commands/genshinProfile.ts (1)
37-37: 💤 Low valueOptional: move
await loadGenshinData()inside the try block.If
Bun.file(...).json()rejects (file missing/corrupt), the existingcatchat line 87-90 is bypassed and the user gets no reply. Moving line 37 below line 39 routes load failures through the same user-facing fallback path used for upstream API errors.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/genshinProfile.ts` at line 37, The call to loadGenshinData() is currently awaited outside the try/catch so filesystem errors escape the catch; move the await into the existing try block so failures are handled by the same user-facing fallback. Concretely, declare variables for profilePictures and namecards (e.g. let profilePictures, namecards;) before the try, then perform const { profilePictures: pp, namecards: nc } = await loadGenshinData() or assign directly inside the try (or use const inside the try) and use those variables afterward; update any renamed bindings accordingly so scope/usage remains correct and no unhandled rejections occur. Ensure the change references the loadGenshinData() call and the surrounding try/catch that currently handles upstream API errors.utils/ai.ts (1)
129-133: ⚡ Quick winInline estimation duplicates
countTokensOpenRouterMessagesfromtokenizer.ts.The loop on lines 130–132 is byte-for-byte equivalent to the exported
countTokensOpenRouterMessages(requestMessages). If the formula is updated intokenizer.tsagain (as it was in this PR), this copy inai.tswill silently diverge.♻️ Suggested refactor
Add the import at the top of
ai.ts:+import { countTokensOpenRouterMessages } from './tokenizer';Then replace the estimation loop:
- let estimated = 0; - for (const m of requestMessages) { - estimated += Math.ceil(m.content.length / 4) + 4; - } + const estimated = countTokensOpenRouterMessages(requestMessages); recordUsage(model, estimated, actualPromptTokens);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/ai.ts` around lines 129 - 133, The token-estimation loop in ai.ts duplicates logic from tokenizer.ts (countTokensOpenRouterMessages); import countTokensOpenRouterMessages into ai.ts and replace the manual loop (the estimated calculation using Math.ceil over requestMessages) with a call like const estimated = countTokensOpenRouterMessages(requestMessages), then pass that estimated value into recordUsage(model, estimated, actualPromptTokens); remove the now-unused manual loop/variable to avoid divergence when the token formula changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@commands/f1Standings.ts`:
- Around line 44-48: The parsing of numeric cells uses parseInt which can return
NaN for non-numeric text (e.g. "–"), so update the parsing logic in
commands/f1Standings.ts (the lines setting position and points and the analogous
team parsing at lines referenced in the review) to coerce NaN to 0;
specifically, replace direct parseInt(...) uses with a safe parse that checks
Number.isNaN(result) and falls back to 0 (apply the same pattern for position,
points and the team parsing block), so buildEmbed never receives NaN values.
---
Outside diff comments:
In `@commands/f1Standings.ts`:
- Around line 112-117: The invalid-year branch in the interaction handler calls
interaction.editReply(...) but does not stop execution, allowing fetch and a
second interaction.editReply(...) later; update the validation in the handler
(the block that checks "if (year > currentYear || year < minYear)" using
variables year, currentYear, minYear, and type) to return immediately after
sending the ephemeral error reply so the subsequent fetch and the later
interaction.editReply are not executed.
In `@commands/gacha.ts`:
- Around line 73-80: The run() method early-returns before the new lazy-load and
entire roll flow run; remove or relocate the "gacha is not ready yet" early
return so getPools() executes (or gate-disable the command at registration time
instead of returning inside run()), and drop the top-level "eslint-disable
no-unreachable" so unreachable code is detected; ensure the lazy-load populates
this.namesData, this.characterPool, and this.lightconePool by calling getPools()
before any DB/read/write or collector logic, and fix all field accesses that
assume character entries have a "name" property (e.g., code using
this.characterPool.some(c => c.name === ...)) to use the actual AvatarName.Hash
(or the correct property exposed by getPools()), and verify any customId
comparisons match the IDs generated elsewhere.
- Around line 149-194: The collector's i.customId checks use snake_case
('next_roll', 'skip_results') but the buttons are created with camelCase IDs
('nextRoll', 'skipResults'), so the branches never run; fix by making the IDs
consistent — either change the setCustomId calls on the ButtonBuilder instances
(currently 'nextRoll' and 'skipResults') to 'next_roll' and 'skip_results' or
update the collector logic to check for 'nextRoll' and 'skipResults'
(references: the ButtonBuilder.setCustomId entries and the
collector.on('collect', async (i) => { if (i.customId === 'next_roll' ...) } )).
Ensure both the initialRow and updateMessage button builders use the exact same
IDs the collector expects.
---
Nitpick comments:
In `@commands/genshinProfile.ts`:
- Line 37: The call to loadGenshinData() is currently awaited outside the
try/catch so filesystem errors escape the catch; move the await into the
existing try block so failures are handled by the same user-facing fallback.
Concretely, declare variables for profilePictures and namecards (e.g. let
profilePictures, namecards;) before the try, then perform const {
profilePictures: pp, namecards: nc } = await loadGenshinData() or assign
directly inside the try (or use const inside the try) and use those variables
afterward; update any renamed bindings accordingly so scope/usage remains
correct and no unhandled rejections occur. Ensure the change references the
loadGenshinData() call and the surrounding try/catch that currently handles
upstream API errors.
In `@commands/hsrProfile.ts`:
- Around line 46-48: Move the await loadHsrData() call inside the existing try
block so any rejection from reading avatarData, characterData, namesData, or
lightconeData is handled by the existing catch that sends the user-facing error
reply; specifically, replace the top-level const { avatarData, characterData,
namesData, lightconeData } = await loadHsrData() with the same await call
executed inside the try that surrounds the fetch/processing logic so failures
from loadHsrData() flow into the same catch path.
In `@utils/ai.ts`:
- Around line 129-133: The token-estimation loop in ai.ts duplicates logic from
tokenizer.ts (countTokensOpenRouterMessages); import
countTokensOpenRouterMessages into ai.ts and replace the manual loop (the
estimated calculation using Math.ceil over requestMessages) with a call like
const estimated = countTokensOpenRouterMessages(requestMessages), then pass that
estimated value into recordUsage(model, estimated, actualPromptTokens); remove
the now-unused manual loop/variable to avoid divergence when the token formula
changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 06b0557e-f3d6-483a-9a04-8c9fdf90772c
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
commands/f1Standings.tscommands/gacha.tscommands/genshinProfile.tscommands/hsrProfile.tscommands/nword.tscommands/spotifyPlaylist.tsdata/characters.jsondata/genshinNamecards.jsondata/genshinPfps.jsondata/hsr.jsondata/hsrAvartars.jsondata/hsrCharacters.jsondata/hsrLC.jsondata/nwords.jsondata/words_dictionary.jsonpackage.jsonutils/ai.tsutils/tokenizer.ts
💤 Files with no reviewable changes (1)
- data/characters.json
f1Standings.ts: added safeParseInt helper, replaced parseInt calls in driver/team rows, added await + return to invalid-year branch. gacha.ts: removed eslint-disable no-unreachable and the early-return so getPools() populates fields; replaced the broken c.name check with rollResult?.AvatarName discriminator; aligned collector customIds (nextRoll/skipResults) with the button IDs. genshinProfile.ts & hsrProfile.ts: moved loadGenshinData()/loadHsrData() calls inside the existing try blocks so file-read failures hit the existing user-facing fallback. ai.ts: imported countTokensOpenRouterMessages from tokenizer.ts and replaced the duplicated manual loop with a single call.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
commands/gacha.ts (1)
57-69:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
imagePathkey in the early return should beimageUrlafter the rename.Line 67 was updated to return
imageUrl, but the early-return guard on line 58 still returnsimagePath: null. Any caller that doesitem.imageUrl(lines 138, 161) will receiveundefinedinstead ofnullwhengetItemDetailsis called with a falsy item (e.g., an empty pool).🐛 Proposed fix
- if (!item) return { name: 'Unknown', imagePath: null, rarity: 'Unknown' }; + if (!item) return { name: 'Unknown', imageUrl: null, rarity: 'Unknown' };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/gacha.ts` around lines 57 - 69, In getItemDetails, the early-return object still uses the old key imagePath; change that to imageUrl so it matches the rest of the function and callers (e.g., where item.imageUrl is accessed). Update the guard return from { name: 'Unknown', imagePath: null, rarity: 'Unknown' } to use imageUrl: null while keeping name and rarity the same so callers receive a consistent shape.
🧹 Nitpick comments (1)
commands/hsrProfile.ts (1)
12-25: ⚡ Quick winCache the in-flight
Promiseto prevent concurrent duplicate file reads.Because
loadHsrDatastores only the resolved value, two concurrent calls can both passif (!dataCache)before eitherPromise.allresolves, causing the four JSON files to be read twice. Caching the promise itself closes the race:♻️ Proposed refactor
-let dataCache: { - avatarData: any; - characterData: any; - namesData: any; - lightconeData: any; -} | null = null; - -async function loadHsrData() { - if (!dataCache) { - const [avatarData, characterData, namesData, lightconeData] = await Promise.all([ - Bun.file(path.join(__dirname, '../data/hsrAvartars.json')).json(), - Bun.file(path.join(__dirname, '../data/hsrCharacters.json')).json(), - Bun.file(path.join(__dirname, '../data/hsr.json')).json(), - Bun.file(path.join(__dirname, '../data/hsrLC.json')).json(), - ]); - dataCache = { - avatarData, characterData, namesData, lightconeData, - }; - } - return dataCache!; -} +let dataCachePromise: Promise<{ + avatarData: any; + characterData: any; + namesData: any; + lightconeData: any; +}> | null = null; + +function loadHsrData() { + if (!dataCachePromise) { + dataCachePromise = Promise.all([ + Bun.file(path.join(__dirname, '../data/hsrAvartars.json')).json(), + Bun.file(path.join(__dirname, '../data/hsrCharacters.json')).json(), + Bun.file(path.join(__dirname, '../data/hsr.json')).json(), + Bun.file(path.join(__dirname, '../data/hsrLC.json')).json(), + ]).then(([avatarData, characterData, namesData, lightconeData]) => ({ + avatarData, characterData, namesData, lightconeData, + })); + } + return dataCachePromise; +}The same pattern exists in
genshinProfile.ts(loadGenshinData) andgacha.ts(getPools) and should receive the same fix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@commands/hsrProfile.ts` around lines 12 - 25, loadHsrData currently only caches the resolved value so concurrent calls race and duplicate file reads; change it to cache the in-flight Promise (e.g., add a module-level dataCachePromise alongside dataCache), assign dataCachePromise = Promise.all([...]).then(results => { set dataCache from results; dataCachePromise = null; return dataCache; }) before awaiting, and return await dataCachePromise; apply the same pattern to loadGenshinData and getPools so simultaneous callers share the same Promise and only one set of file reads occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@commands/gacha.ts`:
- Around line 57-69: In getItemDetails, the early-return object still uses the
old key imagePath; change that to imageUrl so it matches the rest of the
function and callers (e.g., where item.imageUrl is accessed). Update the guard
return from { name: 'Unknown', imagePath: null, rarity: 'Unknown' } to use
imageUrl: null while keeping name and rarity the same so callers receive a
consistent shape.
---
Nitpick comments:
In `@commands/hsrProfile.ts`:
- Around line 12-25: loadHsrData currently only caches the resolved value so
concurrent calls race and duplicate file reads; change it to cache the in-flight
Promise (e.g., add a module-level dataCachePromise alongside dataCache), assign
dataCachePromise = Promise.all([...]).then(results => { set dataCache from
results; dataCachePromise = null; return dataCache; }) before awaiting, and
return await dataCachePromise; apply the same pattern to loadGenshinData and
getPools so simultaneous callers share the same Promise and only one set of file
reads occurs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 856898d1-4d6a-401c-9bc4-3d0b5249204c
📒 Files selected for processing (5)
commands/f1Standings.tscommands/gacha.tscommands/genshinProfile.tscommands/hsrProfile.tsutils/ai.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- commands/f1Standings.ts
commands/gacha.ts:58 — guard return now uses imageUrl: null to match the success-path shape. commands/hsrProfile.ts:12-29, commands/genshinProfile.ts:5-21, commands/gacha.ts:7-28 — each loader now caches the in-flight Promise so concurrent callers share one set of file reads, clearing the promise once the cache is populated.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores