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,15 +3,31 @@ import { TimelineReader } from '$lib/server/timeline';
import type { Handle, HandleServerError } from '@sveltejs/kit'; import type { Handle, HandleServerError } from '@sveltejs/kit';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { close } from '$lib/server/db';
const logger = new Logger('App'); 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()); logger.log('Debug log enabled', Logger.isDebugEnabled());
TimelineReader.init(); TimelineReader.init();
export const handleError = (({ error }) => { process.on('sveltekit:shutdown', (reason) => {
if (error instanceof Error) { 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); logger.error('Something went wrong:', error.name, error.message);
} }

View File

@ -48,25 +48,25 @@ export class Logger {
if (!enableVerboseLog) { if (!enableVerboseLog) {
return; return;
} }
console.debug(new Date().toISOString(), `- ${this.name} -`, ...params); console.debug(new Date().toISOString(), `- ${this.name} -`, '- [VRBSE] -', ...params);
} }
public debug(...params: any[]) { public debug(...params: any[]) {
if (!Logger.isDebugEnabled()) { if (!Logger.isDebugEnabled()) {
return; return;
} }
console.debug(new Date().toISOString(), `- ${this.name} -`, ...params); console.debug(new Date().toISOString(), `- ${this.name} -`, '- [DEBUG] -', ...params);
} }
public log(...params: any[]) { 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[]) { 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[]) { 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[]) { 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[]) { public static error(...params: any[]) {

View File

@ -68,6 +68,15 @@ type Migration = {
}; };
const db: sqlite3.Database = new sqlite3.Database('moshingmammut.db'); 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 // for the local masto instance, the instance name is *not* saved
// as part of the username or acct, so it needs to be stripped // as part of the username or acct, so it needs to be stripped
const ignoredUsers: string[] = const ignoredUsers: string[] =

View File

@ -1,3 +1,4 @@
import { BASE_URL } from '$env/static/private';
import { Logger } from '$lib/log'; import { Logger } from '$lib/log';
import type { OauthResponse } from '$lib/mastodon/response'; import type { OauthResponse } from '$lib/mastodon/response';
import fs from 'fs/promises'; import fs from 'fs/promises';
@ -6,6 +7,7 @@ export abstract class OauthPlaylistAdder {
/// How many minutes before expiry the token will be refreshed /// How many minutes before expiry the token will be refreshed
protected refresh_time: number = 15; protected refresh_time: number = 15;
protected logger: Logger = new Logger('OauthPlaylistAdder'); protected logger: Logger = new Logger('OauthPlaylistAdder');
protected redirectUri?: URL;
protected constructor( protected constructor(
protected apiBase: string, 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( protected constructAuthUrlInternal(
endpointUrl: string, endpointUrl: string,
clientId: string, clientId: string,

View File

@ -1,9 +1,4 @@
import { import { YOUTUBE_CLIENT_ID, YOUTUBE_CLIENT_SECRET, YOUTUBE_PLAYLIST_ID } from '$env/static/private';
BASE_URL,
YOUTUBE_CLIENT_ID,
YOUTUBE_CLIENT_SECRET,
YOUTUBE_PLAYLIST_ID
} from '$env/static/private';
import { Logger } from '$lib/log'; 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';
@ -17,6 +12,7 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
} }
public constructAuthUrl(redirectUri: URL): URL { public constructAuthUrl(redirectUri: URL): URL {
this.redirectUri = redirectUri;
let additionalParameters = new Map([ let additionalParameters = new Map([
['access_type', 'offline'], ['access_type', 'offline'],
['include_granted_scopes', 'false'] ['include_granted_scopes', 'false']
@ -33,6 +29,7 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
public async receivedAuthCode(code: string, url: URL) { public async receivedAuthCode(code: string, url: URL) {
this.logger.debug('received code'); this.logger.debug('received code');
this.redirectUri = url;
const tokenUrl = new URL('https://oauth2.googleapis.com/token'); const tokenUrl = new URL('https://oauth2.googleapis.com/token');
await this.receivedAuthCodeInternal( await this.receivedAuthCodeInternal(
tokenUrl, 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(); const tokenInfo = await this.shouldRefreshToken();
if (tokenInfo == null) { if (tokenInfo == null) {
return null; return null;
} }
let token = tokenInfo.token; let token = tokenInfo.token;
if (!tokenInfo.refresh) { if (!tokenInfo.refresh && !force) {
return token; return token;
} }
if (!token.refresh_token) { if (!token.refresh_token) {
@ -76,12 +73,17 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
tokenUrl, tokenUrl,
YOUTUBE_CLIENT_ID, YOUTUBE_CLIENT_ID,
token.refresh_token, token.refresh_token,
`${BASE_URL}/ytauth`, this.getRedirectUri('ytauth').toString(),
YOUTUBE_CLIENT_SECRET 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'); this.logger.debug('addToYoutubePlaylist');
const token = await this.refreshToken(); const token = await this.refreshToken();
if (token == null) { if (token == null) {
@ -139,7 +141,15 @@ export class YoutubePlaylistAdder extends OauthPlaylistAdder implements Playlist
const respObj = await resp.json(); const respObj = await resp.json();
this.logger.info('Added to playlist', youtubeId, song.title); this.logger.info('Added to playlist', youtubeId, song.title);
if (respObj.error) { 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, HASHTAG_FILTER,
MASTODON_ACCESS_TOKEN, MASTODON_ACCESS_TOKEN,
MASTODON_INSTANCE, MASTODON_INSTANCE,
IGNORE_USERS,
ODESLI_API_KEY, ODESLI_API_KEY,
YOUTUBE_API_KEY YOUTUBE_API_KEY
} from '$env/static/private'; } from '$env/static/private';
@ -48,6 +49,7 @@ export class TimelineReader {
private lastPosts: string[] = []; private lastPosts: string[] = [];
private playlistAdders: PlaylistAdder[]; private playlistAdders: PlaylistAdder[];
private logger: Logger; private logger: Logger;
private ignoredUsers: string[];
private async isMusicVideo(videoId: string) { private async isMusicVideo(videoId: string) {
if (!YOUTUBE_API_KEY || YOUTUBE_API_KEY === 'CHANGE_ME') { if (!YOUTUBE_API_KEY || YOUTUBE_API_KEY === 'CHANGE_ME') {
@ -372,6 +374,20 @@ export class TimelineReader {
} }
private async checkAndSavePost(post: Post) { 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 hashttags: string[] = HASHTAG_FILTER.split(',');
const found_tags: Tag[] = post.tags.filter((t: Tag) => hashttags.includes(t.name)); 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 = new Logger('Timeline');
this.logger.log('Constructing timeline object'); this.logger.log('Constructing timeline object');
this.playlistAdders = [new YoutubePlaylistAdder(), new SpotifyPlaylistAdder()]; 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.startWebsocket();
this.loadPostsSinceLastRun() this.loadPostsSinceLastRun()

View File

@ -7,9 +7,10 @@ const { DEV } = import.meta.env;
const logger = new Logger('SpotifyAuth'); 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(); 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) { if (url.hostname === 'localhost' && DEV) {
redirect_uri.hostname = '127.0.0.1'; redirect_uri.hostname = '127.0.0.1';
} }

View File

@ -6,9 +6,11 @@ import type { PageServerLoad } from './$types';
const logger = new Logger('YT Auth'); 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 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')) { if (url.searchParams.has('code')) {
logger.debug(url.searchParams); logger.debug(url.searchParams);
await adder.receivedAuthCode(url.searchParams.get('code') || '', redirect_uri); await adder.receivedAuthCode(url.searchParams.get('code') || '', redirect_uri);