@serifpersia/dango 3.6.7 → 3.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DKDJK8Mp.js} +1 -1
- package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
- package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
- package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
- package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
- package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
- package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
- package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
- package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
- package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
- package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
- package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
- package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
- package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
- package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
- package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
- package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
- package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
- package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
- package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
- package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
- package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
- package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
- package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
- package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
- package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
- package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
- package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
- package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
- package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
- package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
- package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
- package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
- package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
- package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
- package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +3 -5
- package/server/dist/config.js +1 -174
- package/server/dist/server.js +1068 -289
- package/server/dist/shipped-defaults.js +1 -6
- package/server/package.json +4 -5
- package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
- package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
- package/client/dist/assets/Music-BGH_0voi.css +0 -1
- package/client/dist/assets/Music-CQpyXTLH.js +0 -1
- package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
- package/client/dist/assets/Radio-DF6g6odd.js +0 -1
- package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
- package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
- package/server/dist/app-auth.js +0 -157
- package/server/dist/app-hono.js +0 -598
- package/server/dist/db/drizzle.js +0 -16
- package/server/dist/db/schema-anime.js +0 -61
- package/server/dist/db/schema-asmr.js +0 -13
- package/server/dist/db/schema-manga.js +0 -31
- package/server/dist/db/schema-tv.js +0 -18
- package/server/dist/db.js +0 -217
- package/server/dist/discord-gateway.js +0 -385
- package/server/dist/discord-rpc.js +0 -413
- package/server/dist/github-sync.js +0 -508
- package/server/dist/google.js +0 -741
- package/server/dist/hono/asmr.js +0 -10
- package/server/dist/hono/auth.js +0 -49
- package/server/dist/hono/music.js +0 -209
- package/server/dist/hono/proxy.js +0 -827
- package/server/dist/hono/tv.js +0 -200
- package/server/dist/hono/watchlist.js +0 -341
- package/server/dist/lib/anilist.js +0 -1583
- package/server/dist/lib/anischedule.js +0 -191
- package/server/dist/lib/insights.js +0 -185
- package/server/dist/lib/ipc.js +0 -23
- package/server/dist/lib/kitsu.js +0 -517
- package/server/dist/lib/mal-import.js +0 -291
- package/server/dist/lib/mal.js +0 -957
- package/server/dist/lib/migration.js +0 -310
- package/server/dist/lib/offline-db.js +0 -395
- package/server/dist/lib/potoken.js +0 -135
- package/server/dist/lib/recommendations.service.js +0 -216
- package/server/dist/lib/temp-ids.js +0 -3
- package/server/dist/lib/tmdb.js +0 -72
- package/server/dist/lib/tracker/anilist-tracker.js +0 -308
- package/server/dist/lib/tracker/manga-sync.service.js +0 -773
- package/server/dist/lib/tracker/sync-state.js +0 -21
- package/server/dist/lib/tracker/sync.service.js +0 -476
- package/server/dist/lib/translate.js +0 -79
- package/server/dist/lib/ump.js +0 -106
- package/server/dist/lib/ytmusic.js +0 -162
- package/server/dist/logger.js +0 -16
- package/server/dist/providers/manga/manga.types.js +0 -1
- package/server/dist/providers/provider.interface.js +0 -1
- package/server/dist/providers/remote-loader.js +0 -614
- package/server/dist/providers/remote-types.js +0 -159
- package/server/dist/providers/title-matching.js +0 -226
- package/server/dist/providers/tv.types.js +0 -1
- package/server/dist/rclone.js +0 -120
- package/server/dist/repositories/asmr.repository.js +0 -77
- package/server/dist/repositories/insights.repository.js +0 -106
- package/server/dist/repositories/library.repository.js +0 -94
- package/server/dist/repositories/mal-cache.repository.js +0 -52
- package/server/dist/repositories/manga.repository.js +0 -95
- package/server/dist/repositories/notifications.repository.js +0 -60
- package/server/dist/repositories/queue.repository.js +0 -72
- package/server/dist/repositories/settings.repository.js +0 -30
- package/server/dist/repositories/shows-meta.repository.js +0 -58
- package/server/dist/repositories/temp-show-ids.repository.js +0 -63
- package/server/dist/repositories/tv.repository.js +0 -93
- package/server/dist/repositories/watched-episodes.repository.js +0 -82
- package/server/dist/repositories/watchlist.repository.js +0 -60
- package/server/dist/scripts/backfill-episode-duration.js +0 -42
- package/server/dist/sync-payload.js +0 -91
- package/server/dist/sync.js +0 -657
- package/server/dist/trpc/context.js +0 -7
- package/server/dist/trpc/errors.js +0 -15
- package/server/dist/trpc/index.js +0 -18
- package/server/dist/trpc/router.js +0 -49
- package/server/dist/trpc/routers/asmr-library.js +0 -112
- package/server/dist/trpc/routers/asmr-progress.js +0 -121
- package/server/dist/trpc/routers/asmr.js +0 -87
- package/server/dist/trpc/routers/auth.js +0 -194
- package/server/dist/trpc/routers/continue-watching.js +0 -81
- package/server/dist/trpc/routers/data.js +0 -1026
- package/server/dist/trpc/routers/discord.js +0 -285
- package/server/dist/trpc/routers/discovery.js +0 -11
- package/server/dist/trpc/routers/insights.js +0 -14
- package/server/dist/trpc/routers/manga-library.js +0 -263
- package/server/dist/trpc/routers/manga-progress.js +0 -169
- package/server/dist/trpc/routers/manga.js +0 -201
- package/server/dist/trpc/routers/music.js +0 -655
- package/server/dist/trpc/routers/notifications.js +0 -80
- package/server/dist/trpc/routers/progress.js +0 -109
- package/server/dist/trpc/routers/queue.js +0 -93
- package/server/dist/trpc/routers/radio.js +0 -145
- package/server/dist/trpc/routers/settings.js +0 -96
- package/server/dist/trpc/routers/tracker.js +0 -213
- package/server/dist/trpc/routers/tv-library.js +0 -135
- package/server/dist/trpc/routers/tv-progress.js +0 -159
- package/server/dist/trpc/routers/tv.js +0 -551
- package/server/dist/trpc/routers/watchlist.js +0 -550
- package/server/dist/trpc/validation.js +0 -258
- package/server/dist/utils/cache.utils.js +0 -25
- package/server/dist/utils/cookie.utils.js +0 -13
- package/server/dist/utils/db-utils.js +0 -17
- package/server/dist/utils/env.utils.js +0 -77
- package/server/dist/utils/http.utils.js +0 -82
- package/server/dist/utils/request-context.js +0 -2
- package/server/dist/utils/security.utils.js +0 -76
- /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import logger from '../../logger.js';
|
|
3
|
-
import { performWriteTransactionAsync } from '../../sync.js';
|
|
4
|
-
import { WatchlistRepository } from '../../repositories/watchlist.repository.js';
|
|
5
|
-
import { WatchedEpisodesRepository } from '../../repositories/watched-episodes.repository.js';
|
|
6
|
-
import { NotificationsRepository } from '../../repositories/notifications.repository.js';
|
|
7
|
-
import { notificationDismissInput } from '../validation.js';
|
|
8
|
-
function clearAllInput() {
|
|
9
|
-
return {
|
|
10
|
-
'~standard': {
|
|
11
|
-
version: 1,
|
|
12
|
-
vendor: 'dango',
|
|
13
|
-
types: undefined,
|
|
14
|
-
validate(value) {
|
|
15
|
-
try {
|
|
16
|
-
if (value === undefined || value === null)
|
|
17
|
-
return { value: {} };
|
|
18
|
-
if (typeof value !== 'object' || value === null)
|
|
19
|
-
throw new Error('Expected an object');
|
|
20
|
-
const showId = value.showId;
|
|
21
|
-
if (showId === undefined || showId === null)
|
|
22
|
-
return { value: {} };
|
|
23
|
-
if (typeof showId !== 'string' || showId.length === 0)
|
|
24
|
-
throw new Error('showId must be a string');
|
|
25
|
-
return { value: { showId } };
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
return { issues: [{ message: err instanceof Error ? err.message : 'Invalid input' }] };
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export const notificationsRouter = router({
|
|
35
|
-
list: protectedProcedure.query(async ({ ctx }) => {
|
|
36
|
-
const watchingShows = await WatchlistRepository.getWatchingShows(ctx.db);
|
|
37
|
-
const notifications = [];
|
|
38
|
-
for (const show of watchingShows) {
|
|
39
|
-
try {
|
|
40
|
-
const [watchedEps, dismissedEps, discoveredEps] = await Promise.all([
|
|
41
|
-
WatchedEpisodesRepository.getWatchedEpisodeNumbers(ctx.db, show.id),
|
|
42
|
-
NotificationsRepository.getDismissedByShow(ctx.db, show.id),
|
|
43
|
-
NotificationsRepository.getDiscoveredByShow(ctx.db, show.id),
|
|
44
|
-
]);
|
|
45
|
-
const watchedSet = new Set(watchedEps.map((e) => e.toString()));
|
|
46
|
-
const dismissedSet = new Set(dismissedEps.map((e) => e.episodeNumber.toString()));
|
|
47
|
-
for (const discovered of discoveredEps) {
|
|
48
|
-
if (!watchedSet.has(discovered.episodeNumber) &&
|
|
49
|
-
!dismissedSet.has(discovered.episodeNumber)) {
|
|
50
|
-
notifications.push({
|
|
51
|
-
showId: show.id,
|
|
52
|
-
name: show.name,
|
|
53
|
-
nativeName: show.nativeName,
|
|
54
|
-
englishName: show.englishName,
|
|
55
|
-
thumbnail: show.thumbnail,
|
|
56
|
-
episodeNumber: discovered.episodeNumber,
|
|
57
|
-
id: `${show.id}-${discovered.episodeNumber}`,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch (e) {
|
|
63
|
-
logger.error({ err: e, showId: show.id }, 'Failed to get notifications for show');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return notifications.sort((a, b) => parseFloat(b.episodeNumber) - parseFloat(a.episodeNumber));
|
|
67
|
-
}),
|
|
68
|
-
dismiss: protectedProcedure.input(notificationDismissInput()).mutation(async ({ ctx, input }) => {
|
|
69
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
70
|
-
await NotificationsRepository.addDismissed(tx, input.showId, input.episodeNumber);
|
|
71
|
-
});
|
|
72
|
-
return { success: true };
|
|
73
|
-
}),
|
|
74
|
-
clearAll: protectedProcedure.input(clearAllInput()).mutation(async ({ ctx, input }) => {
|
|
75
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
76
|
-
await NotificationsRepository.dismissFromDiscovered(tx, input.showId);
|
|
77
|
-
});
|
|
78
|
-
return { success: true };
|
|
79
|
-
}),
|
|
80
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import { WatchedEpisodesRepository } from '../../repositories/watched-episodes.repository.js';
|
|
3
|
-
import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js';
|
|
4
|
-
import { NotificationsRepository } from '../../repositories/notifications.repository.js';
|
|
5
|
-
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
6
|
-
import { performWriteTransactionAsync } from '../../sync.js';
|
|
7
|
-
import { getMigratedId } from '../../lib/migration.js';
|
|
8
|
-
import { discordRPCService } from '../../discord-rpc.js';
|
|
9
|
-
import { dbGet } from '../../utils/db-utils.js';
|
|
10
|
-
import { episodeProgressInput, progressUpdateInput, showIdInput } from '../validation.js';
|
|
11
|
-
async function showsMetaChanged(db, showId, candidate) {
|
|
12
|
-
const existing = (await ShowsMetaRepository.getById(db, showId));
|
|
13
|
-
if (!existing)
|
|
14
|
-
return true;
|
|
15
|
-
const differs = (incoming, stored) => {
|
|
16
|
-
if (incoming === undefined || incoming === null || incoming === '')
|
|
17
|
-
return false;
|
|
18
|
-
return String(incoming) !== String(stored ?? '');
|
|
19
|
-
};
|
|
20
|
-
return (differs(candidate.name, existing.name) ||
|
|
21
|
-
differs(candidate.thumbnail, existing.thumbnail) ||
|
|
22
|
-
differs(candidate.nativeName, existing.nativeName) ||
|
|
23
|
-
differs(candidate.englishName, existing.englishName) ||
|
|
24
|
-
differs(candidate.genres, existing.genres) ||
|
|
25
|
-
differs(candidate.popularityScore, existing.popularityScore) ||
|
|
26
|
-
differs(candidate.status, existing.status) ||
|
|
27
|
-
differs(candidate.episodeCount, existing.episodeCount) ||
|
|
28
|
-
differs(candidate.type, existing.type) ||
|
|
29
|
-
differs(candidate.anilistId, existing.anilistId) ||
|
|
30
|
-
differs(candidate.isAdult, existing.isAdult) ||
|
|
31
|
-
differs(candidate.episodeDuration, existing.episodeDuration));
|
|
32
|
-
}
|
|
33
|
-
export const progressRouter = router({
|
|
34
|
-
getEpisode: protectedProcedure.input(episodeProgressInput()).query(async ({ ctx, input }) => {
|
|
35
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
36
|
-
const progress = await WatchedEpisodesRepository.getByShowAndEpisode(ctx.db, showId, input.episodeNumber);
|
|
37
|
-
return progress || { currentTime: 0, duration: 0 };
|
|
38
|
-
}),
|
|
39
|
-
getWatchedEpisodes: protectedProcedure.input(showIdInput()).query(async ({ ctx, input }) => {
|
|
40
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
41
|
-
return WatchedEpisodesRepository.getWatchedEpisodeNumbers(ctx.db, showId);
|
|
42
|
-
}),
|
|
43
|
-
updateProgress: protectedProcedure
|
|
44
|
-
.input(progressUpdateInput())
|
|
45
|
-
.mutation(async ({ ctx, input }) => {
|
|
46
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
47
|
-
const { episodeNumber } = input;
|
|
48
|
-
const currentTime = input.currentTime || 0;
|
|
49
|
-
const duration = input.duration || 0;
|
|
50
|
-
const titlePreferenceRow = await SettingsRepository.getByKey(ctx.db, 'titlePreference');
|
|
51
|
-
const titlePreference = titlePreferenceRow ? titlePreferenceRow.value : 'englishName';
|
|
52
|
-
let displayName = input.showName;
|
|
53
|
-
if (titlePreference === 'englishName' && input.englishName) {
|
|
54
|
-
displayName = input.englishName;
|
|
55
|
-
}
|
|
56
|
-
else if (titlePreference === 'nativeName' && input.nativeName) {
|
|
57
|
-
displayName = input.nativeName;
|
|
58
|
-
}
|
|
59
|
-
discordRPCService.updatePresence({
|
|
60
|
-
title: displayName,
|
|
61
|
-
episode: String(episodeNumber),
|
|
62
|
-
totalEpisodes: input.episodeCount ? String(input.episodeCount) : undefined,
|
|
63
|
-
currentTime,
|
|
64
|
-
duration,
|
|
65
|
-
thumbnail: input.showThumbnail || '',
|
|
66
|
-
isPlaying: input.isPlaying !== false,
|
|
67
|
-
sessionId: input.sessionId,
|
|
68
|
-
isAdult: input.isAdult,
|
|
69
|
-
});
|
|
70
|
-
const genresStr = Array.isArray(input.genres) ? JSON.stringify(input.genres) : input.genres;
|
|
71
|
-
const anilistId = /^\d+$/.test(showId)
|
|
72
|
-
? (dbGet(ctx.db, 'SELECT anilistId FROM shows_meta WHERE id = ? AND anilistId IS NOT NULL', [showId])?.anilistId ?? parseInt(showId))
|
|
73
|
-
: undefined;
|
|
74
|
-
const metaCandidate = {
|
|
75
|
-
name: input.showName,
|
|
76
|
-
thumbnail: input.showThumbnail,
|
|
77
|
-
nativeName: input.nativeName,
|
|
78
|
-
englishName: input.englishName,
|
|
79
|
-
genres: genresStr,
|
|
80
|
-
popularityScore: input.popularityScore,
|
|
81
|
-
status: input.status,
|
|
82
|
-
episodeCount: input.episodeCount,
|
|
83
|
-
type: input.type,
|
|
84
|
-
anilistId,
|
|
85
|
-
isAdult: typeof input.isAdult === 'boolean' ? (input.isAdult ? 1 : 0) : null,
|
|
86
|
-
episodeDuration: duration > 0 ? Math.round(duration / 60) : undefined,
|
|
87
|
-
};
|
|
88
|
-
const metaChanged = await showsMetaChanged(ctx.db, showId, metaCandidate);
|
|
89
|
-
if (metaChanged) {
|
|
90
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
91
|
-
await ShowsMetaRepository.upsert(tx, {
|
|
92
|
-
id: showId,
|
|
93
|
-
...metaCandidate,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
98
|
-
await WatchedEpisodesRepository.upsert(tx, {
|
|
99
|
-
showId,
|
|
100
|
-
episodeNumber,
|
|
101
|
-
currentTime,
|
|
102
|
-
duration,
|
|
103
|
-
});
|
|
104
|
-
await NotificationsRepository.deleteSpecificDismissed(tx, showId, episodeNumber);
|
|
105
|
-
await NotificationsRepository.deleteDiscovered(tx, showId, episodeNumber);
|
|
106
|
-
});
|
|
107
|
-
return { success: true };
|
|
108
|
-
}),
|
|
109
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import { QueueRepository } from '../../repositories/queue.repository.js';
|
|
3
|
-
import { ShowsMetaRepository } from '../../repositories/shows-meta.repository.js';
|
|
4
|
-
import { performWriteTransactionAsync } from '../../sync.js';
|
|
5
|
-
import { getMigratedId } from '../../lib/migration.js';
|
|
6
|
-
import { queueAddInput, queueBatchInput, queueRemoveInput, queueRemoveManyInput, queueReorderInput, } from '../validation.js';
|
|
7
|
-
function applyMeta(meta) {
|
|
8
|
-
return meta.showName || meta.showThumbnail || meta.nativeName || meta.englishName || meta.type;
|
|
9
|
-
}
|
|
10
|
-
export const queueRouter = router({
|
|
11
|
-
list: protectedProcedure.query(async ({ ctx }) => {
|
|
12
|
-
const rows = await QueueRepository.getAll(ctx.db);
|
|
13
|
-
return rows.map((row) => ({
|
|
14
|
-
...row,
|
|
15
|
-
_id: row.showId,
|
|
16
|
-
id: row.id,
|
|
17
|
-
thumbnail: row.thumbnail || '',
|
|
18
|
-
}));
|
|
19
|
-
}),
|
|
20
|
-
add: protectedProcedure.input(queueAddInput()).mutation(async ({ ctx, input }) => {
|
|
21
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
22
|
-
const episodeNumber = input.episodeNumber;
|
|
23
|
-
const existing = await QueueRepository.getByEpisode(ctx.db, showId, episodeNumber);
|
|
24
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
25
|
-
if (applyMeta(input)) {
|
|
26
|
-
await ShowsMetaRepository.upsert(tx, {
|
|
27
|
-
id: showId,
|
|
28
|
-
name: input.showName || '',
|
|
29
|
-
thumbnail: input.showThumbnail || '',
|
|
30
|
-
nativeName: input.nativeName,
|
|
31
|
-
englishName: input.englishName,
|
|
32
|
-
type: input.type,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
if (existing) {
|
|
36
|
-
await QueueRepository.removeEpisode(tx, showId, episodeNumber);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
await QueueRepository.addToEnd(tx, showId, episodeNumber);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
return { success: true, queued: !existing };
|
|
43
|
-
}),
|
|
44
|
-
addBatch: protectedProcedure.input(queueBatchInput()).mutation(async ({ ctx, input }) => {
|
|
45
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
46
|
-
const normalized = [...new Set(input.episodeNumbers)];
|
|
47
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
48
|
-
if (applyMeta(input)) {
|
|
49
|
-
await ShowsMetaRepository.upsert(tx, {
|
|
50
|
-
id: showId,
|
|
51
|
-
name: input.showName || '',
|
|
52
|
-
thumbnail: input.showThumbnail || '',
|
|
53
|
-
nativeName: input.nativeName,
|
|
54
|
-
englishName: input.englishName,
|
|
55
|
-
type: input.type,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
await QueueRepository.addManyToEnd(tx, normalized.map((episodeNumber) => ({ showId, episodeNumber })));
|
|
59
|
-
});
|
|
60
|
-
return { success: true, added: normalized.length };
|
|
61
|
-
}),
|
|
62
|
-
remove: protectedProcedure.input(queueRemoveInput()).mutation(async ({ ctx, input }) => {
|
|
63
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
64
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
65
|
-
await QueueRepository.removeEpisode(tx, showId, input.episodeNumber);
|
|
66
|
-
});
|
|
67
|
-
return { success: true };
|
|
68
|
-
}),
|
|
69
|
-
removeMany: protectedProcedure.input(queueRemoveManyInput()).mutation(async ({ ctx, input }) => {
|
|
70
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
71
|
-
let removed = [];
|
|
72
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
73
|
-
removed = ((await QueueRepository.getByShow(tx, showId)) || []).map((ep) => ep.episodeNumber);
|
|
74
|
-
const toRemove = Array.isArray(input.episodeNumbers) && input.episodeNumbers.length
|
|
75
|
-
? [...new Set(input.episodeNumbers)]
|
|
76
|
-
: removed;
|
|
77
|
-
await QueueRepository.removeMany(tx, showId, toRemove);
|
|
78
|
-
});
|
|
79
|
-
return { success: true, removed: removed.length };
|
|
80
|
-
}),
|
|
81
|
-
clear: protectedProcedure.mutation(async ({ ctx }) => {
|
|
82
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
83
|
-
await QueueRepository.clear(tx);
|
|
84
|
-
});
|
|
85
|
-
return { success: true };
|
|
86
|
-
}),
|
|
87
|
-
reorder: protectedProcedure.input(queueReorderInput()).mutation(async ({ ctx, input }) => {
|
|
88
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
89
|
-
await QueueRepository.reorder(tx, input);
|
|
90
|
-
});
|
|
91
|
-
return { success: true };
|
|
92
|
-
}),
|
|
93
|
-
});
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import { defineSchema, optStr, reqObj } from '../validation.js';
|
|
3
|
-
import logger from '../../logger.js';
|
|
4
|
-
import { parseJsonBody } from '../../utils/http.utils.js';
|
|
5
|
-
const LISTEN_MOE_STATIONS = [
|
|
6
|
-
{
|
|
7
|
-
id: 'listenmoe-jpop',
|
|
8
|
-
name: 'LISTEN.moe JPOP',
|
|
9
|
-
streamUrl: 'https://listen.moe/fallback',
|
|
10
|
-
homepage: 'https://listen.moe/',
|
|
11
|
-
tags: 'anime,j-pop,jpop',
|
|
12
|
-
codec: 'MP3',
|
|
13
|
-
source: 'listen.moe',
|
|
14
|
-
gateway: 'wss://listen.moe/gateway_v2',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: 'listenmoe-kpop',
|
|
18
|
-
name: 'LISTEN.moe KPOP',
|
|
19
|
-
streamUrl: 'https://listen.moe/kpop/fallback',
|
|
20
|
-
homepage: 'https://listen.moe/',
|
|
21
|
-
tags: 'k-pop,kpop',
|
|
22
|
-
codec: 'MP3',
|
|
23
|
-
source: 'listen.moe',
|
|
24
|
-
gateway: 'wss://listen.moe/kpop/gateway_v2',
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
const FEATURED_STATIONS = [
|
|
28
|
-
{
|
|
29
|
-
id: 'stereo-anime',
|
|
30
|
-
name: 'stereo anime',
|
|
31
|
-
streamUrl: 'https://radio.stereoanime.com/listen/stereoanime/128',
|
|
32
|
-
tags: 'anime',
|
|
33
|
-
codec: 'MP3',
|
|
34
|
-
bitrate: 128,
|
|
35
|
-
source: 'radio-browser',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: 'jpop-sakura',
|
|
39
|
-
name: 'J-Pop Sakura (asia DREAM radio)',
|
|
40
|
-
streamUrl: 'https://quincy.torontocast.com:2070/stream.mp3',
|
|
41
|
-
tags: 'j-pop,jpop',
|
|
42
|
-
codec: 'MP3',
|
|
43
|
-
bitrate: 128,
|
|
44
|
-
source: 'radio-browser',
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: 'n-kpop',
|
|
48
|
-
name: '0 N - K-Pop on Radio',
|
|
49
|
-
streamUrl: 'https://0n-kpop.radionetz.de/0n-kpop.mp3',
|
|
50
|
-
tags: 'k-pop,kpop',
|
|
51
|
-
codec: 'MP3',
|
|
52
|
-
bitrate: 128,
|
|
53
|
-
source: 'radio-browser',
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
id: 'bigb-kpop',
|
|
57
|
-
name: 'Big B Radio - Kpop',
|
|
58
|
-
streamUrl: 'https://antares.dribbcast.com/proxy/kpop?mp=/s',
|
|
59
|
-
tags: 'k-pop,kpop',
|
|
60
|
-
codec: 'MP3',
|
|
61
|
-
bitrate: 192,
|
|
62
|
-
source: 'radio-browser',
|
|
63
|
-
},
|
|
64
|
-
];
|
|
65
|
-
const RB_MIRRORS = ['https://de1.api.radio-browser.info', 'https://nl1.api.radio-browser.info'];
|
|
66
|
-
async function rbFetch(path) {
|
|
67
|
-
let lastErr = null;
|
|
68
|
-
for (const base of RB_MIRRORS) {
|
|
69
|
-
try {
|
|
70
|
-
const res = await fetch(`${base}${path}`, {
|
|
71
|
-
headers: { 'User-Agent': 'dango/1.0' },
|
|
72
|
-
signal: AbortSignal.timeout(10000),
|
|
73
|
-
});
|
|
74
|
-
if (!res.ok)
|
|
75
|
-
throw new Error(`radio-browser ${res.status}`);
|
|
76
|
-
return parseJsonBody(res);
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
lastErr = err;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
throw lastErr;
|
|
83
|
-
}
|
|
84
|
-
function toStation(s) {
|
|
85
|
-
const url = s.url_resolved || '';
|
|
86
|
-
if (!url.startsWith('https://'))
|
|
87
|
-
return null;
|
|
88
|
-
return {
|
|
89
|
-
id: s.stationuuid || url,
|
|
90
|
-
name: s.name || 'Unknown station',
|
|
91
|
-
streamUrl: url,
|
|
92
|
-
homepage: s.homepage || undefined,
|
|
93
|
-
favicon: s.favicon && s.favicon.startsWith('https://') ? s.favicon : undefined,
|
|
94
|
-
tags: s.tags || undefined,
|
|
95
|
-
codec: s.codec || undefined,
|
|
96
|
-
bitrate: s.bitrate || undefined,
|
|
97
|
-
source: 'radio-browser',
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
const radioSearchInput = () => defineSchema((value) => {
|
|
101
|
-
const obj = reqObj(value);
|
|
102
|
-
const out = {};
|
|
103
|
-
const q = optStr(obj, 'q');
|
|
104
|
-
if (q !== undefined)
|
|
105
|
-
out.q = q;
|
|
106
|
-
return out;
|
|
107
|
-
});
|
|
108
|
-
export const radioRouter = router({
|
|
109
|
-
stations: protectedProcedure.query(async ({ ctx }) => {
|
|
110
|
-
try {
|
|
111
|
-
const cached = ctx.apiCache.get('route-radio-stations');
|
|
112
|
-
if (cached)
|
|
113
|
-
return { stations: cached };
|
|
114
|
-
const stations = [...LISTEN_MOE_STATIONS, ...FEATURED_STATIONS];
|
|
115
|
-
ctx.apiCache.set('route-radio-stations', stations, 3600);
|
|
116
|
-
return { stations };
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
logger.error({ err }, '[Radio] stations failed');
|
|
120
|
-
return { stations: LISTEN_MOE_STATIONS };
|
|
121
|
-
}
|
|
122
|
-
}),
|
|
123
|
-
search: protectedProcedure.input(radioSearchInput()).query(async ({ ctx, input }) => {
|
|
124
|
-
try {
|
|
125
|
-
const q = String(input.q || '').trim();
|
|
126
|
-
if (!q)
|
|
127
|
-
return { stations: [] };
|
|
128
|
-
const cacheKey = `route-radio-search-${q.toLowerCase()}`;
|
|
129
|
-
const cached = ctx.apiCache.get(cacheKey);
|
|
130
|
-
if (cached)
|
|
131
|
-
return { stations: cached };
|
|
132
|
-
const raw = (await rbFetch(`/json/stations/search?name=${encodeURIComponent(q)}&hidebroken=true&order=clickcount&reverse=true&limit=25`));
|
|
133
|
-
const stations = raw
|
|
134
|
-
.map(toStation)
|
|
135
|
-
.filter((s) => s !== null)
|
|
136
|
-
.slice(0, 25);
|
|
137
|
-
ctx.apiCache.set(cacheKey, stations, 300);
|
|
138
|
-
return { stations };
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
logger.error({ err }, '[Radio] search failed');
|
|
142
|
-
return { stations: [] };
|
|
143
|
-
}
|
|
144
|
-
}),
|
|
145
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
|
-
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
4
|
-
import { performWriteTransactionAsync } from '../../sync.js';
|
|
5
|
-
import { discordRPCService } from '../../discord-rpc.js';
|
|
6
|
-
import logger from '../../logger.js';
|
|
7
|
-
import { offlineDb } from '../../lib/offline-db.js';
|
|
8
|
-
import { translateTexts } from '../../lib/translate.js';
|
|
9
|
-
import { getMalImportStatus, requestMalImportCancel } from '../../lib/mal-import.js';
|
|
10
|
-
import { defineSchema, reqObj } from '../validation.js';
|
|
11
|
-
import { keyInput, setSettingInput } from '../validation.js';
|
|
12
|
-
export const SETTING_DEFAULTS = {
|
|
13
|
-
discordRPCEnabled: 'true',
|
|
14
|
-
discordRPCHideMature: 'true',
|
|
15
|
-
dangoGaze: 'true',
|
|
16
|
-
ignoreAdultContent: 'true',
|
|
17
|
-
mangaIgnoreAdultContent: 'true',
|
|
18
|
-
tvIgnoreAdultContent: 'true',
|
|
19
|
-
asmrIgnoreAdultContent: 'true',
|
|
20
|
-
};
|
|
21
|
-
const autoUpdateInput = () => defineSchema((value) => {
|
|
22
|
-
const obj = reqObj(value);
|
|
23
|
-
if (typeof obj.enabled !== 'boolean')
|
|
24
|
-
throw new Error('enabled must be a boolean');
|
|
25
|
-
return { enabled: obj.enabled };
|
|
26
|
-
});
|
|
27
|
-
const translateInput = () => defineSchema((value) => {
|
|
28
|
-
if (value === undefined || value === null)
|
|
29
|
-
return {};
|
|
30
|
-
const obj = reqObj(value);
|
|
31
|
-
return { texts: obj.texts, source: obj.source, target: obj.target };
|
|
32
|
-
});
|
|
33
|
-
export const settingsRouter = router({
|
|
34
|
-
getByKey: protectedProcedure.input(keyInput()).query(async ({ ctx, input }) => {
|
|
35
|
-
const row = await SettingsRepository.getByKey(ctx.db, input.key);
|
|
36
|
-
const value = row ? row.value : (SETTING_DEFAULTS[input.key] ?? null);
|
|
37
|
-
return { value };
|
|
38
|
-
}),
|
|
39
|
-
set: protectedProcedure.input(setSettingInput()).mutation(async ({ ctx, input }) => {
|
|
40
|
-
const key = input.key;
|
|
41
|
-
const value = String(input.value ?? '');
|
|
42
|
-
const shouldDelete = value === '' && key === 'tracker_anilist_client_id';
|
|
43
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
44
|
-
if (shouldDelete)
|
|
45
|
-
await SettingsRepository.deleteByKey(tx, key);
|
|
46
|
-
else
|
|
47
|
-
await SettingsRepository.upsert(tx, key, value);
|
|
48
|
-
});
|
|
49
|
-
if (key === 'discordRPCEnabled') {
|
|
50
|
-
discordRPCService.setEnabled(input.value === 'true' || input.value === true);
|
|
51
|
-
}
|
|
52
|
-
if (key === 'discordRPCHideMature') {
|
|
53
|
-
discordRPCService.setHideMature(input.value === 'true' || input.value === true);
|
|
54
|
-
}
|
|
55
|
-
return { success: true };
|
|
56
|
-
}),
|
|
57
|
-
offlineDbInfo: protectedProcedure.query(async ({ ctx }) => {
|
|
58
|
-
return await offlineDb.getOfflineDbInfo(ctx.db);
|
|
59
|
-
}),
|
|
60
|
-
setOfflineDbAutoUpdate: protectedProcedure
|
|
61
|
-
.input(autoUpdateInput())
|
|
62
|
-
.mutation(async ({ ctx, input }) => {
|
|
63
|
-
await SettingsRepository.upsert(ctx.db, 'offlineDbAutoUpdateEnabled', String(input.enabled));
|
|
64
|
-
return { success: true, enabled: input.enabled };
|
|
65
|
-
}),
|
|
66
|
-
triggerOfflineDbUpdate: protectedProcedure.mutation(async ({ ctx }) => {
|
|
67
|
-
const info = await offlineDb.getOfflineDbInfo(ctx.db);
|
|
68
|
-
if (info.isRefreshing) {
|
|
69
|
-
throw new TRPCError({
|
|
70
|
-
code: 'CONFLICT',
|
|
71
|
-
message: 'Offline database refresh already in progress',
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
offlineDb.refreshDatabase(ctx.db).catch((err) => {
|
|
75
|
-
logger.warn({ err: err?.message }, 'Manual offline database update failed');
|
|
76
|
-
});
|
|
77
|
-
return { message: 'Offline database refresh started' };
|
|
78
|
-
}),
|
|
79
|
-
translate: protectedProcedure.input(translateInput()).mutation(async ({ input }) => {
|
|
80
|
-
const texts = input.texts;
|
|
81
|
-
const source = input.source ?? 'ja';
|
|
82
|
-
const target = input.target ?? 'en';
|
|
83
|
-
if (!Array.isArray(texts) || texts.length === 0) {
|
|
84
|
-
return { translations: {} };
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
translations: await translateTexts(texts, String(source), String(target)),
|
|
88
|
-
};
|
|
89
|
-
}),
|
|
90
|
-
malImportStatus: protectedProcedure.query(() => {
|
|
91
|
-
return getMalImportStatus();
|
|
92
|
-
}),
|
|
93
|
-
malImportCancel: protectedProcedure.mutation(() => {
|
|
94
|
-
return requestMalImportCancel();
|
|
95
|
-
}),
|
|
96
|
-
});
|