refactor playlist adders

This commit is contained in:
2025-07-03 18:52:00 +02:00
parent a0757ea3ff
commit b0465a020d
7 changed files with 19 additions and 89 deletions

View File

@ -0,0 +1,5 @@
import type { SongInfo } from '$lib/odesliResponse';
export interface PlaylistAdder {
addToPlaylist(song: SongInfo): Promise<void>;
}

View File

@ -3,8 +3,9 @@ import { Logger } from '$lib/log';
import type { OauthResponse } from '$lib/mastodon/response'; import type { OauthResponse } from '$lib/mastodon/response';
import type { SongInfo } from '$lib/odesliResponse'; import type { SongInfo } from '$lib/odesliResponse';
import { OauthPlaylistAdder } from './oauthPlaylistAdder'; import { OauthPlaylistAdder } from './oauthPlaylistAdder';
import type { PlaylistAdder } from './playlistAdder';
export class SpotifyPlaylistAdder extends OauthPlaylistAdder { export class SpotifyPlaylistAdder extends OauthPlaylistAdder implements PlaylistAdder {
public constructor() { public constructor() {
super('https://api.spotify.com/v1', 'spotify_auth_token'); super('https://api.spotify.com/v1', 'spotify_auth_token');
this.logger = new Logger('SpotifyPlaylistAdder'); this.logger = new Logger('SpotifyPlaylistAdder');

View File

@ -8,8 +8,9 @@ import { log } from '$lib/log';
import type { OauthResponse } from '$lib/mastodon/response'; import type { OauthResponse } from '$lib/mastodon/response';
import type { SongInfo } from '$lib/odesliResponse'; import type { SongInfo } from '$lib/odesliResponse';
import { OauthPlaylistAdder } from './oauthPlaylistAdder'; import { OauthPlaylistAdder } from './oauthPlaylistAdder';
import type { PlaylistAdder } from './playlistAdder';
export class YoutubePlaylistAdder extends OauthPlaylistAdder { export class YoutubePlaylistAdder extends OauthPlaylistAdder implements PlaylistAdder {
public constructor() { public constructor() {
super('https://www.googleapis.com/youtube/v3', 'yt_auth_token'); super('https://www.googleapis.com/youtube/v3', 'yt_auth_token');
} }

View File

@ -25,8 +25,9 @@ import {
savePost, savePost,
saveSongThumbnail saveSongThumbnail
} from '$lib/server/db'; } from '$lib/server/db';
import { SpotifyPlaylistAdder } from '$lib/server/playlist/spotifyPlaylistAdder';
import { YoutubePlaylistAdder } from '$lib/server/playlist/ytPlaylistAdder';
import { createFeed, saveAtomFeed } from '$lib/server/rss'; import { createFeed, saveAtomFeed } from '$lib/server/rss';
import { YoutubePlaylistAdder } from '$lib/server/ytPlaylistAdder';
import { sleep } from '$lib/sleep'; import { sleep } from '$lib/sleep';
import crypto from 'crypto'; import crypto from 'crypto';
import fs from 'fs/promises'; import fs from 'fs/promises';
@ -34,7 +35,7 @@ import { console } from 'inspector/promises';
import sharp from 'sharp'; import sharp from 'sharp';
import { URL, URLSearchParams } from 'url'; import { URL, URLSearchParams } from 'url';
import { WebSocket } from 'ws'; import { WebSocket } from 'ws';
import { SpotifyPlaylistAdder } from './spotifyPlaylistAdder'; import type { PlaylistAdder } from './playlist/playlistAdder';
const URL_REGEX = new RegExp(/href="(?<postUrl>[^>]+?)" target="_blank"/gm); const URL_REGEX = new RegExp(/href="(?<postUrl>[^>]+?)" target="_blank"/gm);
const INVIDIOUS_REGEX = new RegExp(/invidious.*?watch.*?v=(?<videoId>[a-zA-Z_0-9-]+)/gm); const INVIDIOUS_REGEX = new RegExp(/invidious.*?watch.*?v=(?<videoId>[a-zA-Z_0-9-]+)/gm);
@ -45,8 +46,7 @@ const YOUTUBE_REGEX = new RegExp(
export class TimelineReader { export class TimelineReader {
private static _instance: TimelineReader; private static _instance: TimelineReader;
private lastPosts: string[] = []; private lastPosts: string[] = [];
private youtubePlaylistAdder: YoutubePlaylistAdder; private playlistAdders: PlaylistAdder[];
private spotifyPlaylistAdder: SpotifyPlaylistAdder;
private static async isMusicVideo(videoId: string) { private static async isMusicVideo(videoId: string) {
if (!YOUTUBE_API_KEY || YOUTUBE_API_KEY === 'CHANGE_ME') { if (!YOUTUBE_API_KEY || YOUTUBE_API_KEY === 'CHANGE_ME') {
@ -190,86 +190,10 @@ export class TimelineReader {
} }
} }
/*
private async addToYoutubePlaylist(song: SongInfo) {
log.debug('addToYoutubePlaylist');
let token: OauthResponse;
try {
const youtube_token_file = await fs.readFile('yt_auth_token', { encoding: 'utf8' });
token = JSON.parse(youtube_token_file);
log.debug('read youtube access token', token);
} catch (e) {
log.error('Could not read youtube access token', e);
return;
}
if (!YOUTUBE_PLAYLIST_ID || YOUTUBE_PLAYLIST_ID === 'CHANGE_ME') {
log.debug('no playlist ID configured');
return;
}
if (!song.youtubeUrl) {
log.debug('Skip adding song to YT playlist, no youtube Url', song);
return;
}
const songUrl = new URL(song.youtubeUrl);
const youtubeId = songUrl.searchParams.get('v');
if (!youtubeId) {
log.debug(
'Skip adding song to YT playlist, could not extract YT id from URL',
song.youtubeUrl
);
return;
}
log.debug('Found YT id from URL', song.youtubeUrl, youtubeId);
const playlistItemsUrl = new URL('https://www.googleapis.com/youtube/v3/playlistItems');
playlistItemsUrl.searchParams.append('videoId', youtubeId);
playlistItemsUrl.searchParams.append('playlistId', YOUTUBE_PLAYLIST_ID);
playlistItemsUrl.searchParams.append('part', 'id');
const existingPlaylistItem = await fetch(
'https://www.googleapis.com/youtube/v3/playlistItems',
{
headers: { Authorization: `${token.token_type} ${token.access_token}` }
}
).then((r) => r.json());
log.debug('existingPlaylistItem', existingPlaylistItem);
if (existingPlaylistItem.pageInfo && existingPlaylistItem.pageInfo.totalResults > 0) {
log.info('Item already in playlist');
return;
}
const searchParams = new URLSearchParams([
['part', 'snippet']
//['key', token.access_token]
]);
const options: RequestInit = {
method: 'POST',
headers: { Authorization: `${token.token_type} ${token.access_token}` },
body: JSON.stringify({
snippet: {
playlistId: YOUTUBE_PLAYLIST_ID,
resourceId: {
videoId: youtubeId,
kind: 'youtube#video'
}
}
})
};
const youtubeApiUrl = new URL(
`https://www.googleapis.com/youtube/v3/playlistItems?${searchParams}`
);
const resp = await fetch(youtubeApiUrl, options);
const respObj = await resp.json();
log.debug('Added to playlist', options, respObj);
if (respObj.error) {
log.debug('Add to playlist failed', respObj.error.errors);
}
}
*/
private async addToPlaylist(song: SongInfo) { private async addToPlaylist(song: SongInfo) {
await this.youtubePlaylistAdder.addToPlaylist(song); for (let adder of this.playlistAdders) {
await this.spotifyPlaylistAdder.addToPlaylist(song); await adder.addToPlaylist(song);
}
} }
private static async resizeAvatar( private static async resizeAvatar(
@ -554,8 +478,7 @@ export class TimelineReader {
private constructor() { private constructor() {
log.log('Constructing timeline object'); log.log('Constructing timeline object');
this.youtubePlaylistAdder = new YoutubePlaylistAdder(); this.playlistAdders = [new YoutubePlaylistAdder(), new SpotifyPlaylistAdder()];
this.spotifyPlaylistAdder = new SpotifyPlaylistAdder();
this.startWebsocket(); this.startWebsocket();
this.loadPostsSinceLastRun() this.loadPostsSinceLastRun()

View File

@ -1,5 +1,5 @@
import { log } from '$lib/log'; import { log } from '$lib/log';
import { SpotifyPlaylistAdder } from '$lib/server/spotifyPlaylistAdder'; import { SpotifyPlaylistAdder } from '$lib/server/playlist/spotifyPlaylistAdder';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';

View File

@ -1,5 +1,5 @@
import { log } from '$lib/log'; import { log } from '$lib/log';
import { YoutubePlaylistAdder } from '$lib/server/ytPlaylistAdder'; import { YoutubePlaylistAdder } from '$lib/server/playlist/ytPlaylistAdder';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';