@@ -28,13 +28,15 @@ import {
2828 buildHowMessage ,
2929 buildSearchMessage ,
3030 buildSetlistMessage ,
31+ formatPathList ,
3132 getActiveRequestLimit ,
3233 getArraySetting ,
3334 getRateLimitWindow ,
3435 getRequiredPathsMatchMode ,
3536 getRequiredPathsWarning ,
3637 isRequesterAllowed ,
3738 isSongAllowed ,
39+ songMatchesRequestedPaths ,
3840} from "~/lib/request-policy" ;
3941import type { NormalizedChatEvent , ParsedChatCommand } from "~/lib/requests" ;
4042import type { SongSearchResult } from "~/lib/song-search/types" ;
@@ -324,6 +326,11 @@ function getRejectedSongMessage(input: {
324326 } `;
325327}
326328
329+ function getRequestedPathMismatchMessage ( requestedPaths : string [ ] ) {
330+ const formattedPaths = formatPathList ( requestedPaths ) ;
331+ return `That song does not include the requested path${ requestedPaths . length === 1 ? "" : "s" } : ${ formattedPaths } .` ;
332+ }
333+
327334function extractRequestedSourceSongId ( query : string | undefined ) {
328335 const match = / ^ s o n g : ( \d + ) $ / i. exec ( ( query ?? "" ) . trim ( ) ) ;
329336 if ( ! match ) {
@@ -408,8 +415,9 @@ function getSongAllowance(input: {
408415 state : EventSubChatState ;
409416 requesterContext : Parameters < typeof isRequesterAllowed > [ 1 ] ;
410417 allowBlacklistOverride : boolean ;
418+ requestedPaths : string [ ] ;
411419} ) {
412- return isSongAllowed ( {
420+ const policyAllowance = isSongAllowed ( {
413421 song : input . song ,
414422 settings : input . state . settings ,
415423 blacklistArtists : input . state . blacklistArtists ,
@@ -420,6 +428,26 @@ function getSongAllowance(input: {
420428 requester : input . requesterContext ,
421429 allowBlacklistOverride : input . allowBlacklistOverride ,
422430 } ) ;
431+
432+ if ( ! policyAllowance . allowed ) {
433+ return policyAllowance ;
434+ }
435+
436+ if (
437+ input . requestedPaths . length > 0 &&
438+ ! songMatchesRequestedPaths ( {
439+ song : input . song ,
440+ requestedPaths : input . requestedPaths ,
441+ } )
442+ ) {
443+ return {
444+ allowed : false ,
445+ reason : getRequestedPathMismatchMessage ( input . requestedPaths ) ,
446+ reasonCode : "requested_paths_not_matched" ,
447+ } ;
448+ }
449+
450+ return policyAllowance ;
423451}
424452
425453async function resolveChatRandomMatch ( input : {
@@ -429,6 +457,7 @@ async function resolveChatRandomMatch(input: {
429457 state : EventSubChatState ;
430458 requesterContext : Parameters < typeof isRequesterAllowed > [ 1 ] ;
431459 allowBlacklistOverride : boolean ;
460+ requestedPaths : string [ ] ;
432461} ) {
433462 const baseSearchInput = buildCatalogSearchInput ( {
434463 query : input . query ,
@@ -477,6 +506,7 @@ async function resolveChatRandomMatch(input: {
477506 state : input . state ,
478507 requesterContext : input . requesterContext ,
479508 allowBlacklistOverride : input . allowBlacklistOverride ,
509+ requestedPaths : input . requestedPaths ,
480510 } ) ;
481511 if ( songAllowed . allowed ) {
482512 return { firstMatch : candidate , firstRejectedMatch : undefined } ;
@@ -505,6 +535,7 @@ async function resolveChatRandomMatch(input: {
505535 state : input . state ,
506536 requesterContext : input . requesterContext ,
507537 allowBlacklistOverride : input . allowBlacklistOverride ,
538+ requestedPaths : input . requestedPaths ,
508539 } ) . allowed
509540 ) ;
510541
@@ -530,6 +561,7 @@ async function resolveChatRandomMatch(input: {
530561 state : input . state ,
531562 requesterContext : input . requesterContext ,
532563 allowBlacklistOverride : input . allowBlacklistOverride ,
564+ requestedPaths : input . requestedPaths ,
533565 } ) ;
534566 if ( ! songAllowed . allowed && ! firstRejectedMatch ) {
535567 firstRejectedMatch = {
@@ -555,6 +587,7 @@ async function resolveChatChoiceAvailability(input: {
555587 state : EventSubChatState ;
556588 requesterContext : Parameters < typeof isRequesterAllowed > [ 1 ] ;
557589 allowBlacklistOverride : boolean ;
590+ requestedPaths : string [ ] ;
558591} ) {
559592 const filteredSearch = await input . deps . searchSongs (
560593 input . env ,
@@ -596,6 +629,7 @@ async function resolveChatChoiceAvailability(input: {
596629 state : input . state ,
597630 requesterContext : input . requesterContext ,
598631 allowBlacklistOverride : input . allowBlacklistOverride ,
632+ requestedPaths : input . requestedPaths ,
599633 } ) ;
600634
601635 if ( songAllowed . allowed ) {
@@ -957,6 +991,7 @@ export async function processEventSubChatMessage(input: {
957991 commandPrefix : state . settings . commandPrefix ,
958992 appUrl : env . APP_URL ,
959993 channelSlug : channel . slug ,
994+ allowRequestPathModifiers : state . settings . allowRequestPathModifiers ,
960995 } ) ,
961996 } ) ;
962997 return { body : "Accepted" , status : 202 } ;
@@ -1153,10 +1188,13 @@ export async function processEventSubChatMessage(input: {
11531188 return { body : "Rejected" , status : 202 } ;
11541189 }
11551190
1156- const parsedRequest = parseRequestModifiers ( parsed . query ?. trim ( ) ?? "" ) ;
1191+ const parsedRequest = parseRequestModifiers ( parsed . query ?. trim ( ) ?? "" , {
1192+ allowPathModifiers : state . settings . allowRequestPathModifiers ,
1193+ } ) ;
11571194 const requestMode = parsedRequest . mode ;
11581195 const normalizedQuery = parsedRequest . query ;
11591196 const unmatchedQuery = normalizedQuery ;
1197+ const requestedPaths = parsedRequest . requestedPaths ;
11601198
11611199 if ( ! normalizedQuery ) {
11621200 await deps . createRequestLog ( env , {
@@ -1201,6 +1239,7 @@ export async function processEventSubChatMessage(input: {
12011239 state,
12021240 requesterContext,
12031241 allowBlacklistOverride,
1242+ requestedPaths,
12041243 } ) ;
12051244
12061245 if (
@@ -1217,6 +1256,7 @@ export async function processEventSubChatMessage(input: {
12171256 state,
12181257 requesterContext,
12191258 allowBlacklistOverride,
1259+ requestedPaths,
12201260 } ) ;
12211261 firstMatch = randomMatch . firstMatch ;
12221262 firstRejectedMatch = randomMatch . firstRejectedMatch ;
@@ -1233,29 +1273,27 @@ export async function processEventSubChatMessage(input: {
12331273 } ) ;
12341274 candidateMatchesJson = buildCandidateMatchesJson ( search . results ) ;
12351275 for ( const result of search . results ) {
1236- const songAllowed = isSongAllowed ( {
1276+ const effectiveSongAllowance = getSongAllowance ( {
12371277 song : result ,
1238- settings : state . settings ,
1239- blacklistArtists : state . blacklistArtists ,
1240- blacklistCharters : state . blacklistCharters ,
1241- blacklistSongs : state . blacklistSongs ,
1242- blacklistSongGroups : state . blacklistSongGroups ,
1243- setlistArtists : state . setlistArtists ,
1244- requester : requesterContext ,
1278+ state,
1279+ requesterContext,
12451280 allowBlacklistOverride,
1281+ requestedPaths,
12461282 } ) ;
12471283
1248- if ( songAllowed . allowed ) {
1284+ if ( effectiveSongAllowance . allowed ) {
12491285 firstMatch = result ;
12501286 break ;
12511287 }
12521288
12531289 if ( ! firstRejectedMatch ) {
12541290 firstRejectedMatch = {
12551291 song : result ,
1256- reason : songAllowed . reason ,
1292+ reason : effectiveSongAllowance . reason ,
12571293 reasonCode :
1258- "reasonCode" in songAllowed ? songAllowed . reasonCode : undefined ,
1294+ "reasonCode" in effectiveSongAllowance
1295+ ? effectiveSongAllowance . reasonCode
1296+ : undefined ,
12591297 } ;
12601298 }
12611299 }
@@ -1285,6 +1323,28 @@ export async function processEventSubChatMessage(input: {
12851323 return { body : "Lookup failed" , status : 202 } ;
12861324 }
12871325
1326+ if ( firstMatch ) {
1327+ const firstMatchAllowance = getSongAllowance ( {
1328+ song : firstMatch ,
1329+ state,
1330+ requesterContext,
1331+ allowBlacklistOverride,
1332+ requestedPaths,
1333+ } ) ;
1334+
1335+ if ( ! firstMatchAllowance . allowed ) {
1336+ firstRejectedMatch ??= {
1337+ song : firstMatch ,
1338+ reason : firstMatchAllowance . reason ,
1339+ reasonCode :
1340+ "reasonCode" in firstMatchAllowance
1341+ ? firstMatchAllowance . reasonCode
1342+ : undefined ,
1343+ } ;
1344+ firstMatch = null ;
1345+ }
1346+ }
1347+
12881348 let warningCode : string | undefined ;
12891349 let warningMessage : string | undefined ;
12901350
@@ -1370,16 +1430,12 @@ export async function processEventSubChatMessage(input: {
13701430 }
13711431
13721432 if ( firstMatch ) {
1373- const songAllowed = isSongAllowed ( {
1433+ const songAllowed = getSongAllowance ( {
13741434 song : firstMatch ,
1375- settings : state . settings ,
1376- blacklistArtists : state . blacklistArtists ,
1377- blacklistCharters : state . blacklistCharters ,
1378- blacklistSongs : state . blacklistSongs ,
1379- blacklistSongGroups : state . blacklistSongGroups ,
1380- setlistArtists : state . setlistArtists ,
1381- requester : requesterContext ,
1435+ state,
1436+ requesterContext,
13821437 allowBlacklistOverride,
1438+ requestedPaths,
13831439 } ) ;
13841440
13851441 if ( ! songAllowed . allowed ) {
0 commit comments