YT Music-knapp på korten + Veckans låtar (egen låtförslags-batch)
All checks were successful
build-and-push / build (push) Successful in 13s

- MusicCard: ▶ YT-knapp öppnar music.youtube.com-sökning på artist/låt/album
- Låtförslag: egen batch-typ 'tracks' — signaler: 10 senast gillade
  (Liked Music-listan i Youtubarr-DB, fallback senaste tillägg) + 10 mest
  spelade i Jellyfin (SortBy=PlayCount); egen Gemini-prompt för enskilda
  låtar; berikas med artist-MBID så Hämta lägger in artisten i Lidarr
- Upptäck: egen 'Veckans låtar'-ruta med senaste 10 + generera-knapp
- Schema: trackIntervalDays (default 7, 0 = av) i inställningarna,
  schemaläggaren kör båda typerna oberoende
- Förslag-sidan märker batcher med typ; 26 tester gröna

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01824ZrvG2mDYYrmwqypLNup
This commit is contained in:
2026-08-01 01:57:14 +02:00
parent 94065f6c11
commit 75f3788c1f
8 changed files with 308 additions and 58 deletions

View File

@@ -11,14 +11,16 @@ function makeYoutubarrDb(dir) {
const p = path.join(dir, 'db.sqlite3')
const db = new Database(p)
db.exec(`
CREATE TABLE youtubarr_playlist (id INTEGER PRIMARY KEY, title TEXT);
CREATE TABLE youtubarr_playlist (id INTEGER PRIMARY KEY, playlist_id TEXT, title TEXT);
CREATE TABLE youtubarr_trackitem (
id INTEGER PRIMARY KEY, playlist_id INTEGER, title TEXT,
artist_name_guess TEXT, blacklisted INTEGER DEFAULT 0
);
INSERT INTO youtubarr_playlist VALUES (1, 'lefu-Musick');
INSERT INTO youtubarr_playlist VALUES (1, 'PLC0x', 'lefu-Musick');
INSERT INTO youtubarr_playlist VALUES (2, 'LM', 'Liked Music');
INSERT INTO youtubarr_trackitem VALUES (1, 1, 'Amaranth', 'Nightwish', 0);
INSERT INTO youtubarr_trackitem VALUES (2, 1, 'Svart', 'Hemligt Band', 1);
INSERT INTO youtubarr_trackitem VALUES (3, 2, 'Gimme! Gimme! Gimme!', 'ABBA', 0);
`)
db.close()
return p
@@ -109,6 +111,29 @@ test('run: markdown-staket runt JSON hanteras', async () => {
assert.equal(batch.items[0].name, 'Ghost')
})
test('run(tracks): gillade + mest spelade in i prompten, egen batch-typ', async () => {
const { engine, fetchImpl } = setup({
geminiResponse: JSON.stringify([
{ name: 'Dancing Queen', artist: 'ABBA', motivation: 'Klassiker.' },
]),
})
const batch = await engine.run('manuell', 'tracks')
assert.match(fetchImpl.prompt, /ENSKILDA LÅTAR/)
assert.match(fetchImpl.prompt, /ABBA Gimme! Gimme! Gimme!/, 'Liked Music-låtar ska in')
assert.match(fetchImpl.prompt, /1000mods Zenit/, 'mest spelade ska in')
assert.equal(batch.type, 'tracks')
assert.equal(batch.items[0].kind, 'track')
assert.equal(batch.items[0].secondary, 'ABBA')
assert.equal(batch.items[0].artistMbid, 'mbid-x', 'artist-mbid berikas för Hämta')
assert.equal(engine.latest(10, 'tracks').length, 1)
assert.equal(engine.latest(10, 'mix').length, 0, 'låt-batcher ska inte blandas in i mixen')
assert.equal(engine.lastRun('tracks'), batch.createdAt)
assert.equal(engine.lastRun('mix'), null)
})
test('run utan Gemini-nyckel ger begripligt fel', async () => {
const { engine, dataDir } = setup()
const store = settingsStore(dataDir)