Skip to content

Commit 29f02d2

Browse files
Merge pull request #29 from Jamesllllllllll/codex/issue-5-charter-blacklist
Add charter blacklisting
2 parents f3815aa + 53d9dfe commit 29f02d2

File tree

19 files changed

+434
-19
lines changed

19 files changed

+434
-19
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE IF NOT EXISTS `blacklisted_charters` (
2+
`channel_id` text NOT NULL,
3+
`charter_id` integer NOT NULL,
4+
`charter_name` text NOT NULL,
5+
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
6+
PRIMARY KEY(`channel_id`, `charter_id`),
7+
FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE no action
8+
);

src/components/blacklist-panel.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@ export type BlacklistedSongItem = {
1212
artistName?: string | null;
1313
};
1414

15+
export type BlacklistedCharterItem = {
16+
charterId: number;
17+
charterName: string;
18+
};
19+
1520
export function BlacklistPanel(props: {
1621
title?: string;
1722
description?: string;
1823
artists: BlacklistedArtistItem[];
24+
charters?: BlacklistedCharterItem[];
1925
songs: BlacklistedSongItem[];
2026
collapsible?: boolean;
2127
defaultOpen?: boolean;
2228
}) {
2329
const content = (
24-
<CardContent className="grid items-start gap-6 md:grid-cols-2">
30+
<CardContent className="grid items-start gap-6 lg:grid-cols-3">
2531
<div className="grid content-start gap-3 self-start">
2632
{props.artists.length > 0 ? (
2733
<div className="overflow-hidden rounded-[20px] border border-(--border)">
@@ -43,6 +49,27 @@ export function BlacklistPanel(props: {
4349
)}
4450
</div>
4551

52+
<div className="grid content-start gap-3 self-start">
53+
{(props.charters ?? []).length > 0 ? (
54+
<div className="overflow-hidden rounded-[20px] border border-(--border)">
55+
{(props.charters ?? []).map((charter, index) => (
56+
<div
57+
key={charter.charterId}
58+
className={`px-4 py-2.5 text-sm ${
59+
index % 2 === 0 ? "bg-(--panel-soft)" : "bg-(--panel-muted)"
60+
}`}
61+
>
62+
<p className="truncate text-(--text)">
63+
{charter.charterName} ({charter.charterId})
64+
</p>
65+
</div>
66+
))}
67+
</div>
68+
) : (
69+
<p className="text-sm text-(--muted)">No blacklisted charters.</p>
70+
)}
71+
</div>
72+
4673
<div className="grid content-start gap-3 self-start">
4774
{props.songs.length > 0 ? (
4875
<div className="overflow-hidden rounded-[20px] border border-(--border)">
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const LATEST_MIGRATION_NAME = "0007_setlist_artist_ids.sql";
1+
export const LATEST_MIGRATION_NAME = "0008_blacklisted_charters.sql";

src/lib/db/repositories.ts

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import {
2020
type AuditLogInsert,
2121
auditLogs,
2222
type BlacklistedArtistInsert,
23+
type BlacklistedCharterInsert,
2324
type BlacklistedSongInsert,
2425
blacklistedArtists,
26+
blacklistedCharters,
2527
blacklistedSongs,
2628
blockedUsers,
2729
type CatalogSongInsert,
@@ -329,6 +331,7 @@ export async function getDashboardState(env: AppEnv, ownerUserId: string) {
329331
audits,
330332
blacklistArtistsRows,
331333
blacklistSongsRows,
334+
blacklistCharterRows,
332335
setlistArtistRows,
333336
vipTokenRows,
334337
playedRows,
@@ -368,6 +371,10 @@ export async function getDashboardState(env: AppEnv, ownerUserId: string) {
368371
asc(blacklistedSongs.artistName),
369372
],
370373
}),
374+
db.query.blacklistedCharters.findMany({
375+
where: eq(blacklistedCharters.channelId, channel.id),
376+
orderBy: [asc(blacklistedCharters.charterName)],
377+
}),
371378
db.query.setlistArtists.findMany({
372379
where: eq(setlistArtists.channelId, channel.id),
373380
orderBy: [asc(setlistArtists.artistName)],
@@ -394,6 +401,7 @@ export async function getDashboardState(env: AppEnv, ownerUserId: string) {
394401
logs,
395402
audits,
396403
blacklistArtists: blacklistArtistsRows,
404+
blacklistCharters: blacklistCharterRows,
397405
blacklistSongs: blacklistSongsRows,
398406
setlistArtists: setlistArtistRows,
399407
vipTokens: vipTokenRows,
@@ -472,11 +480,15 @@ export async function getChannelBlacklistByChannelId(
472480
env: AppEnv,
473481
channelId: string
474482
) {
475-
const [artistRows, songRows] = await Promise.all([
483+
const [artistRows, charterRows, songRows] = await Promise.all([
476484
getDb(env).query.blacklistedArtists.findMany({
477485
where: eq(blacklistedArtists.channelId, channelId),
478486
orderBy: [asc(blacklistedArtists.artistName)],
479487
}),
488+
getDb(env).query.blacklistedCharters.findMany({
489+
where: eq(blacklistedCharters.channelId, channelId),
490+
orderBy: [asc(blacklistedCharters.charterName)],
491+
}),
480492
getDb(env).query.blacklistedSongs.findMany({
481493
where: eq(blacklistedSongs.channelId, channelId),
482494
orderBy: [
@@ -488,6 +500,7 @@ export async function getChannelBlacklistByChannelId(
488500

489501
return {
490502
blacklistArtists: artistRows,
503+
blacklistCharters: charterRows,
491504
blacklistSongs: songRows,
492505
};
493506
}
@@ -1155,6 +1168,7 @@ export async function searchCatalogSongs(
11551168
id: string;
11561169
sourceSongId: number;
11571170
artistId: number | null;
1171+
authorId: number | null;
11581172
title: string;
11591173
artistName: string;
11601174
albumName: string | null;
@@ -1178,10 +1192,11 @@ export async function searchCatalogSongs(
11781192
}
11791193
)
11801194
SELECT
1181-
catalog_songs.id,
1182-
catalog_songs.source_song_id AS sourceSongId,
1183-
catalog_songs.artist_id AS artistId,
1184-
catalog_songs.title,
1195+
catalog_songs.id,
1196+
catalog_songs.source_song_id AS sourceSongId,
1197+
catalog_songs.artist_id AS artistId,
1198+
catalog_songs.author_id AS authorId,
1199+
catalog_songs.title,
11851200
catalog_songs.artist_name AS artistName,
11861201
catalog_songs.album_name AS albumName,
11871202
catalog_songs.creator_name AS creatorName,
@@ -1209,6 +1224,7 @@ export async function searchCatalogSongs(
12091224
results: resultRows.map((row) => ({
12101225
id: row.id,
12111226
artistId: row.artistId ?? undefined,
1227+
authorId: row.authorId ?? undefined,
12121228
title: decodeHtmlEntities(row.title),
12131229
artist: decodeHtmlEntities(row.artistName),
12141230
album: row.albumName ? decodeHtmlEntities(row.albumName) : undefined,
@@ -1253,6 +1269,7 @@ export async function getCatalogSongBySourceId(
12531269
return {
12541270
id: row.id,
12551271
artistId: row.artistId ?? undefined,
1272+
authorId: row.authorId ?? undefined,
12561273
title: decodeHtmlEntities(row.title),
12571274
artist: decodeHtmlEntities(row.artistName),
12581275
album: row.albumName ? decodeHtmlEntities(row.albumName) : undefined,
@@ -1358,6 +1375,48 @@ export async function searchCatalogSongsForBlacklist(
13581375
}));
13591376
}
13601377

1378+
export async function searchCatalogChartersForBlacklist(
1379+
env: AppEnv,
1380+
input: {
1381+
query: string;
1382+
limit?: number;
1383+
}
1384+
) {
1385+
const normalizedQuery = escapeLikeValue(input.query.toLowerCase());
1386+
if (normalizedQuery.length < 2) {
1387+
return [];
1388+
}
1389+
1390+
const rows = await getDb(env).all<{
1391+
charterId: number;
1392+
charterName: string;
1393+
trackCount: number;
1394+
}>(sql`
1395+
SELECT
1396+
author_id AS charterId,
1397+
creator_name AS charterName,
1398+
COUNT(*) AS trackCount
1399+
FROM catalog_songs
1400+
WHERE author_id IS NOT NULL
1401+
AND trim(coalesce(creator_name, '')) != ''
1402+
AND lower(creator_name) LIKE ${`%${normalizedQuery}%`}
1403+
GROUP BY author_id, creator_name
1404+
ORDER BY
1405+
CASE
1406+
WHEN lower(creator_name) LIKE ${`${normalizedQuery}%`} THEN 0
1407+
ELSE 1
1408+
END,
1409+
creator_name ASC
1410+
LIMIT ${Math.min(Math.max(input.limit ?? 8, 1), 25)}
1411+
`);
1412+
1413+
return unwrapD1Rows(rows).map((row) => ({
1414+
charterId: row.charterId,
1415+
charterName: decodeHtmlEntities(row.charterName),
1416+
trackCount: row.trackCount,
1417+
}));
1418+
}
1419+
13611420
export async function getCatalogSongsByIds(env: AppEnv, songIds: string[]) {
13621421
const uniqueIds = [...new Set(songIds.filter(Boolean))];
13631422
if (uniqueIds.length === 0) {
@@ -1967,6 +2026,16 @@ export async function addBlacklistedSong(
19672026
await getDb(env).insert(blacklistedSongs).values(input).onConflictDoNothing();
19682027
}
19692028

2029+
export async function addBlacklistedCharter(
2030+
env: AppEnv,
2031+
input: Omit<BlacklistedCharterInsert, "createdAt">
2032+
) {
2033+
await getDb(env)
2034+
.insert(blacklistedCharters)
2035+
.values(input)
2036+
.onConflictDoNothing();
2037+
}
2038+
19702039
export async function removeBlacklistedSong(
19712040
env: AppEnv,
19722041
channelId: string,
@@ -1982,6 +2051,21 @@ export async function removeBlacklistedSong(
19822051
);
19832052
}
19842053

2054+
export async function removeBlacklistedCharter(
2055+
env: AppEnv,
2056+
channelId: string,
2057+
charterId: number
2058+
) {
2059+
await getDb(env)
2060+
.delete(blacklistedCharters)
2061+
.where(
2062+
and(
2063+
eq(blacklistedCharters.channelId, channelId),
2064+
eq(blacklistedCharters.charterId, charterId)
2065+
)
2066+
);
2067+
}
2068+
19852069
export async function addSetlistArtist(
19862070
env: AppEnv,
19872071
input: Omit<SetlistArtistInsert, "createdAt">

src/lib/db/schema.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,21 @@ export const blacklistedSongs = sqliteTable(
318318
(table) => [primaryKey({ columns: [table.channelId, table.songId] })]
319319
);
320320

321+
export const blacklistedCharters = sqliteTable(
322+
"blacklisted_charters",
323+
{
324+
channelId: text("channel_id")
325+
.notNull()
326+
.references(() => channels.id),
327+
charterId: integer("charter_id").notNull(),
328+
charterName: text("charter_name").notNull(),
329+
createdAt: integer("created_at")
330+
.notNull()
331+
.default(sql`(unixepoch() * 1000)`),
332+
},
333+
(table) => [primaryKey({ columns: [table.channelId, table.charterId] })]
334+
);
335+
321336
export const setlistArtists = sqliteTable(
322337
"setlist_artists",
323338
{
@@ -663,6 +678,7 @@ export type PlaylistItemInsert = typeof playlistItems.$inferInsert;
663678
export type BlockedUserInsert = typeof blockedUsers.$inferInsert;
664679
export type BlacklistedArtistInsert = typeof blacklistedArtists.$inferInsert;
665680
export type BlacklistedSongInsert = typeof blacklistedSongs.$inferInsert;
681+
export type BlacklistedCharterInsert = typeof blacklistedCharters.$inferInsert;
666682
export type SetlistArtistInsert = typeof setlistArtists.$inferInsert;
667683
export type VipTokenInsert = typeof vipTokens.$inferInsert;
668684
export type RequestLogInsert = typeof requestLogs.$inferInsert;

src/lib/eventsub/chat-message.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface EventSubChatSettings {
4848
export interface EventSubChatState {
4949
settings: EventSubChatSettings & Parameters<typeof isRequesterAllowed>[0];
5050
blacklistArtists: Array<{ artistId: number; artistName: string }>;
51+
blacklistCharters: Array<{ charterId: number; charterName: string }>;
5152
blacklistSongs: Array<{
5253
songId: number;
5354
songTitle: string;
@@ -440,6 +441,7 @@ export async function processEventSubChatMessage(input: {
440441
commandPrefix: state.settings.commandPrefix,
441442
appUrl: env.APP_URL,
442443
blacklistArtists: state.blacklistArtists,
444+
blacklistCharters: state.blacklistCharters,
443445
blacklistSongs: state.blacklistSongs,
444446
setlistArtists: state.setlistArtists,
445447
}),
@@ -462,6 +464,7 @@ export async function processEventSubChatMessage(input: {
462464
broadcasterUserId: channel.twitchChannelId,
463465
message: buildBlacklistMessage(
464466
state.blacklistArtists,
467+
state.blacklistCharters,
465468
state.blacklistSongs
466469
),
467470
});
@@ -698,6 +701,7 @@ export async function processEventSubChatMessage(input: {
698701
song: firstMatch,
699702
settings: state.settings,
700703
blacklistArtists: state.blacklistArtists,
704+
blacklistCharters: state.blacklistCharters,
701705
blacklistSongs: state.blacklistSongs,
702706
setlistArtists: state.setlistArtists,
703707
requester: requesterContext,

0 commit comments

Comments
 (0)