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
8 changes: 8 additions & 0 deletions drizzle/0008_blacklisted_charters.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `blacklisted_charters` (
`channel_id` text NOT NULL,
`charter_id` integer NOT NULL,
`charter_name` text NOT NULL,
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
PRIMARY KEY(`channel_id`, `charter_id`),
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
);
29 changes: 28 additions & 1 deletion src/components/blacklist-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ export type BlacklistedSongItem = {
artistName?: string | null;
};

export type BlacklistedCharterItem = {
charterId: number;
charterName: string;
};

export function BlacklistPanel(props: {
title?: string;
description?: string;
artists: BlacklistedArtistItem[];
charters?: BlacklistedCharterItem[];
songs: BlacklistedSongItem[];
collapsible?: boolean;
defaultOpen?: boolean;
}) {
const content = (
<CardContent className="grid items-start gap-6 md:grid-cols-2">
<CardContent className="grid items-start gap-6 lg:grid-cols-3">
<div className="grid content-start gap-3 self-start">
{props.artists.length > 0 ? (
<div className="overflow-hidden rounded-[20px] border border-(--border)">
Expand All @@ -43,6 +49,27 @@ export function BlacklistPanel(props: {
)}
</div>

<div className="grid content-start gap-3 self-start">
{(props.charters ?? []).length > 0 ? (
<div className="overflow-hidden rounded-[20px] border border-(--border)">
{(props.charters ?? []).map((charter, index) => (
<div
key={charter.charterId}
className={`px-4 py-2.5 text-sm ${
index % 2 === 0 ? "bg-(--panel-soft)" : "bg-(--panel-muted)"
}`}
>
<p className="truncate text-(--text)">
{charter.charterName} ({charter.charterId})
</p>
</div>
))}
</div>
) : (
<p className="text-sm text-(--muted)">No blacklisted charters.</p>
)}
</div>

<div className="grid content-start gap-3 self-start">
{props.songs.length > 0 ? (
<div className="overflow-hidden rounded-[20px] border border-(--border)">
Expand Down
2 changes: 1 addition & 1 deletion src/lib/db/latest-migration.generated.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const LATEST_MIGRATION_NAME = "0007_setlist_artist_ids.sql";
export const LATEST_MIGRATION_NAME = "0008_blacklisted_charters.sql";
94 changes: 89 additions & 5 deletions src/lib/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import {
type AuditLogInsert,
auditLogs,
type BlacklistedArtistInsert,
type BlacklistedCharterInsert,
type BlacklistedSongInsert,
blacklistedArtists,
blacklistedCharters,
blacklistedSongs,
blockedUsers,
type CatalogSongInsert,
Expand Down Expand Up @@ -329,6 +331,7 @@ export async function getDashboardState(env: AppEnv, ownerUserId: string) {
audits,
blacklistArtistsRows,
blacklistSongsRows,
blacklistCharterRows,
setlistArtistRows,
vipTokenRows,
playedRows,
Expand Down Expand Up @@ -368,6 +371,10 @@ export async function getDashboardState(env: AppEnv, ownerUserId: string) {
asc(blacklistedSongs.artistName),
],
}),
db.query.blacklistedCharters.findMany({
where: eq(blacklistedCharters.channelId, channel.id),
orderBy: [asc(blacklistedCharters.charterName)],
}),
db.query.setlistArtists.findMany({
where: eq(setlistArtists.channelId, channel.id),
orderBy: [asc(setlistArtists.artistName)],
Expand All @@ -394,6 +401,7 @@ export async function getDashboardState(env: AppEnv, ownerUserId: string) {
logs,
audits,
blacklistArtists: blacklistArtistsRows,
blacklistCharters: blacklistCharterRows,
blacklistSongs: blacklistSongsRows,
setlistArtists: setlistArtistRows,
vipTokens: vipTokenRows,
Expand Down Expand Up @@ -472,11 +480,15 @@ export async function getChannelBlacklistByChannelId(
env: AppEnv,
channelId: string
) {
const [artistRows, songRows] = await Promise.all([
const [artistRows, charterRows, songRows] = await Promise.all([
getDb(env).query.blacklistedArtists.findMany({
where: eq(blacklistedArtists.channelId, channelId),
orderBy: [asc(blacklistedArtists.artistName)],
}),
getDb(env).query.blacklistedCharters.findMany({
where: eq(blacklistedCharters.channelId, channelId),
orderBy: [asc(blacklistedCharters.charterName)],
}),
getDb(env).query.blacklistedSongs.findMany({
where: eq(blacklistedSongs.channelId, channelId),
orderBy: [
Expand All @@ -488,6 +500,7 @@ export async function getChannelBlacklistByChannelId(

return {
blacklistArtists: artistRows,
blacklistCharters: charterRows,
blacklistSongs: songRows,
};
}
Expand Down Expand Up @@ -1155,6 +1168,7 @@ export async function searchCatalogSongs(
id: string;
sourceSongId: number;
artistId: number | null;
authorId: number | null;
title: string;
artistName: string;
albumName: string | null;
Expand All @@ -1178,10 +1192,11 @@ export async function searchCatalogSongs(
}
)
SELECT
catalog_songs.id,
catalog_songs.source_song_id AS sourceSongId,
catalog_songs.artist_id AS artistId,
catalog_songs.title,
catalog_songs.id,
catalog_songs.source_song_id AS sourceSongId,
catalog_songs.artist_id AS artistId,
catalog_songs.author_id AS authorId,
catalog_songs.title,
catalog_songs.artist_name AS artistName,
catalog_songs.album_name AS albumName,
catalog_songs.creator_name AS creatorName,
Expand Down Expand Up @@ -1209,6 +1224,7 @@ export async function searchCatalogSongs(
results: resultRows.map((row) => ({
id: row.id,
artistId: row.artistId ?? undefined,
authorId: row.authorId ?? undefined,
title: decodeHtmlEntities(row.title),
artist: decodeHtmlEntities(row.artistName),
album: row.albumName ? decodeHtmlEntities(row.albumName) : undefined,
Expand Down Expand Up @@ -1253,6 +1269,7 @@ export async function getCatalogSongBySourceId(
return {
id: row.id,
artistId: row.artistId ?? undefined,
authorId: row.authorId ?? undefined,
title: decodeHtmlEntities(row.title),
artist: decodeHtmlEntities(row.artistName),
album: row.albumName ? decodeHtmlEntities(row.albumName) : undefined,
Expand Down Expand Up @@ -1358,6 +1375,48 @@ export async function searchCatalogSongsForBlacklist(
}));
}

export async function searchCatalogChartersForBlacklist(
env: AppEnv,
input: {
query: string;
limit?: number;
}
) {
const normalizedQuery = escapeLikeValue(input.query.toLowerCase());
if (normalizedQuery.length < 2) {
return [];
}

const rows = await getDb(env).all<{
charterId: number;
charterName: string;
trackCount: number;
}>(sql`
SELECT
author_id AS charterId,
creator_name AS charterName,
COUNT(*) AS trackCount
FROM catalog_songs
WHERE author_id IS NOT NULL
AND trim(coalesce(creator_name, '')) != ''
AND lower(creator_name) LIKE ${`%${normalizedQuery}%`}
GROUP BY author_id, creator_name
ORDER BY
CASE
WHEN lower(creator_name) LIKE ${`${normalizedQuery}%`} THEN 0
ELSE 1
END,
creator_name ASC
LIMIT ${Math.min(Math.max(input.limit ?? 8, 1), 25)}
`);

return unwrapD1Rows(rows).map((row) => ({
charterId: row.charterId,
charterName: decodeHtmlEntities(row.charterName),
trackCount: row.trackCount,
}));
}

export async function getCatalogSongsByIds(env: AppEnv, songIds: string[]) {
const uniqueIds = [...new Set(songIds.filter(Boolean))];
if (uniqueIds.length === 0) {
Expand Down Expand Up @@ -1967,6 +2026,16 @@ export async function addBlacklistedSong(
await getDb(env).insert(blacklistedSongs).values(input).onConflictDoNothing();
}

export async function addBlacklistedCharter(
env: AppEnv,
input: Omit<BlacklistedCharterInsert, "createdAt">
) {
await getDb(env)
.insert(blacklistedCharters)
.values(input)
.onConflictDoNothing();
}

export async function removeBlacklistedSong(
env: AppEnv,
channelId: string,
Expand All @@ -1982,6 +2051,21 @@ export async function removeBlacklistedSong(
);
}

export async function removeBlacklistedCharter(
env: AppEnv,
channelId: string,
charterId: number
) {
await getDb(env)
.delete(blacklistedCharters)
.where(
and(
eq(blacklistedCharters.channelId, channelId),
eq(blacklistedCharters.charterId, charterId)
)
);
}

export async function addSetlistArtist(
env: AppEnv,
input: Omit<SetlistArtistInsert, "createdAt">
Expand Down
16 changes: 16 additions & 0 deletions src/lib/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,21 @@ export const blacklistedSongs = sqliteTable(
(table) => [primaryKey({ columns: [table.channelId, table.songId] })]
);

export const blacklistedCharters = sqliteTable(
"blacklisted_charters",
{
channelId: text("channel_id")
.notNull()
.references(() => channels.id),
charterId: integer("charter_id").notNull(),
charterName: text("charter_name").notNull(),
createdAt: integer("created_at")
.notNull()
.default(sql`(unixepoch() * 1000)`),
},
(table) => [primaryKey({ columns: [table.channelId, table.charterId] })]
);

export const setlistArtists = sqliteTable(
"setlist_artists",
{
Expand Down Expand Up @@ -663,6 +678,7 @@ export type PlaylistItemInsert = typeof playlistItems.$inferInsert;
export type BlockedUserInsert = typeof blockedUsers.$inferInsert;
export type BlacklistedArtistInsert = typeof blacklistedArtists.$inferInsert;
export type BlacklistedSongInsert = typeof blacklistedSongs.$inferInsert;
export type BlacklistedCharterInsert = typeof blacklistedCharters.$inferInsert;
export type SetlistArtistInsert = typeof setlistArtists.$inferInsert;
export type VipTokenInsert = typeof vipTokens.$inferInsert;
export type RequestLogInsert = typeof requestLogs.$inferInsert;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/eventsub/chat-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface EventSubChatSettings {
export interface EventSubChatState {
settings: EventSubChatSettings & Parameters<typeof isRequesterAllowed>[0];
blacklistArtists: Array<{ artistId: number; artistName: string }>;
blacklistCharters: Array<{ charterId: number; charterName: string }>;
blacklistSongs: Array<{
songId: number;
songTitle: string;
Expand Down Expand Up @@ -440,6 +441,7 @@ export async function processEventSubChatMessage(input: {
commandPrefix: state.settings.commandPrefix,
appUrl: env.APP_URL,
blacklistArtists: state.blacklistArtists,
blacklistCharters: state.blacklistCharters,
blacklistSongs: state.blacklistSongs,
setlistArtists: state.setlistArtists,
}),
Expand All @@ -462,6 +464,7 @@ export async function processEventSubChatMessage(input: {
broadcasterUserId: channel.twitchChannelId,
message: buildBlacklistMessage(
state.blacklistArtists,
state.blacklistCharters,
state.blacklistSongs
),
});
Expand Down Expand Up @@ -698,6 +701,7 @@ export async function processEventSubChatMessage(input: {
song: firstMatch,
settings: state.settings,
blacklistArtists: state.blacklistArtists,
blacklistCharters: state.blacklistCharters,
blacklistSongs: state.blacklistSongs,
setlistArtists: state.setlistArtists,
requester: requesterContext,
Expand Down
Loading
Loading