Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions src/lib/eventsub/chat-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ function buildCandidateMatchesJson(results: SongSearchResult[]) {
return JSON.stringify(
results.slice(0, 5).map((result) => ({
id: result.id,
authorId: result.authorId,
title: result.title,
artist: result.artist,
album: result.album,
Expand All @@ -223,6 +224,20 @@ function buildCandidateMatchesJson(results: SongSearchResult[]) {
);
}

function getRejectedSongMessage(input: {
login: string;
reason?: string;
reasonCode?: string;
}) {
if (input.reasonCode === "charter_blacklist") {
return `${mention(input.login)} this song cannot be played in this channel.`;
}

return `${mention(input.login)} ${
input.reason ?? "that song is not allowed in this channel."
}`;
}

function extractRequestedSourceSongId(query: string | undefined) {
const match = /^song:(\d+)$/i.exec((query ?? "").trim());
if (!match) {
Expand Down Expand Up @@ -644,6 +659,13 @@ export async function processEventSubChatMessage(input: {

let firstMatch: SongSearchResult | null = null;
let candidateMatchesJson: string | undefined;
let firstRejectedMatch:
| {
song: SongSearchResult;
reason?: string;
reasonCode?: string;
}
| undefined;
const normalizedQuery = parsed.query?.trim() ?? "";

try {
Expand All @@ -660,7 +682,31 @@ export async function processEventSubChatMessage(input: {
pageSize: 5,
});
candidateMatchesJson = buildCandidateMatchesJson(search.results);
firstMatch = search.results[0] ?? null;
for (const result of search.results) {
const songAllowed = isSongAllowed({
song: result,
settings: state.settings,
blacklistArtists: state.blacklistArtists,
blacklistCharters: state.blacklistCharters,
blacklistSongs: state.blacklistSongs,
setlistArtists: state.setlistArtists,
requester: requesterContext,
});

if (songAllowed.allowed) {
firstMatch = result;
break;
}

if (!firstRejectedMatch) {
firstRejectedMatch = {
song: result,
reason: songAllowed.reason,
reasonCode:
"reasonCode" in songAllowed ? songAllowed.reasonCode : undefined,
};
}
}
}
} catch (error) {
console.error("EventSub song lookup failed", {
Expand Down Expand Up @@ -692,6 +738,33 @@ export async function processEventSubChatMessage(input: {
let warningMessage: string | undefined;

if (!firstMatch) {
if (firstRejectedMatch) {
await deps.createRequestLog(env, {
channelId: channel.id,
twitchMessageId: event.messageId,
twitchUserId: requesterIdentity.twitchUserId,
requesterLogin: requesterIdentity.login,
requesterDisplayName: requesterIdentity.displayName,
rawMessage: event.rawMessage,
normalizedQuery: parsed.query,
matchedSongId: firstRejectedMatch.song.id,
matchedSongTitle: firstRejectedMatch.song.title,
matchedSongArtist: firstRejectedMatch.song.artist,
outcome: "rejected",
outcomeReason: firstRejectedMatch.reason,
});
await deps.sendChatReply(env, {
channelId: channel.id,
broadcasterUserId: channel.twitchChannelId,
message: getRejectedSongMessage({
login: requesterIdentity.login,
reason: firstRejectedMatch.reason,
reasonCode: firstRejectedMatch.reasonCode,
}),
});
return { body: "Rejected", status: 202 };
}

warningCode = "no_song_match";
warningMessage = `No matching track found for "${unmatchedQuery}".`;
}
Expand Down Expand Up @@ -725,7 +798,12 @@ export async function processEventSubChatMessage(input: {
await deps.sendChatReply(env, {
channelId: channel.id,
broadcasterUserId: channel.twitchChannelId,
message: `${mention(requesterIdentity.login)} ${songAllowed.reason ?? "that song is not allowed in this channel."}`,
message: getRejectedSongMessage({
login: requesterIdentity.login,
reason: songAllowed.reason,
reasonCode:
"reasonCode" in songAllowed ? songAllowed.reasonCode : undefined,
}),
});
return { body: "Rejected", status: 202 };
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/playlist/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface AddRequestInput {
song: {
id: string;
title: string;
authorId?: number;
artist?: string;
album?: string;
creator?: string;
Expand Down Expand Up @@ -97,6 +98,7 @@ export interface ManualAddInput {
song: {
id: string;
title: string;
authorId?: number;
artist?: string;
album?: string;
creator?: string;
Expand Down
3 changes: 3 additions & 0 deletions src/lib/request-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export function isSongAllowed(input: {
return {
allowed: false,
reason: "That song's tuning is not allowed in this channel.",
reasonCode: "disallowed_tuning",
};
}
}
Expand All @@ -205,6 +206,7 @@ export function isSongAllowed(input: {
return {
allowed: false,
reason: "That artist is not in the current setlist.",
reasonCode: "artist_not_in_setlist",
};
}
}
Expand All @@ -230,6 +232,7 @@ export function isSongAllowed(input: {
reason: charterBlocked
? `${charterBlocked.charterName} is blacklisted in this channel.`
: "That song is blocked in this channel.",
reasonCode: charterBlocked ? "charter_blacklist" : "song_blacklist",
};
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export const playlistMutationSchema = z.discriminatedUnion("action", [
songId: z.string(),
requesterLogin: z.string().trim().min(2).max(25).optional(),
title: z.string().min(1),
authorId: z.number().optional(),
artist: z.string().optional(),
album: z.string().optional(),
creator: z.string().optional(),
Expand All @@ -272,5 +273,6 @@ export const playlistMutationSchema = z.discriminatedUnion("action", [
source: z.string(),
sourceUrl: z.string().optional(),
sourceId: z.number().optional(),
candidateMatchesJson: z.string().optional(),
}),
]);
Loading
Loading