Skip to content

feat(bot): autoplay artist prefer/block preferences - #572

Merged
LucasSantana-Dev merged 7 commits into
mainfrom
feature/autoplay-artist-preferences-v3
Apr 12, 2026
Merged

LucasSantana-Dev merged 7 commits into
mainfrom
feature/autoplay-artist-preferences-v3

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Apr 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Extends feedbackService with artist-level feedback: setArtistFeedback, removeArtistFeedback, getPreferredArtistKeys, getBlockedArtistKeys, getArtistFeedbackSummary
  • Artist keys normalized via cleanAuthor() + lowercase for consistent matching
  • Wires artist preferences into calculateRecommendationScore: blocked artists excluded, preferred get +0.3 boost
  • Adds /autoplay artist subcommand group: prefer, block, remove, list
  • All data stored in Redis with 30d TTL

Replaces #571 (rebased onto main after v2.6.91 merge).

Summary by CodeRabbit

  • New Features

    • Added an /autoplay "artist" group so users can prefer, block, list, and remove artists to influence recommendations.
    • Recommendations now respect per-user artist preferences and blocks, improving suggestion relevance.
  • Bug Fixes

    • Clarified "Unknown Subcommand" help text to remove an obsolete hint.
  • Tests

    • Expanded test coverage for artist-level feedback and recommendation behavior.

- add setArtistFeedback, removeArtistFeedback methods
- add getPreferredArtistKeys, getBlockedArtistKeys methods
- add getArtistFeedbackSummary for listing user's preferences
- store as separate Redis hash with 30d TTL
- normalize artist names using cleanAuthor + normalizeText
- update calculateRecommendationScore to accept preferred/blocked artist keys
- return -Infinity score for blocked artists (exclude from candidates)
- add +0.3 boost for preferred artists
- fetch artist preference sets in _replenishQueue
- pass artist keys to all candidate collection functions
- skip candidates with -Infinity scores
- Add handleAutoplayArtist handler with prefer, block, remove, list subcommands
- Integrate with recommendationFeedbackService for artist preference storage
- Display preferred/blocked artists with emoji indicators and counts
- List subcommand shows all preferred and blocked artists for user
@vercel

vercel Bot commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment Apr 12, 2026 4:38am

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file bot labels Apr 12, 2026
@coderabbitai

coderabbitai Bot commented Apr 12, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2d7ab50-b575-4121-a2a2-fbc111719245

📥 Commits

Reviewing files that changed from the base of the PR and between 38e02f6 and 304bf59.

📒 Files selected for processing (2)
  • packages/bot/src/functions/music/commands/autoplay.spec.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bot/src/functions/music/commands/autoplay.spec.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Quality Gates
  • GitHub Check: SonarCloud Scan
  • GitHub Check: compressed-size

📝 Walkthrough

Walkthrough

Adds artist-level feedback: new artist subcommand group for /autoplay, RecommendationFeedbackService artist APIs (prefer/block/remove/list) persisted in Redis, and artist-based prefer/block logic wired into queue replenishment and recommendation scoring.

Changes

