Corner-case review of the #90 play-by-id parser found two reachable issues:
- A NetEase playlist/album/artist/toplist/djradio share URL (which reuses ?id=)
was matched as a SONG id, so pasting one into !play called getSongDetail() on
a collection id and returned a confusing 'No song found' instead of falling
back to a normal search. Guard the id= branch to exclude collection pages.
- The id: prefix captured trailing punctuation from a chat paste ('id:12345.' ->
'12345.'), which then failed to resolve. Strip trailing .,;)] from the id.
Both fall back to safe behavior (plain search / clean id). Tests added for
collection URLs and pasted ids with punctuation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
!play/!add/!playnext only ever searched with limit 1, so a same-name song could
never be reached from chat (e.g. 'Die For You' always returned the most popular
match, not The Weeknd's). Add three disambiguation paths via a shared resolver:
- !search <name> — list the top matches (numbered, with id), remembered per bot
- !play #N / !add #N — play/queue the Nth result of the last !search
- !play id:<id> and pasted NetEase/QQ/BiliBili song URLs — play an exact song
Pure parsing (parseSongRef / parseSelectionIndex) is unit-tested; plain-text
search keeps the historical top-hit behavior. WebUI search (20 results) already
allowed picking same-name songs and is unchanged.
Fixes#90
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>