prepare for being available on multiple domain names

This commit is contained in:
2025-07-06 18:43:36 +02:00
parent 260cef7b73
commit 38e8b4c2ba
8 changed files with 97 additions and 25 deletions

View File

@ -3,16 +3,32 @@ import { TimelineReader } from '$lib/server/timeline';
import type { Handle, HandleServerError } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import fs from 'fs/promises';
import { close } from '$lib/server/db';
const logger = new Logger('App');
logger.log('App startup');
if (process?.pid) {
try {
await fs.writeFile('moshing-mammut.pid', process.pid.toString());
} catch (e) {
logger.error('Could not write PID to file', e);
}
}
logger.log('App startup, PID', process?.pid);
logger.log('Debug log enabled', Logger.isDebugEnabled());
TimelineReader.init();
export const handleError = (({ error }) => {
if (error instanceof Error) {
logger.error('Something went wrong: ', error.name, error.message);
process.on('sveltekit:shutdown', (reason) => {
close();
logger.log('Shutting down', reason);
process.exit(0);
});
export const handleError = (({ error, status }) => {
if (error instanceof Error && status !== 404) {
logger.error('Something went wrong:', error.name, error.message);
}
return {

View File

@ -48,25 +48,25 @@ export class Logger {
if (!enableVerboseLog) {
return;
}
console.debug(new Date().toISOString(), `- ${this.name} -`, ...params);
console.debug(new Date().toISOString(), `- ${this.name} -`, '- [VRBSE] -', ...params);
}
public debug(...params: any[]) {
if (!Logger.isDebugEnabled()) {
return;
}
console.debug(new Date().toISOString(), `- ${this.name} -`, ...params);
console.debug(new Date().toISOString(), `- ${this.name} -`, '- [DEBUG] -', ...params);
}
public log(...params: any[]) {
console.log(new Date().toISOString(), `- ${this.name} -`, ...params);
console.log(new Date().toISOString(), `- ${this.name} -`, '- [ LOG ] -', ...params);
}
public info(...params: any[]) {
console.info(new Date().toISOString(), `- ${this.name} -`, ...params);
console.info(new Date().toISOString(), `- ${this.name} -`, '- [INFO ] -', ...params);
}
public warn(...params: any[]) {
console.warn(new Date().toISOString(), `- ${this.name} -`, ...params);
console.warn(new Date().toISOString(), `- ${this.name} -`, '- [WARN ] -', ...params);
}
public error(...params: any[]) {
console.error(new Date().toISOString(), `- ${this.name} -`, ...params);
console.error(new Date().toISOString(), `- ${this.name} -`, '- [ERROR] -', ...params);
}
public static error(...params: any[]) {

View File

@ -68,6 +68,15 @@ type Migration = {
};
const db: sqlite3.Database = new sqlite3.Database('moshingmammut.db');
export function close() {
try {
db.close();
} catch (e) {
logger.error('Could not close DB');
}
}
// for the local masto instance, the instance name is *not* saved
// as part of the username or acct, so it needs to be stripped
const ignoredUsers: string[] =

View File

@ -1,3 +1,4 @@
import { BASE_URL } from '$env/static/private';
import { Logger } from '$lib/log';
import type { OauthResponse } from '$lib/mastodon/response';
import fs from 'fs/promises';
@ -6,6 +7,7 @@ export abstract class OauthPlaylistAdder {
/// How many minutes before expiry the token will be refreshed
protected refresh_time: number = 15;
protected logger: Logger = new Logger('OauthPlaylistAdder');
protected redirectUri?: URL;
protected constructor(
protected apiBase: string,
@ -22,6 +24,12 @@ export abstract class OauthPlaylistAdder {
}
}
protected getRedirectUri(suffix: string): URL {
const uri = this.redirectUri ?? new URL(`${BASE_URL}/${suffix}`);
this.logger.debug('getRedirectUri', uri);
return uri;
}
protected constructAuthUrlInternal(
endpointUrl: string,
clientId: string,

View File

@ -1,9 +1,4 @@
import {
BASE_URL,
YOUTUBE_CLIENT_ID,
YOUTUBE_CLIENT_SECRET,
YOUTUBE_PLAYLIST_ID
} from '$env/static/private';
import { YOUTUBE_CLIENT_ID, YOUTUBE_CLIENT_SECRET, YOUTUBE_PLAYLIST_ID } from '$env/static/private';
import { Logger } from '$lib/log';
import type { OauthResponse } from '$lib/mastodon/response';
import type { SongInfo } from '$lib/odesliResponse';
@ -17,6 +12,7 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
}
public constructAuthUrl(redirectUri: URL): URL {
this.redirectUri = redirectUri;
let additionalParameters = new Map([
['access_type', 'offline'],
['include_granted_scopes', 'false']
@ -33,6 +29,7 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
public async receivedAuthCode(code: string, url: URL) {
this.logger.debug('received code');
this.redirectUri = url;
const tokenUrl = new URL('https://oauth2.googleapis.com/token');
await this.receivedAuthCodeInternal(
tokenUrl,
@ -57,13 +54,13 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
}
}
private async refreshToken(): Promise<OauthResponse | null> {
private async refreshToken(force: boolean = false): Promise<OauthResponse | null> {
const tokenInfo = await this.shouldRefreshToken();
if (tokenInfo == null) {
return null;
}
let token = tokenInfo.token;
if (!tokenInfo.refresh) {
if (!tokenInfo.refresh && !force) {
return token;
}
if (!token.refresh_token) {
@ -76,12 +73,17 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
tokenUrl,
YOUTUBE_CLIENT_ID,
token.refresh_token,
`${BASE_URL}/ytauth`,
this.getRedirectUri('ytauth').toString(),
YOUTUBE_CLIENT_SECRET
);
}
public async addToPlaylist(song: SongInfo) {
public async addToPlaylist(song: SongInfo) {}
private async addToPlaylistRetry(song: SongInfo, remaning: number = 3) {
if (remaning < 0) {
this.logger.error('max retries reached, song will not be added to spotify playlist');
}
this.logger.debug('addToYoutubePlaylist');
const token = await this.refreshToken();
if (token == null) {
@ -139,7 +141,15 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
const respObj = await resp.json();
this.logger.info('Added to playlist', youtubeId, song.title);
if (respObj.error) {
this.logger.debug('Add to playlist failed', respObj.error.errors);
this.logger.error('Add to playlist failed', respObj.error.errors);
if (respObj.error.errors && respObj.error.errors[0].reason === 'authError') {
this.logger.info('Refreshing auth token');
const token = await this.refreshToken(true);
if (token == null) {
return;
}
this.addToPlaylistRetry(song, remaning--);
}
}
}
}

View File

@ -2,6 +2,7 @@ import {
HASHTAG_FILTER,
MASTODON_ACCESS_TOKEN,
MASTODON_INSTANCE,
IGNORE_USERS,
ODESLI_API_KEY,
YOUTUBE_API_KEY
} from '$env/static/private';
@ -48,6 +49,7 @@ export class TimelineReader {
private lastPosts: string[] = [];
private playlistAdders: PlaylistAdder[];
private logger: Logger;
private ignoredUsers: string[];
private async isMusicVideo(videoId: string) {
if (!YOUTUBE_API_KEY || YOUTUBE_API_KEY === 'CHANGE_ME') {
@ -372,6 +374,20 @@ export class TimelineReader {
}
private async checkAndSavePost(post: Post) {
if (IGNORE_USERS !== undefined && IGNORE_USERS !== '' && IGNORE_USERS !== 'CHANGE_ME') {
const ignorelist = IGNORE_USERS.split(',');
const isIgnored = ignorelist.includes(post.account.username);
const isIgnoredDb = this.ignoredUsers.includes(post.account.username);
this.logger.debug(
'Check if user',
post.account.username,
'is ignored',
this.ignoredUsers,
isIgnored,
isIgnoredDb
);
}
const hashttags: string[] = HASHTAG_FILTER.split(',');
const found_tags: Tag[] = post.tags.filter((t: Tag) => hashttags.includes(t.name));
@ -480,6 +496,16 @@ export class TimelineReader {
this.logger = new Logger('Timeline');
this.logger.log('Constructing timeline object');
this.playlistAdders = [new YoutubePlaylistAdder(), new SpotifyPlaylistAdder()];
this.ignoredUsers =
IGNORE_USERS === undefined
? []
: IGNORE_USERS.split(',')
.map((u) => (u.startsWith('@') ? u.substring(1) : u))
.map((u) =>
u.endsWith('@' + MASTODON_INSTANCE)
? u.substring(0, u.length - ('@' + MASTODON_INSTANCE).length)
: u
);
this.startWebsocket();
this.loadPostsSinceLastRun()

View File

@ -7,9 +7,10 @@ const { DEV } = import.meta.env;
const logger = new Logger('SpotifyAuth');
export const load: PageServerLoad = async ({ url }) => {
export const load: PageServerLoad = async ({ url, request }) => {
const baseUrl = request.headers.get('X-Forwarded-Host') ?? BASE_URL;
const adder = new SpotifyPlaylistAdder();
let redirect_uri = new URL(`${BASE_URL}/spotifyAuth`);
let redirect_uri = new URL(`${new URL(BASE_URL).protocol}//${baseUrl}/spotifyAuth`);
if (url.hostname === 'localhost' && DEV) {
redirect_uri.hostname = '127.0.0.1';
}

View File

@ -6,9 +6,11 @@ import type { PageServerLoad } from './$types';
const logger = new Logger('YT Auth');
export const load: PageServerLoad = async ({ url }) => {
export const load: PageServerLoad = async ({ url, request }) => {
const baseUrl = request.headers.get('X-Forwarded-Host') ?? BASE_URL;
const adder = new YoutubePlaylistAdder();
const redirect_uri = new URL(`${BASE_URL}/ytauth`);
logger.debug('redirect URL', `${new URL(BASE_URL).protocol}//${baseUrl}/ytauth`);
const redirect_uri = new URL(`${new URL(BASE_URL).protocol}//${baseUrl}/ytauth`);
if (url.searchParams.has('code')) {
logger.debug(url.searchParams);
await adder.receivedAuthCode(url.searchParams.get('code') || '', redirect_uri);