Cohort / File(s) Summary
Autoplay Command
packages/bot/src/functions/music/commands/autoplay.ts, packages/bot/src/functions/music/commands/autoplay.spec.ts
Adds artist subcommand group (prefer, block, remove, list), implements handleAutoplayArtist() routing and replies; updates unknown-subcommand help text; tests for artist flows and error cases.
Feedback Service
packages/bot/src/services/musicRecommendation/feedbackService.ts, packages/bot/src/services/musicRecommendation/feedbackService.spec.ts
Introduces ArtistFeedback type and Redis-backed artist feedback namespace (music:artist_feedback:{userId}); implements set/remove/get and summary methods with normalization, parsing, TTL, and tests.
Queue Manipulation & Scoring
packages/bot/src/utils/music/queueManipulation.ts, packages/bot/src/utils/music/queueManipulation.spec.ts
Fetches preferred/blocked artist key sets during _replenishQueue(), threads them into candidate collection and calculateRecommendationScore(), skips candidates scoring -Infinity, and applies preferred/blocked scoring rules; tests updated with new mocks.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Bot as Autoplay Command
    participant Service as Recommendation<br/>Feedback Service
    participant Redis as Redis Store
    participant Queue as QueueReplenisher

    User->>Bot: /autoplay artist prefer {artist}
    activate Bot
    Bot->>Service: setArtistFeedback(guildId,userId,artist,'prefer')
    activate Service
    Service->>Service: normalize & validate artist
    Service->>Redis: setex music:artist_feedback:{userId} (JSON)
    activate Redis
    Redis-->>Service: OK
    deactivate Redis
    Service-->>Bot: success
    deactivate Service
    Bot-->>User: confirmation embed
    deactivate Bot

    Note over Bot,Queue: Later: queue refill uses artist feedback
    Queue->>Service: getPreferredArtistKeys(userId)
    activate Service
    Service->>Redis: get music:artist_feedback:{userId}
    activate Redis
    Redis-->>Service: JSON map
    deactivate Redis
    Service-->>Queue: Set(preferred)
    deactivate Service

    Queue->>Queue: collectRecommendationCandidates(..., preferred, blocked)
    Queue->>Queue: calculateRecommendationScore(candidate, preferred, blocked)
    alt Artist is blocked
        Queue->>Queue: return -Infinity (skip)
    else Artist is preferred
        Queue->>Queue: apply positive boost
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

size/l

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(bot): autoplay artist prefer/block preferences' accurately and concisely summarizes the main change: adding artist-level preference functionality (prefer/block) to the autoplay system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/autoplay-artist-preferences-v3

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 12, 2026 •

Copy link
Copy Markdown

Size Change: 0 B

Total Size: 324 kB

