mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 20:40:15 +02:00
16ac536f93
* feat(lyrics): simpmusic lyrics provider
363 lines
13 KiB
TypeScript
363 lines
13 KiB
TypeScript
import { mutationOptions, useMutation } from '@tanstack/react-query';
|
|
import dayjs from 'dayjs';
|
|
import utc from 'dayjs/plugin/utc';
|
|
import isElectron from 'is-electron';
|
|
import { useEffect, useRef } from 'react';
|
|
|
|
dayjs.extend(utc);
|
|
|
|
import packageJson from '../../../../../package.json';
|
|
|
|
import { isAnalyticsDisabled } from '/@/renderer/features/analytics/hooks/use-analytics-disabled';
|
|
import {
|
|
PlayerbarSliderType,
|
|
SideQueueType,
|
|
useAuthStore,
|
|
usePlayerStore,
|
|
useSettingsStore,
|
|
} from '/@/renderer/store';
|
|
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
|
import { logMsg } from '/@/renderer/utils/logger-message';
|
|
import { LyricSource, ServerType } from '/@/shared/types/domain-types';
|
|
import { FontType, Platform, PlayerStyle, PlayerType } from '/@/shared/types/types';
|
|
|
|
const utils = isElectron() ? window.api.utils : null;
|
|
let appTrackerInFlight = false;
|
|
let appTrackerLastSentDate: null | string = null;
|
|
|
|
const getVersion = (): AppTrackerProperties['_version'] => {
|
|
return packageJson.version;
|
|
};
|
|
|
|
const getPlatform = (): AppTrackerProperties['_platform'] => {
|
|
if (!isElectron()) {
|
|
return Platform.WEB;
|
|
}
|
|
|
|
if (utils?.isWindows()) {
|
|
return Platform.WINDOWS;
|
|
}
|
|
|
|
if (utils?.isMacOS()) {
|
|
return Platform.MACOS;
|
|
}
|
|
|
|
if (utils?.isLinux()) {
|
|
return Platform.LINUX;
|
|
}
|
|
|
|
return 'unknown';
|
|
};
|
|
|
|
type AppTrackerProperties = PlayerProperties &
|
|
SettingsProperties & {
|
|
_platform: 'unknown' | Platform;
|
|
_server: 'unknown' | ServerType;
|
|
_version: string;
|
|
};
|
|
|
|
type PlayerProperties = {
|
|
'player.mediaSession': boolean;
|
|
'player.style': PlayerStyle;
|
|
'player.transcoding': boolean;
|
|
'player.type': PlayerType;
|
|
'player.webAudio': boolean;
|
|
};
|
|
|
|
type SettingsProperties = {
|
|
'settings.albumBackground': boolean;
|
|
'settings.artistBackground': boolean;
|
|
'settings.autoDJ': boolean;
|
|
'settings.autoDJItemCount': number;
|
|
'settings.autoDJTiming': number;
|
|
'settings.customCss': boolean;
|
|
'settings.disableAutoUpdate': boolean;
|
|
'settings.discord': boolean;
|
|
'settings.exitToTray': boolean;
|
|
'settings.followSystemTheme': boolean;
|
|
'settings.fontType': FontType;
|
|
'settings.globalHotkeys': boolean;
|
|
'settings.homeFeature': boolean;
|
|
'settings.language': string;
|
|
'settings.lyrics.enableAutoTranslation': boolean;
|
|
'settings.lyrics.enableNeteaseTranslation': boolean;
|
|
'settings.lyrics.fetch': boolean;
|
|
'settings.lyrics.sources.genius': boolean;
|
|
'settings.lyrics.sources.lrclib': boolean;
|
|
'settings.lyrics.sources.netease': boolean;
|
|
'settings.minimizeToTray': boolean;
|
|
'settings.nativeAspectRatio': boolean;
|
|
'settings.playerbarSliderType': PlayerbarSliderType;
|
|
'settings.preventSleepOnPlayback': boolean;
|
|
'settings.releaseChannel': string;
|
|
'settings.resume': boolean;
|
|
'settings.scrobble.enabled': boolean;
|
|
'settings.scrobble.notify': boolean;
|
|
'settings.showLyricsInSidebar': boolean;
|
|
'settings.showVisualizerInSidebar': boolean;
|
|
'settings.sideQueueType': SideQueueType;
|
|
'settings.skipButtons': boolean;
|
|
'settings.startMinimized': boolean;
|
|
'settings.theme': string;
|
|
'settings.themeDark': string;
|
|
'settings.themeLight': string;
|
|
'settings.tray': boolean;
|
|
'settings.useThemeAccentColor': boolean;
|
|
'settings.useThemePrimaryShade': boolean;
|
|
'settings.windowBarStyle': Platform;
|
|
'settings.zoomFactor': number;
|
|
};
|
|
|
|
const getPlayerProperties = (): Pick<
|
|
AppTrackerProperties,
|
|
| 'player.mediaSession'
|
|
| 'player.style'
|
|
| 'player.transcoding'
|
|
| 'player.type'
|
|
| 'player.webAudio'
|
|
> => {
|
|
const player = usePlayerStore.getState();
|
|
const playbackSettings = useSettingsStore.getState().playback;
|
|
|
|
return {
|
|
'player.mediaSession': ignoreWeb(playbackSettings.mediaSession),
|
|
'player.style': player.player.transitionType,
|
|
'player.transcoding': playbackSettings.transcode.enabled,
|
|
'player.type': ignoreWeb(playbackSettings.type),
|
|
'player.webAudio': ignoreWeb(playbackSettings.webAudio),
|
|
} as any;
|
|
};
|
|
|
|
function ignoreWeb<T>(value: T): T | undefined {
|
|
return isElectron() ? value : undefined;
|
|
}
|
|
|
|
const getSettingsProperties = (): SettingsProperties => {
|
|
const settings = useSettingsStore.getState();
|
|
|
|
return {
|
|
'settings.albumBackground': settings.general.albumBackground,
|
|
// 'settings.albumBackgroundBlur': settings.general.albumBackgroundBlur,
|
|
'settings.artistBackground': settings.general.artistBackground,
|
|
// 'settings.artistBackgroundBlur': settings.general.artistBackgroundBlur,
|
|
'settings.autoDJ': settings.autoDJ.enabled,
|
|
'settings.autoDJItemCount': settings.autoDJ.itemCount,
|
|
'settings.autoDJTiming': settings.autoDJ.timing,
|
|
'settings.customCss': settings.css.enabled,
|
|
'settings.disableAutoUpdate': ignoreWeb(settings.window.disableAutoUpdate),
|
|
'settings.discord': ignoreWeb(settings.discord.enabled),
|
|
'settings.exitToTray': ignoreWeb(settings.window.exitToTray),
|
|
'settings.followSystemTheme': settings.general.followSystemTheme,
|
|
'settings.fontType': settings.font.type,
|
|
'settings.globalHotkeys': settings.hotkeys.globalMediaHotkeys,
|
|
'settings.homeFeature': settings.general.homeFeature,
|
|
'settings.language': settings.general.language,
|
|
// 'settings.lastFM': settings.general.lastFM,
|
|
'settings.lyrics.enableAutoTranslation': ignoreWeb(settings.lyrics.enableAutoTranslation),
|
|
'settings.lyrics.enableNeteaseTranslation': ignoreWeb(
|
|
settings.lyrics.enableNeteaseTranslation,
|
|
),
|
|
'settings.lyrics.fetch': ignoreWeb(settings.lyrics.fetch),
|
|
'settings.lyrics.sources.genius': ignoreWeb(
|
|
settings.lyrics.sources.includes(LyricSource.GENIUS),
|
|
),
|
|
'settings.lyrics.sources.lrclib': ignoreWeb(
|
|
settings.lyrics.sources.includes(LyricSource.LRCLIB),
|
|
),
|
|
'settings.lyrics.sources.netease': ignoreWeb(
|
|
settings.lyrics.sources.includes(LyricSource.NETEASE),
|
|
),
|
|
'settings.lyrics.sources.simpmusic': ignoreWeb(
|
|
settings.lyrics.sources.includes(LyricSource.SIMPMUSIC),
|
|
),
|
|
'settings.minimizeToTray': ignoreWeb(settings.window.minimizeToTray),
|
|
// 'settings.musicBrainz': settings.general.musicBrainz,
|
|
'settings.nativeAspectRatio': settings.general.nativeAspectRatio,
|
|
'settings.playerbarSliderType': settings.general.playerbarSlider
|
|
.type as PlayerbarSliderType,
|
|
// 'settings.playerbarWaveformAlign': settings.general.playerbarSlider.barAlign as BarAlign,
|
|
// 'settings.playerbarWaveformBarWidth': settings.general.playerbarSlider.barWidth,
|
|
// 'settings.playerbarWaveformGap': settings.general.playerbarSlider.barGap,
|
|
// 'settings.playerbarWaveformRadius': settings.general.playerbarSlider.barRadius,
|
|
'settings.preventSleepOnPlayback': ignoreWeb(settings.window.preventSleepOnPlayback),
|
|
'settings.releaseChannel': ignoreWeb(settings.window.releaseChannel),
|
|
'settings.resume': settings.general.resume,
|
|
'settings.scrobble.enabled': settings.playback.scrobble.enabled,
|
|
'settings.scrobble.notify': ignoreWeb(settings.playback.scrobble.notify),
|
|
'settings.showLyricsInSidebar': settings.general.showLyricsInSidebar,
|
|
'settings.showVisualizerInSidebar': settings.general.showVisualizerInSidebar,
|
|
'settings.sideQueueType': settings.general.sideQueueType,
|
|
// 'settings.skipBackwardSeconds': settings.general.skipButtons.skipBackwardSeconds,
|
|
'settings.skipButtons': settings.general.skipButtons.enabled,
|
|
// 'settings.skipForwardSeconds': settings.general.skipButtons.skipForwardSeconds,
|
|
'settings.startMinimized': ignoreWeb(settings.window.startMinimized),
|
|
'settings.theme': settings.general.theme,
|
|
'settings.themeDark': settings.general.themeDark,
|
|
'settings.themeLight': settings.general.themeLight,
|
|
'settings.tray': ignoreWeb(settings.window.tray),
|
|
'settings.useThemeAccentColor': settings.general.useThemeAccentColor,
|
|
'settings.useThemePrimaryShade': settings.general.useThemePrimaryShade,
|
|
'settings.windowBarStyle': ignoreWeb(settings.window.windowBarStyle),
|
|
'settings.zoomFactor': ignoreWeb(settings.general.zoomFactor),
|
|
} as any;
|
|
};
|
|
|
|
const getServer = (): 'unknown' | ServerType => {
|
|
const auth = useAuthStore.getState();
|
|
|
|
const currentServer = auth.currentServer;
|
|
|
|
if (currentServer) {
|
|
return currentServer.type;
|
|
}
|
|
|
|
const serverList = auth.serverList;
|
|
const server = Object.values(serverList)[0];
|
|
|
|
if (server) {
|
|
return server.type;
|
|
}
|
|
|
|
return 'unknown';
|
|
};
|
|
|
|
export const useAppTracker = () => {
|
|
const { mutate: trackAppMutation } = useMutation(appTrackerMutation);
|
|
const { mutate: trackAppViewMutation } = useMutation(appViewMutation);
|
|
const hasRunOnMountRef = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (!window.umami || isAnalyticsDisabled()) {
|
|
return;
|
|
}
|
|
|
|
const waitForServer = async (): Promise<void> => {
|
|
if (useAuthStore.getState().currentServer) {
|
|
return;
|
|
}
|
|
|
|
const pollInterval = 1000 * 60;
|
|
|
|
while (!useAuthStore.getState().currentServer) {
|
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
}
|
|
};
|
|
|
|
const getProperties = () => {
|
|
const platform = getPlatform();
|
|
const version = getVersion();
|
|
const server = getServer();
|
|
const playerProperties = getPlayerProperties();
|
|
const settingsProperties = getSettingsProperties();
|
|
|
|
const properties: AppTrackerProperties = {
|
|
_platform: platform,
|
|
_server: server,
|
|
_version: version,
|
|
...playerProperties,
|
|
...settingsProperties,
|
|
};
|
|
|
|
return properties;
|
|
};
|
|
|
|
const checkAndTrack = () => {
|
|
// Prevent multiple simultaneous requests
|
|
if (appTrackerInFlight) {
|
|
return;
|
|
}
|
|
|
|
const lastSentDate = localStorage.getItem('analytics_app_tracker_timestamp');
|
|
const lastTrackedDate = appTrackerLastSentDate ?? lastSentDate;
|
|
const todayUTC = dayjs.utc().format('YYYY-MM-DD');
|
|
|
|
// Only send if it's a new day in UTC (ensures once per 24 hours)
|
|
if (lastTrackedDate !== todayUTC) {
|
|
appTrackerInFlight = true;
|
|
const properties = getProperties();
|
|
logFn.info(logMsg[LogCategory.ANALYTICS].appTracked, {
|
|
category: LogCategory.ANALYTICS,
|
|
meta: { properties, todayUTC },
|
|
});
|
|
|
|
trackAppViewMutation(undefined, {
|
|
onError: () => {},
|
|
});
|
|
|
|
trackAppMutation(properties, {
|
|
onError: () => {},
|
|
onSettled: () => {
|
|
appTrackerInFlight = false;
|
|
},
|
|
onSuccess: () => {
|
|
// Only update timestamp on success to ensure we only send once per 24 hours
|
|
const utcDate = dayjs.utc().format('YYYY-MM-DD');
|
|
appTrackerLastSentDate = utcDate;
|
|
localStorage.setItem('analytics_app_tracker_timestamp', utcDate);
|
|
|
|
logFn.debug(logMsg[LogCategory.ANALYTICS].appTracked, {
|
|
category: LogCategory.ANALYTICS,
|
|
meta: { properties },
|
|
});
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
// Check immediately on mount
|
|
if (!hasRunOnMountRef.current) {
|
|
waitForServer().then(() => {
|
|
checkAndTrack();
|
|
});
|
|
hasRunOnMountRef.current = true;
|
|
}
|
|
|
|
const interval = setInterval(checkAndTrack, 1000 * 60 * 60);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [trackAppMutation, trackAppViewMutation]);
|
|
};
|
|
|
|
// Sends the app event to the analytics server which includes usage data
|
|
const appTrackerMutation = mutationOptions({
|
|
mutationFn: (properties: AppTrackerProperties) => {
|
|
try {
|
|
window.umami?.track((props) => ({
|
|
data: properties,
|
|
language: props.language,
|
|
name: 'app',
|
|
screen: props.screen,
|
|
website: props.website,
|
|
}));
|
|
return Promise.resolve();
|
|
} catch (error) {
|
|
return Promise.reject(error);
|
|
}
|
|
},
|
|
mutationKey: ['analytics', 'settings-tracker'],
|
|
onSuccess: () => {},
|
|
retry: false,
|
|
throwOnError: false,
|
|
});
|
|
|
|
// Sends a view event to the analytics server which only includes language, screen, and website
|
|
// and triggers a page view event
|
|
const appViewMutation = mutationOptions({
|
|
mutationFn: () => {
|
|
try {
|
|
window.umami?.track((props) => ({
|
|
language: props.language,
|
|
screen: props.screen,
|
|
website: props.website,
|
|
}));
|
|
return Promise.resolve();
|
|
} catch (error) {
|
|
return Promise.reject(error);
|
|
}
|
|
},
|
|
mutationKey: ['analytics', 'app-view'],
|
|
onSuccess: () => {},
|
|
retry: false,
|
|
throwOnError: false,
|
|
});
|