ℹ️ View Unchanged
Filename Size
packages/frontend/dist/assets/ActionPanel-C1_aFFa9.js 398 B
packages/frontend/dist/assets/api-D_-RdxXc.js 2.9 kB
packages/frontend/dist/assets/authStore-CL0s37B7.js 559 B
packages/frontend/dist/assets/AutoMessages-CPYY01Td.js 2.69 kB
packages/frontend/dist/assets/AutoMod-CDpr6_0e.js 4.09 kB
packages/frontend/dist/assets/avatar-BQnx8ml3.js 1.79 kB
packages/frontend/dist/assets/badge-1w20SMT3.js 499 B
packages/frontend/dist/assets/Button-CK6bsnJt.js 761 B
packages/frontend/dist/assets/Card-DnMZziVL.js 453 B
packages/frontend/dist/assets/chunk-QFMPRPBF-B0_TipZB.js 14.8 kB
packages/frontend/dist/assets/CommandsConfig-BaafAP8k.js 1.49 kB
packages/frontend/dist/assets/Config-y-1dh3xE.js 1.74 kB
packages/frontend/dist/assets/CustomCommands-DFSNb6wg.js 2.16 kB
packages/frontend/dist/assets/DashboardOverview-Dw4Oj8vS.js 3.43 kB
packages/frontend/dist/assets/dialog-Bebu5T9M.js 947 B
packages/frontend/dist/assets/dist-BYvsGl5s.js 414 B
packages/frontend/dist/assets/EmbedBuilder-CuEvIZCu.js 3.36 kB
packages/frontend/dist/assets/EmptyState-lhGRi9gh.js 428 B
packages/frontend/dist/assets/Features-Ciu-k9zJ.js 2.85 kB
packages/frontend/dist/assets/GuildAutomation-CJFMIn2D.js 2.94 kB
packages/frontend/dist/assets/guildStore-BV_XBBBy.js 792 B
packages/frontend/dist/assets/index-DoLe_VYd.css 14.4 kB
packages/frontend/dist/assets/index-pmhDNDvr.js 11.7 kB
packages/frontend/dist/assets/input-CmtLKKUO.js 461 B
packages/frontend/dist/assets/label-BHlkUPCY.js 483 B
packages/frontend/dist/assets/LastFm-BMvpoKNR.js 1.74 kB
packages/frontend/dist/assets/Levels-CEPSA5iI.js 2.65 kB
packages/frontend/dist/assets/Login-Dd1nb_AU.js 2.54 kB
packages/frontend/dist/assets/Lyrics-Ds029m2Q.js 1.34 kB
packages/frontend/dist/assets/Moderation-B_5DFs5S.js 3.86 kB
packages/frontend/dist/assets/Music-CCdDzezk.js 6.17 kB
packages/frontend/dist/assets/MusicConfig-CGXlDETa.js 1.62 kB
packages/frontend/dist/assets/PrivacyPolicy-BTjDqB3o.js 1.38 kB
packages/frontend/dist/assets/rbac-BN1DzQHh.js 152 B
packages/frontend/dist/assets/ReactionRoles-CFdc-f8y.js 1.9 kB
packages/frontend/dist/assets/rolldown-runtime-COnpUsM8.js 467 B
packages/frontend/dist/assets/scroll-area-DE87jUCx.js 517 B
packages/frontend/dist/assets/SectionHeader-Ct2qWDhq.js 382 B
packages/frontend/dist/assets/select-sC_ROyTR.js 1.22 kB
packages/frontend/dist/assets/ServerLogs-GsBRQPqJ.js 2.9 kB
packages/frontend/dist/assets/ServerSettings-CySBcB-C.js 4.22 kB
packages/frontend/dist/assets/ServersPage-aNYsiHLz.js 2.95 kB
packages/frontend/dist/assets/Skeleton-DMHFbvVW.js 234 B
packages/frontend/dist/assets/Starboard-Huh0Q8J2.js 2.1 kB
packages/frontend/dist/assets/StatTile-BkmmFyEu.js 604 B
packages/frontend/dist/assets/switch-RfNgpq5v.js 539 B
packages/frontend/dist/assets/TermsOfService-DLolfOXC.js 1.37 kB
packages/frontend/dist/assets/TrackHistory-gWiPO0eI.js 1.91 kB
packages/frontend/dist/assets/TwitchNotifications-BQDRT0yF.js 2.29 kB
packages/frontend/dist/assets/useGuildSelection-DHENV7N5.js 491 B
packages/frontend/dist/assets/usePageMetadata-B513X5D1.js 328 B
packages/frontend/dist/assets/utils-D5piDr1U.js 148 B
packages/frontend/dist/assets/vendor-forms-B7m4p2yU.js 25.4 kB
packages/frontend/dist/assets/vendor-radix-CqxcePto.js 33.5 kB
packages/frontend/dist/assets/vendor-react-DRFstzzV.js 55.6 kB
packages/frontend/dist/assets/vendor-state-C5KtdxzA.js 22.2 kB
packages/frontend/dist/assets/vendor-ui-BIJpYk2I.js 64.2 kB

compressed-size-action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/bot/src/utils/music/queueManipulation.ts (2)

809-821: ⚠️ Potential issue | 🔴 Critical

Genre candidates need the same blocked-artist guard.

addGenreTrackCandidate() always upserts, even when calculateRecommendationScore() returns -Infinity for a blocked artist. That breaks the new “blocked artists are excluded” contract whenever recommendations come from autoplayGenres.

Minimal fix
     const rec = calculateRecommendationScore(
         track,
         ctx.currentTrack,
         ctx.recentArtists,
         ctx.likedTrackKeys,
         ctx.preferredArtistKeys,
         ctx.blockedArtistKeys,
         ctx.autoplayMode,
     )
+    if (rec.score === -Infinity) return
     upsertScoredCandidate(ctx.candidates, track, {
         score: rec.score + GENRE_SCORE_BOOST,
         reason: rec.reason ? `${rec.reason} • ${tag} vibes` : `${tag} vibes`,
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/music/queueManipulation.ts` around lines 809 - 821,
addGenreTrackCandidate currently always calls upsertScoredCandidate even when
calculateRecommendationScore returns -Infinity for blocked artists, which
violates the blocked-artist exclusion; fix addGenreTrackCandidate (the block
around the rec = calculateRecommendationScore and upsertScoredCandidate call) to
skip the upsert when rec.score is -Infinity (or otherwise non-finite/represents
blocked) so only valid scored candidates are added to ctx.candidates; use the
existing calculateRecommendationScore and upsertScoredCandidate symbols to
locate and guard the insertion.

731-745: ⚠️ Potential issue | 🔴 Critical

Blocked artists still leak through the Last.fm similar-track branch.

This path now passes blockedArtistKeys into calculateRecommendationScore(), but unlike the seed and broad-fallback loops it never short-circuits on rec.score === -Infinity. If this branch is the only one producing candidates, a blocked artist can still be selected and queued.

Minimal fix
                 const rec = calculateRecommendationScore(
                     track,
                     currentTrack,
                     recentArtists,
                     likedTrackKeys,
                     preferredArtistKeys,
                     blockedArtistKeys,
                     autoplayMode,
                 )
+                if (rec.score === -Infinity) continue
                 upsertScoredCandidate(candidates, track, {
                     score: (rec.score + LASTFM_SCORE_BOOST) * (s.match / 100),
                     reason: rec.reason
                         ? `${rec.reason} • similar to your taste`
                         : 'similar to your taste',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/music/queueManipulation.ts` around lines 731 - 745,
The Last.fm branch calls calculateRecommendationScore(...) and immediately
upserts candidates, allowing blocked artists to leak; change this to skip
inserting when calculateRecommendationScore returns rec.score === -Infinity
(i.e., treat it as a rejected/blocked track). Specifically, in the block that
computes rec via calculateRecommendationScore(track, currentTrack,
recentArtists, likedTrackKeys, preferredArtistKeys, blockedArtistKeys,
autoplayMode) and then calls upsertScoredCandidate(...), add a guard that if
rec.score === -Infinity you do not call upsertScoredCandidate (continue/return
from the loop), preserving the existing score/ reason logic (including
LASTFM_SCORE_BOOST and s.match multiplier) only for non-rejected rec values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bot/src/services/musicRecommendation/feedbackService.ts`:
- Around line 224-226: The code reads the whole artist feedback map with
getArtistFeedbackMap, mutates it and writes it back with saveArtistFeedbackMap
which causes lost updates under concurrency; replace that read-modify-write with
an atomic update (either switch to a Redis hash and use HSET with a per-user key
so you can set artistKey directly, or implement a Redis transaction/LUA script
that updates the JSON map atomically and retries on watch failure). Update both
call sites that currently call getArtistFeedbackMap/saveArtistFeedbackMap (the
block around the map[artistKey] = feedback and the other occurrence at lines
~245-247) to use the new atomic setter (or HSET) instead and remove the
non-atomic round-trip.
- Around line 285-299: getArtistFeedbackSummary currently returns normalized
storage keys instead of human-readable artist names; change it to resolve each
artistKey from getArtistFeedbackMap into a display name before pushing to
preferred/blocked. For each artistKey from the map, look up the artist record
(e.g. via an existing lookup like this.getArtistByKey, this.artistStore.get, or
a similar method) and use its display/name field (falling back to artistKey if
no record) when populating the preferred and blocked arrays, then return those
display names. Ensure you only change getArtistFeedbackSummary and keep the
returned shape { preferred: string[]; blocked: string[] } so callers such as the
autoplay command render human-friendly names.
- Around line 214-255: Change setArtistFeedback and removeArtistFeedback to
return a success boolean (Promise<boolean>) instead of void: if
normalizeArtistKey(...) returns falsy immediately return false; after loading
and mutating the map via getArtistFeedbackMap(...) and
saveArtistFeedbackMap(...), return true on success; in each catch block return
false (and still call errorLog with the same context). Use the existing symbols
normalizeArtistKey, getArtistFeedbackMap, and saveArtistFeedbackMap to locate
the logic and ensure callers (e.g., the autoplay command) can await the boolean
result to decide whether to show a success embed.

---

Outside diff comments:
In `@packages/bot/src/utils/music/queueManipulation.ts`:
- Around line 809-821: addGenreTrackCandidate currently always calls
upsertScoredCandidate even when calculateRecommendationScore returns -Infinity
for blocked artists, which violates the blocked-artist exclusion; fix
addGenreTrackCandidate (the block around the rec = calculateRecommendationScore
and upsertScoredCandidate call) to skip the upsert when rec.score is -Infinity
(or otherwise non-finite/represents blocked) so only valid scored candidates are
added to ctx.candidates; use the existing calculateRecommendationScore and
upsertScoredCandidate symbols to locate and guard the insertion.
- Around line 731-745: The Last.fm branch calls
calculateRecommendationScore(...) and immediately upserts candidates, allowing
blocked artists to leak; change this to skip inserting when
calculateRecommendationScore returns rec.score === -Infinity (i.e., treat it as
a rejected/blocked track). Specifically, in the block that computes rec via
calculateRecommendationScore(track, currentTrack, recentArtists, likedTrackKeys,
preferredArtistKeys, blockedArtistKeys, autoplayMode) and then calls
upsertScoredCandidate(...), add a guard that if rec.score === -Infinity you do
not call upsertScoredCandidate (continue/return from the loop), preserving the
existing score/ reason logic (including LASTFM_SCORE_BOOST and s.match
multiplier) only for non-rejected rec values.
🪄 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: 084d5ecd-f075-4791-b3dc-1dad32ba61e9

📥 Commits

Reviewing files that changed from the base of the PR and between 14e406f and 38e02f6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • packages/bot/src/functions/music/commands/autoplay.spec.ts
  • packages/bot/src/functions/music/commands/autoplay.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.spec.ts
  • packages/bot/src/services/musicRecommendation/feedbackService.ts
  • packages/bot/src/utils/music/queueManipulation.spec.ts
  • packages/bot/src/utils/music/queueManipulation.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: SonarCloud Scan
  • GitHub Check: Quality Gates
  • GitHub Check: compressed-size

Comment thread packages/bot/src/services/musicRecommendation/feedbackService.ts
Comment thread packages/bot/src/services/musicRecommendation/feedbackService.ts
Comment thread packages/bot/src/services/musicRecommendation/feedbackService.ts
@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev merged commit fa55140 into main Apr 12, 2026
13 checks passed
LucasSantana-Dev added a commit that referenced this pull request May 13, 2026
* feat(bot): add artist-level feedback methods to feedbackService

- add setArtistFeedback, removeArtistFeedback methods
- add getPreferredArtistKeys, getBlockedArtistKeys methods
- add getArtistFeedbackSummary for listing user's preferences
- store as separate Redis hash with 30d TTL
- normalize artist names using cleanAuthor + normalizeText

* feat(bot): wire artist preferences into autoplay scoring

- update calculateRecommendationScore to accept preferred/blocked artist keys
- return -Infinity score for blocked artists (exclude from candidates)
- add +0.3 boost for preferred artists
- fetch artist preference sets in _replenishQueue
- pass artist keys to all candidate collection functions
- skip candidates with -Infinity scores

* feat: add /autoplay artist subcommand for managing artist preferences

- Add handleAutoplayArtist handler with prefer, block, remove, list subcommands
- Integrate with recommendationFeedbackService for artist preference storage
- Display preferred/blocked artists with emoji indicators and counts
- List subcommand shows all preferred and blocked artists for user

* test(bot): add artist preference tests for feedback and autoplay

* test(bot): add artist key mocks and fix conflict resolution

* fix(bot): add missing artist feedback methods and test coverage

* test(bot): cover artist handler error paths and default case

This branch was successfully deployed

1 active deployment
Preview — 304bf59e Deployed Apr 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot dependencies Pull requests that update a dependency file size/l size/xl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant