mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-18 21:52:05 +00:00
Compare commits
6 Commits
789a30312b
...
26fa1f85b2
| Author | SHA1 | Date | |
|---|---|---|---|
| 26fa1f85b2 | |||
| 555817e2f5 | |||
| f8654dfdb9 | |||
| 96c0fc412c | |||
| a70a4106df | |||
| 895210cbb6 |
@ -31,6 +31,7 @@ export interface DefaultConfig {
|
|||||||
likeButtons: string;
|
likeButtons: string;
|
||||||
proxy: string;
|
proxy: string;
|
||||||
startingPage: string;
|
startingPage: string;
|
||||||
|
backgroundMaterial?: 'none' | 'mica' | 'acrylic' | 'tabbed';
|
||||||
overrideUserAgent: boolean;
|
overrideUserAgent: boolean;
|
||||||
usePodcastParticipantAsArtist: boolean;
|
usePodcastParticipantAsArtist: boolean;
|
||||||
themes: string[];
|
themes: string[];
|
||||||
|
|||||||
@ -853,6 +853,26 @@
|
|||||||
"description": "Fügt ein TouchBar-Widget für macOS-Benutzer hinzu",
|
"description": "Fügt ein TouchBar-Widget für macOS-Benutzer hinzu",
|
||||||
"name": "TouchBar"
|
"name": "TouchBar"
|
||||||
},
|
},
|
||||||
|
"transparent-player": {
|
||||||
|
"description": "Macht das Player-Fenster transparent",
|
||||||
|
"name": "Transparent Player",
|
||||||
|
"menu": {
|
||||||
|
"opacity": {
|
||||||
|
"label": "Hintergrund-Sichtbarkeit",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"label": "Typ",
|
||||||
|
"submenu": {
|
||||||
|
"acrylic": "Acrylic",
|
||||||
|
"mica": "Mica",
|
||||||
|
"tabbed": "Tabbed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"tuna-obs": {
|
"tuna-obs": {
|
||||||
"description": "Integration mit dem OBS-Plugin Tuna",
|
"description": "Integration mit dem OBS-Plugin Tuna",
|
||||||
"name": "Tuna OBS"
|
"name": "Tuna OBS"
|
||||||
|
|||||||
@ -758,6 +758,7 @@
|
|||||||
"token": "Enter ListenBrainz user token"
|
"token": "Enter ListenBrainz user token"
|
||||||
},
|
},
|
||||||
"scrobble-alternative-title": "Use alternative titles",
|
"scrobble-alternative-title": "Use alternative titles",
|
||||||
|
"scrobble-alternative-artist": "Use alternative artists",
|
||||||
"scrobble-other-media": "Scrobble other media"
|
"scrobble-other-media": "Scrobble other media"
|
||||||
},
|
},
|
||||||
"name": "Scrobbler",
|
"name": "Scrobbler",
|
||||||
@ -874,6 +875,27 @@
|
|||||||
"description": "Adds a TouchBar widget for macOS users",
|
"description": "Adds a TouchBar widget for macOS users",
|
||||||
"name": "TouchBar"
|
"name": "TouchBar"
|
||||||
},
|
},
|
||||||
|
"transparent-player": {
|
||||||
|
"description": "Makes the app window transparent",
|
||||||
|
"name": "Transparent Player",
|
||||||
|
"menu": {
|
||||||
|
"opacity": {
|
||||||
|
"label": "Opacity",
|
||||||
|
"submenu": {
|
||||||
|
"percent": "{{opacity}}%"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"label": "Type",
|
||||||
|
"submenu": {
|
||||||
|
"acrylic": "Acrylic",
|
||||||
|
"mica": "Mica",
|
||||||
|
"tabbed": "Tabbed",
|
||||||
|
"none": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"tuna-obs": {
|
"tuna-obs": {
|
||||||
"description": "Integration with OBS's plugin Tuna",
|
"description": "Integration with OBS's plugin Tuna",
|
||||||
"name": "Tuna OBS"
|
"name": "Tuna OBS"
|
||||||
|
|||||||
@ -421,6 +421,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"custom-output-device": {
|
||||||
|
"description": "Configure um dispositivo de saída de mídia personalizado para músicas",
|
||||||
|
"menu": {
|
||||||
|
"device-selector": "Selecionar dispositivo"
|
||||||
|
},
|
||||||
|
"name": "Dispositivo de saída personalizado",
|
||||||
|
"prompt": {
|
||||||
|
"device-selector": {
|
||||||
|
"label": "Escolha o dispositivo de saída de mídia que será usado",
|
||||||
|
"title": "Selecionar dispositivo de saída"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"disable-autoplay": {
|
"disable-autoplay": {
|
||||||
"description": "Faz a música começar no modo \"pausado\"",
|
"description": "Faz a música começar no modo \"pausado\"",
|
||||||
"menu": {
|
"menu": {
|
||||||
@ -444,7 +457,15 @@
|
|||||||
"hide-duration-left": "Ocultar duração restante",
|
"hide-duration-left": "Ocultar duração restante",
|
||||||
"hide-github-button": "Ocultar botão do GitHub",
|
"hide-github-button": "Ocultar botão do GitHub",
|
||||||
"play-on-youtube-music": "Reproduzir no YouTube Music",
|
"play-on-youtube-music": "Reproduzir no YouTube Music",
|
||||||
"set-inactivity-timeout": "Definir tempo limite de inatividade"
|
"set-inactivity-timeout": "Definir tempo limite de inatividade",
|
||||||
|
"set-status-display-type": {
|
||||||
|
"label": "Texto de status",
|
||||||
|
"submenu": {
|
||||||
|
"artist": "Ouvindo {artist}",
|
||||||
|
"title": "Ouvindo {song title}",
|
||||||
|
"youtube-music": "Ouvindo YouTube Music"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"name": "Rich Presence do Discord",
|
"name": "Rich Presence do Discord",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|||||||
@ -421,6 +421,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"custom-output-device": {
|
||||||
|
"description": "Настройка устройства вывода медиа для песен",
|
||||||
|
"menu": {
|
||||||
|
"device-selector": "Выберите устройство"
|
||||||
|
},
|
||||||
|
"name": "Пользовательское устройство вывода",
|
||||||
|
"prompt": {
|
||||||
|
"device-selector": {
|
||||||
|
"label": "Выберите устройство вывода медиа, которое будет использоваться",
|
||||||
|
"title": "Выберите устройство вывода"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"disable-autoplay": {
|
"disable-autoplay": {
|
||||||
"description": "Запускает песню сразу на паузе",
|
"description": "Запускает песню сразу на паузе",
|
||||||
"menu": {
|
"menu": {
|
||||||
@ -444,7 +457,15 @@
|
|||||||
"hide-duration-left": "Скрыть сколько осталось времени",
|
"hide-duration-left": "Скрыть сколько осталось времени",
|
||||||
"hide-github-button": "Скрыть ссылку на GitHub",
|
"hide-github-button": "Скрыть ссылку на GitHub",
|
||||||
"play-on-youtube-music": "Воспроизвести на YouTube Music",
|
"play-on-youtube-music": "Воспроизвести на YouTube Music",
|
||||||
"set-inactivity-timeout": "Поставить таймер неактивности"
|
"set-inactivity-timeout": "Поставить таймер неактивности",
|
||||||
|
"set-status-display-type": {
|
||||||
|
"label": "Текст статуса",
|
||||||
|
"submenu": {
|
||||||
|
"artist": "Слушает {исполнитель}",
|
||||||
|
"title": "Слушает {название трека}",
|
||||||
|
"youtube-music": "Слушает YouTube Music"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"name": "Discord Rich Presence",
|
"name": "Discord Rich Presence",
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|||||||
36
src/index.ts
36
src/index.ts
@ -59,7 +59,7 @@ import ErrorHtmlAsset from '@assets/error.html?asset';
|
|||||||
|
|
||||||
import { defaultAuthProxyConfig } from '@/plugins/auth-proxy-adapter/config';
|
import { defaultAuthProxyConfig } from '@/plugins/auth-proxy-adapter/config';
|
||||||
|
|
||||||
import { type PluginConfig } from '@/types/plugins';
|
import type { PluginConfig } from '@/types/plugins';
|
||||||
|
|
||||||
// Catch errors and log them
|
// Catch errors and log them
|
||||||
unhandled({
|
unhandled({
|
||||||
@ -338,8 +338,8 @@ async function createMainWindow() {
|
|||||||
titleBarStyle: useInlineMenu
|
titleBarStyle: useInlineMenu
|
||||||
? 'hidden'
|
? 'hidden'
|
||||||
: is.macOS()
|
: is.macOS()
|
||||||
? 'hiddenInset'
|
? 'hiddenInset'
|
||||||
: 'default',
|
: 'default',
|
||||||
autoHideMenuBar: config.get('options.hideMenu'),
|
autoHideMenuBar: config.get('options.hideMenu'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -349,7 +349,7 @@ async function createMainWindow() {
|
|||||||
delete decorations.titleBarStyle;
|
delete decorations.titleBarStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = new BrowserWindow({
|
const electronWindowSettings: Electron.BrowserWindowConstructorOptions = {
|
||||||
icon,
|
icon,
|
||||||
width: windowSize.width,
|
width: windowSize.width,
|
||||||
height: windowSize.height,
|
height: windowSize.height,
|
||||||
@ -369,7 +369,10 @@ async function createMainWindow() {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
...decorations,
|
...decorations,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const win = new BrowserWindow(electronWindowSettings);
|
||||||
|
|
||||||
await initHook(win);
|
await initHook(win);
|
||||||
initTheme(win);
|
initTheme(win);
|
||||||
|
|
||||||
@ -529,8 +532,8 @@ app.once('browser-window-created', (_event, win) => {
|
|||||||
const updatedUserAgent = is.macOS()
|
const updatedUserAgent = is.macOS()
|
||||||
? userAgents.mac
|
? userAgents.mac
|
||||||
: is.windows()
|
: is.windows()
|
||||||
? userAgents.windows
|
? userAgents.windows
|
||||||
: userAgents.linux;
|
: userAgents.linux;
|
||||||
|
|
||||||
win.webContents.userAgent = updatedUserAgent;
|
win.webContents.userAgent = updatedUserAgent;
|
||||||
app.userAgentFallback = updatedUserAgent;
|
app.userAgentFallback = updatedUserAgent;
|
||||||
@ -951,15 +954,18 @@ function removeContentSecurityPolicy(
|
|||||||
betterSession.webRequest.setResolver(
|
betterSession.webRequest.setResolver(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
async (listeners) => {
|
async (listeners) => {
|
||||||
return listeners.reduce(async (accumulator, listener) => {
|
return listeners.reduce(
|
||||||
const acc = await accumulator;
|
async (accumulator, listener) => {
|
||||||
if (acc.cancel) {
|
const acc = await accumulator;
|
||||||
return acc;
|
if (acc.cancel) {
|
||||||
}
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await listener.apply();
|
const result = await listener.apply();
|
||||||
return { ...accumulator, ...result };
|
return { ...accumulator, ...result };
|
||||||
}, Promise.resolve({ cancel: false }));
|
},
|
||||||
|
Promise.resolve({ cancel: false }),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export default createPlugin<
|
|||||||
alpha?: number,
|
alpha?: number,
|
||||||
ratioMultiply?: number,
|
ratioMultiply?: number,
|
||||||
): string;
|
): string;
|
||||||
updateColor(): void;
|
updateColor(alpha: number): void;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -143,7 +143,16 @@ export default createPlugin<
|
|||||||
document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0');
|
document.documentElement.style.setProperty(DARK_COLOR_KEY, '0, 0, 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateColor();
|
let alpha: number | null = null;
|
||||||
|
if (await window.mainConfig.plugins.isEnabled('transparent-player')) {
|
||||||
|
const value: unknown = window.mainConfig.get(
|
||||||
|
'plugins.transparent-player.opacity',
|
||||||
|
);
|
||||||
|
if (typeof value === 'number' && value >= 0 && value <= 1) {
|
||||||
|
alpha = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateColor(alpha ?? 1);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onConfigChange(config) {
|
onConfigChange(config) {
|
||||||
@ -163,7 +172,7 @@ export default createPlugin<
|
|||||||
}
|
}
|
||||||
return `color-mix(in srgb, ${color} ${originalRatio}, ${keyColor} ${colorRatio})`;
|
return `color-mix(in srgb, ${color} ${originalRatio}, ${keyColor} ${colorRatio})`;
|
||||||
},
|
},
|
||||||
updateColor() {
|
updateColor(alpha: number) {
|
||||||
const variableMap = {
|
const variableMap = {
|
||||||
'--ytmusic-color-black1': '#212121',
|
'--ytmusic-color-black1': '#212121',
|
||||||
'--ytmusic-color-black2': '#181818',
|
'--ytmusic-color-black2': '#181818',
|
||||||
@ -202,19 +211,20 @@ export default createPlugin<
|
|||||||
Object.entries(variableMap).map(([variable, color]) => {
|
Object.entries(variableMap).map(([variable, color]) => {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
variable,
|
variable,
|
||||||
this.getMixedColor(color, COLOR_KEY),
|
this.getMixedColor(color, COLOR_KEY, alpha),
|
||||||
'important',
|
'important',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.style.setProperty(
|
document.body.style.setProperty(
|
||||||
'background',
|
'background',
|
||||||
this.getMixedColor('#030303', COLOR_KEY),
|
this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha),
|
||||||
'important',
|
'important',
|
||||||
);
|
);
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
'--ytmusic-background',
|
'--ytmusic-background',
|
||||||
this.getMixedColor('#030303', DARK_COLOR_KEY),
|
// #030303
|
||||||
|
this.getMixedColor('rgba(3, 3, 3)', DARK_COLOR_KEY, alpha),
|
||||||
'important',
|
'important',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -23,13 +23,3 @@ export enum TimerKey {
|
|||||||
UpdateTimeout = 'updateTimeout', // Timer for throttled activity updates
|
UpdateTimeout = 'updateTimeout', // Timer for throttled activity updates
|
||||||
DiscordConnectRetry = 'discordConnectRetry', // Timer for Discord connection retries
|
DiscordConnectRetry = 'discordConnectRetry', // Timer for Discord connection retries
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An enum for Discord's activity.status_display_type field, governing which field of the activity should be used after
|
|
||||||
* "Listening to..." in the user's Discord status.
|
|
||||||
*/
|
|
||||||
export const DiscordStatusDisplayType = {
|
|
||||||
YOUTUBE_MUSIC: 0,
|
|
||||||
ARTIST: 1,
|
|
||||||
TITLE: 2,
|
|
||||||
} as const;
|
|
||||||
|
|||||||
@ -99,9 +99,9 @@ export class DiscordService {
|
|||||||
const activityInfo: SetActivity = {
|
const activityInfo: SetActivity = {
|
||||||
type: ActivityType.Listening,
|
type: ActivityType.Listening,
|
||||||
statusDisplayType: config.statusDisplayType,
|
statusDisplayType: config.statusDisplayType,
|
||||||
details: truncateString(songInfo.title, 128), // Song title
|
details: truncateString(songInfo.alternativeTitle ?? songInfo.title, 128), // Song title
|
||||||
detailsUrl: songInfo.url ?? undefined,
|
detailsUrl: songInfo.url ?? undefined,
|
||||||
state: truncateString(songInfo.artist, 128), // Artist name
|
state: truncateString(songInfo.tags?.at(0) ?? songInfo.artist, 128), // Artist name
|
||||||
stateUrl: songInfo.artistUrl,
|
stateUrl: songInfo.artistUrl,
|
||||||
largeImageKey: songInfo.imageSrc ?? undefined,
|
largeImageKey: songInfo.imageSrc ?? undefined,
|
||||||
largeImageText: songInfo.album
|
largeImageText: songInfo.album
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
import { StatusDisplayType } from 'discord-api-types/v10';
|
||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import { backend } from './main';
|
import { backend } from './main';
|
||||||
import { onMenu } from './menu';
|
import { onMenu } from './menu';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
import { DiscordStatusDisplayType } from './constants';
|
|
||||||
|
|
||||||
export type DiscordPluginConfig = {
|
export type DiscordPluginConfig = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -37,7 +38,7 @@ export type DiscordPluginConfig = {
|
|||||||
/**
|
/**
|
||||||
* Controls which field is displayed in the Discord status text
|
* Controls which field is displayed in the Discord status text
|
||||||
*/
|
*/
|
||||||
statusDisplayType: (typeof DiscordStatusDisplayType)[keyof typeof DiscordStatusDisplayType];
|
statusDisplayType: (typeof StatusDisplayType)[keyof typeof StatusDisplayType];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createPlugin({
|
export default createPlugin({
|
||||||
@ -52,7 +53,7 @@ export default createPlugin({
|
|||||||
playOnYouTubeMusic: true,
|
playOnYouTubeMusic: true,
|
||||||
hideGitHubButton: false,
|
hideGitHubButton: false,
|
||||||
hideDurationLeft: false,
|
hideDurationLeft: false,
|
||||||
statusDisplayType: DiscordStatusDisplayType.ARTIST,
|
statusDisplayType: StatusDisplayType.Details,
|
||||||
} as DiscordPluginConfig,
|
} as DiscordPluginConfig,
|
||||||
menu: onMenu,
|
menu: onMenu,
|
||||||
backend,
|
backend,
|
||||||
|
|||||||
@ -1,30 +1,27 @@
|
|||||||
import prompt from 'custom-electron-prompt';
|
import prompt from 'custom-electron-prompt';
|
||||||
|
|
||||||
import { discordService } from './main';
|
import { StatusDisplayType } from 'discord-api-types/v10';
|
||||||
|
|
||||||
|
import { discordService } from './main';
|
||||||
import { singleton } from '@/providers/decorators';
|
import { singleton } from '@/providers/decorators';
|
||||||
import promptOptions from '@/providers/prompt-options';
|
import promptOptions from '@/providers/prompt-options';
|
||||||
import { setMenuOptions } from '@/config/plugins';
|
import { setMenuOptions } from '@/config/plugins';
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
import { DiscordStatusDisplayType } from './constants';
|
|
||||||
|
|
||||||
import type { MenuContext } from '@/types/contexts';
|
import type { MenuContext } from '@/types/contexts';
|
||||||
import type { DiscordPluginConfig } from './index';
|
import type { DiscordPluginConfig } from './index';
|
||||||
|
|
||||||
import type { MenuTemplate } from '@/menu';
|
import type { MenuTemplate } from '@/menu';
|
||||||
|
|
||||||
const registerRefreshOnce = singleton((refreshMenu: () => void) => {
|
const registerRefreshOnce = singleton((refreshMenu: () => void) => {
|
||||||
discordService?.registerRefreshCallback(refreshMenu);
|
discordService?.registerRefreshCallback(refreshMenu);
|
||||||
});
|
});
|
||||||
|
|
||||||
const DiscordStatusDisplayTypeLabels = {
|
const DiscordStatusDisplayTypeLabels: Record<StatusDisplayType, string> = {
|
||||||
[DiscordStatusDisplayType.YOUTUBE_MUSIC]:
|
[StatusDisplayType.Name]:
|
||||||
'plugins.discord.menu.set-status-display-type.submenu.youtube-music',
|
'plugins.discord.menu.set-status-display-type.submenu.youtube-music',
|
||||||
[DiscordStatusDisplayType.ARTIST]:
|
[StatusDisplayType.State]:
|
||||||
'plugins.discord.menu.set-status-display-type.submenu.artist',
|
'plugins.discord.menu.set-status-display-type.submenu.artist',
|
||||||
[DiscordStatusDisplayType.TITLE]:
|
[StatusDisplayType.Details]:
|
||||||
'plugins.discord.menu.set-status-display-type.submenu.title',
|
'plugins.discord.menu.set-status-display-type.submenu.title',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,18 +102,24 @@ export const onMenu = async ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('plugins.discord.menu.set-status-display-type.label'),
|
label: t('plugins.discord.menu.set-status-display-type.label'),
|
||||||
submenu: Object.values(DiscordStatusDisplayType).map(
|
submenu: Object.values(StatusDisplayType)
|
||||||
(statusDisplayType) => ({
|
.filter(
|
||||||
label: t(DiscordStatusDisplayTypeLabels[statusDisplayType]),
|
(v) => typeof StatusDisplayType[v as StatusDisplayType] !== 'number',
|
||||||
|
)
|
||||||
|
.map((statusDisplayType) => ({
|
||||||
|
label: t(
|
||||||
|
DiscordStatusDisplayTypeLabels[
|
||||||
|
statusDisplayType as StatusDisplayType
|
||||||
|
],
|
||||||
|
),
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
checked: config.statusDisplayType == statusDisplayType,
|
checked: config.statusDisplayType === statusDisplayType,
|
||||||
click() {
|
click() {
|
||||||
setConfig({
|
setConfig({
|
||||||
statusDisplayType,
|
statusDisplayType: statusDisplayType as StatusDisplayType,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
})),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,10 @@ import defaultConfig from '@/config/defaults';
|
|||||||
import { getSongMenu } from '@/providers/dom-elements';
|
import { getSongMenu } from '@/providers/dom-elements';
|
||||||
import { getSongInfo } from '@/providers/song-info-front';
|
import { getSongInfo } from '@/providers/song-info-front';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
import { isMusicOrVideoTrack } from '@/plugins/utils/renderer/check';
|
import {
|
||||||
|
isAlbumOrPlaylist,
|
||||||
|
isMusicOrVideoTrack,
|
||||||
|
} from '@/plugins/utils/renderer/check';
|
||||||
|
|
||||||
import { DownloadButton } from './templates/download';
|
import { DownloadButton } from './templates/download';
|
||||||
|
|
||||||
@ -25,7 +28,7 @@ const menuObserver = new MutationObserver(() => {
|
|||||||
if (
|
if (
|
||||||
!menu ||
|
!menu ||
|
||||||
menu.contains(buttonContainer) ||
|
menu.contains(buttonContainer) ||
|
||||||
!isMusicOrVideoTrack() ||
|
!(isMusicOrVideoTrack() || isAlbumOrPlaylist()) ||
|
||||||
!buttonContainer
|
!buttonContainer
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -329,6 +329,7 @@ export const TitleBar = (props: TitleBarProps) => {
|
|||||||
data-macos={props.isMacOS}
|
data-macos={props.isMacOS}
|
||||||
data-show={mouseY() < 32}
|
data-show={mouseY() < 32}
|
||||||
data-ytmd-main-panel={true}
|
data-ytmd-main-panel={true}
|
||||||
|
id={'ytmd-title-bar-main-panel'}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => setCollapsed(!collapsed())}
|
onClick={() => setCollapsed(!collapsed())}
|
||||||
|
|||||||
@ -13,11 +13,17 @@ export interface ScrobblerPluginConfig {
|
|||||||
*/
|
*/
|
||||||
scrobbleOtherMedia: boolean;
|
scrobbleOtherMedia: boolean;
|
||||||
/**
|
/**
|
||||||
* Use alternative titles for scrobbling (Useful for non-roman song titles)
|
* Use alternative titles for scrobbling (Useful for non-roman song titles, e.g. (Not) A Devil -> デビルじゃないもん)
|
||||||
*
|
*
|
||||||
* @default false
|
* @default true
|
||||||
*/
|
*/
|
||||||
alternativeTitles: boolean;
|
alternativeTitles: boolean;
|
||||||
|
/**
|
||||||
|
* Use alternative artist for scrobbling (e.g., DECO27 & (or) PinocchioP -> DECO27 / marasy -> まらしぃ)
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
alternativeArtist: boolean;
|
||||||
scrobblers: {
|
scrobblers: {
|
||||||
lastfm: {
|
lastfm: {
|
||||||
/**
|
/**
|
||||||
@ -77,7 +83,8 @@ export interface ScrobblerPluginConfig {
|
|||||||
export const defaultConfig: ScrobblerPluginConfig = {
|
export const defaultConfig: ScrobblerPluginConfig = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
scrobbleOtherMedia: true,
|
scrobbleOtherMedia: true,
|
||||||
alternativeTitles: false,
|
alternativeTitles: true,
|
||||||
|
alternativeArtist: true,
|
||||||
scrobblers: {
|
scrobblers: {
|
||||||
lastfm: {
|
lastfm: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
@ -105,6 +105,15 @@ export const onMenu = async ({
|
|||||||
setConfig(config);
|
setConfig(config);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.scrobbler.menu.scrobble-alternative-artist'),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: Boolean(config.alternativeArtist),
|
||||||
|
click(item) {
|
||||||
|
config.alternativeArtist = item.checked;
|
||||||
|
setConfig(config);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Last.fm',
|
label: 'Last.fm',
|
||||||
submenu: [
|
submenu: [
|
||||||
|
|||||||
@ -132,10 +132,15 @@ export class LastFmScrobbler extends ScrobblerBase {
|
|||||||
? songInfo.alternativeTitle
|
? songInfo.alternativeTitle
|
||||||
: songInfo.title;
|
: songInfo.title;
|
||||||
|
|
||||||
|
const artist =
|
||||||
|
config.alternativeArtist && songInfo.tags?.at(0) !== undefined
|
||||||
|
? songInfo.tags?.at(0)
|
||||||
|
: songInfo.artist;
|
||||||
|
|
||||||
const postData: LastFmSongData = {
|
const postData: LastFmSongData = {
|
||||||
track: title,
|
track: title,
|
||||||
duration: songInfo.songDuration,
|
duration: songInfo.songDuration,
|
||||||
artist: songInfo.artist,
|
artist: artist,
|
||||||
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
|
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
|
||||||
api_key: config.scrobblers.lastfm.apiKey,
|
api_key: config.scrobblers.lastfm.apiKey,
|
||||||
sk: config.scrobblers.lastfm.sessionKey,
|
sk: config.scrobblers.lastfm.sessionKey,
|
||||||
|
|||||||
@ -81,8 +81,13 @@ function createRequestBody(
|
|||||||
? songInfo.alternativeTitle
|
? songInfo.alternativeTitle
|
||||||
: songInfo.title;
|
: songInfo.title;
|
||||||
|
|
||||||
|
const artist =
|
||||||
|
config.alternativeArtist && songInfo.tags?.at(0) !== undefined
|
||||||
|
? songInfo.tags?.at(0)
|
||||||
|
: songInfo.artist;
|
||||||
|
|
||||||
const trackMetadata = {
|
const trackMetadata = {
|
||||||
artist_name: songInfo.artist,
|
artist_name: artist,
|
||||||
track_name: title,
|
track_name: title,
|
||||||
release_name: songInfo.album ?? undefined,
|
release_name: songInfo.album ?? undefined,
|
||||||
additional_info: {
|
additional_info: {
|
||||||
|
|||||||
112
src/plugins/transparent-player/index.ts
Normal file
112
src/plugins/transparent-player/index.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { t } from '@/i18n';
|
||||||
|
import { createPlugin } from '@/utils';
|
||||||
|
import { Platform } from '@/types/plugins';
|
||||||
|
|
||||||
|
import { MaterialType, type TransparentPlayerConfig } from './types';
|
||||||
|
|
||||||
|
import style from './style.css?inline';
|
||||||
|
|
||||||
|
import type { BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
const defaultConfig: TransparentPlayerConfig = {
|
||||||
|
enabled: false,
|
||||||
|
opacity: 0.5,
|
||||||
|
type: MaterialType.ACRYLIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const opacityList = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
||||||
|
const typeList = Object.values(MaterialType);
|
||||||
|
|
||||||
|
export default createPlugin({
|
||||||
|
name: () => t('plugins.transparent-player.name'),
|
||||||
|
description: () => t('plugins.transparent-player.description'),
|
||||||
|
addedVersion: '3.10.x',
|
||||||
|
restartNeeded: true,
|
||||||
|
platform: Platform.Windows,
|
||||||
|
config: defaultConfig,
|
||||||
|
stylesheets: [style],
|
||||||
|
async menu({ getConfig, setConfig }) {
|
||||||
|
const config = await getConfig();
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('plugins.transparent-player.menu.opacity.label'),
|
||||||
|
submenu: opacityList.map((opacity) => ({
|
||||||
|
label: t('plugins.transparent-player.menu.opacity.submenu.percent', {
|
||||||
|
opacity: opacity * 100,
|
||||||
|
}),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.opacity === opacity,
|
||||||
|
click() {
|
||||||
|
setConfig({ opacity });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.transparent-player.menu.type.label'),
|
||||||
|
submenu: typeList.map((type) => ({
|
||||||
|
label: t(`plugins.transparent-player.menu.type.submenu.${type}`),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.type === type,
|
||||||
|
click() {
|
||||||
|
setConfig({ type });
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
mainWindow: null as BrowserWindow | null,
|
||||||
|
async start({ window, getConfig }) {
|
||||||
|
this.mainWindow = window;
|
||||||
|
|
||||||
|
const config = await getConfig();
|
||||||
|
window.setBackgroundMaterial?.(config.type);
|
||||||
|
window.setBackgroundColor?.(`rgba(0, 0, 0, ${config.opacity})`);
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) {
|
||||||
|
this.mainWindow?.setBackgroundMaterial?.(newConfig.type);
|
||||||
|
},
|
||||||
|
stop({ window }) {
|
||||||
|
window.setBackgroundMaterial?.('none');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
props: {
|
||||||
|
enabled: defaultConfig.enabled,
|
||||||
|
opacity: defaultConfig.opacity,
|
||||||
|
type: defaultConfig.type,
|
||||||
|
} as TransparentPlayerConfig,
|
||||||
|
async start({ getConfig }) {
|
||||||
|
const config = await getConfig();
|
||||||
|
this.props = config;
|
||||||
|
if (config.enabled) {
|
||||||
|
document.body.classList.add('transparent-background-color');
|
||||||
|
document.body.classList.add('transparent-player-backdrop-filter');
|
||||||
|
|
||||||
|
if (!(await window.mainConfig.plugins.isEnabled('album-color-theme'))) {
|
||||||
|
document.body.classList.add('transparent-player');
|
||||||
|
}
|
||||||
|
this.applyVariables();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfigChange(newConfig) {
|
||||||
|
this.props = newConfig;
|
||||||
|
this.applyVariables();
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
document.body.classList.remove('transparent-background-color');
|
||||||
|
document.body.classList.remove('transparent-player-backdrop-filter');
|
||||||
|
document.body.classList.remove('transparent-player');
|
||||||
|
document.documentElement.style.removeProperty(
|
||||||
|
'--ytmd-transparent-player-opacity',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
applyVariables(this: { props: TransparentPlayerConfig }) {
|
||||||
|
const { opacity } = this.props;
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
'--ytmd-transparent-player-opacity',
|
||||||
|
opacity.toString(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
106
src/plugins/transparent-player/style.css
Normal file
106
src/plugins/transparent-player/style.css
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
:root {
|
||||||
|
--ytmd-transparent-player-transparency-color: #111;
|
||||||
|
--ytmd-transparent-player-transparent-background: rgb(
|
||||||
|
from var(--ytmd-transparent-player-transparency-color) r g b /
|
||||||
|
var(--ytmd-transparent-player-opacity, 0.5)
|
||||||
|
);
|
||||||
|
--ytmd-transparent-player-transparent-background-dark: rgb(
|
||||||
|
from var(--ytmd-transparent-player-transparency-color) r g b / 0.8
|
||||||
|
);
|
||||||
|
--ytmd-transparent-player-backdrop-blur: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.transparent-background-color {
|
||||||
|
background-color: var(--ytmd-transparent-player-transparent-background) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.transparent-player-backdrop-filter {
|
||||||
|
#layout {
|
||||||
|
#nav-bar-background,
|
||||||
|
#player-bar-background {
|
||||||
|
backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-page {
|
||||||
|
#tabs {
|
||||||
|
&.stuck {
|
||||||
|
backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-menu-popup-renderer {
|
||||||
|
backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ytmd-title-bar-main-panel {
|
||||||
|
backdrop-filter: var(--ytmd-transparent-player-backdrop-blur) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.transparent-player {
|
||||||
|
ytmusic-app {
|
||||||
|
ytmusic-app-layout[player-page-open] {
|
||||||
|
#nav-bar-background.ytmusic-app-layout,
|
||||||
|
#player-bar-background.ytmusic-app-layout {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#layout {
|
||||||
|
#nav-bar-background,
|
||||||
|
#player-bar-background {
|
||||||
|
background: var(--ytmd-transparent-player-transparent-background-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mini-guide-background {
|
||||||
|
background: none !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#guide {
|
||||||
|
#guide-wrapper {
|
||||||
|
background: none !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ytmusic-player-bar {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-page {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-page {
|
||||||
|
#tabs {
|
||||||
|
&.stuck {
|
||||||
|
background: var(--ytmd-transparent-player-transparent-background) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#browse-page {
|
||||||
|
#background {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-gradient {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Window Top Panel */
|
||||||
|
nav[data-ytmd-main-panel] {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Video Toggle Plugin */
|
||||||
|
.av-toggle.ytmusic-av-toggle {
|
||||||
|
background-color: var(--ytmd-transparent-player-transparent-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/plugins/transparent-player/types.ts
Normal file
12
src/plugins/transparent-player/types.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export enum MaterialType {
|
||||||
|
MICA = 'mica',
|
||||||
|
ACRYLIC = 'acrylic',
|
||||||
|
TABBED = 'tabbed',
|
||||||
|
NONE = 'none',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransparentPlayerConfig = {
|
||||||
|
enabled: boolean;
|
||||||
|
opacity: number;
|
||||||
|
type: MaterialType;
|
||||||
|
};
|
||||||
@ -22,6 +22,24 @@ export const isMusicOrVideoTrack = () => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isAlbumOrPlaylist = () => {
|
||||||
|
for (const menuSelector of document.querySelectorAll<
|
||||||
|
HTMLAnchorElement & {
|
||||||
|
data: {
|
||||||
|
addToPlaylistEndpoint: {
|
||||||
|
playlistId: string;
|
||||||
|
};
|
||||||
|
clickTrackingParams: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>('tp-yt-paper-listbox #navigation-endpoint')) {
|
||||||
|
if (menuSelector?.data?.addToPlaylistEndpoint?.playlistId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const isPlayerMenu = (menu?: HTMLElement | null) => {
|
export const isPlayerMenu = (menu?: HTMLElement | null) => {
|
||||||
return (
|
return (
|
||||||
menu?.parentElement as
|
menu?.parentElement as
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { fileURLToPath } from 'node:url';
|
|||||||
|
|
||||||
import { globSync } from 'glob';
|
import { globSync } from 'glob';
|
||||||
import { Project } from 'ts-morph';
|
import { Project } from 'ts-morph';
|
||||||
import { Platform } from '../src/types/plugins'
|
|
||||||
|
import { Platform } from '../src/types/plugins';
|
||||||
|
|
||||||
const kebabToCamel = (text: string) =>
|
const kebabToCamel = (text: string) =>
|
||||||
text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
|
text.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
|
||||||
@ -75,7 +76,7 @@ export const pluginVirtualModuleGenerator = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
writer.blankLine();
|
writer.blankLine();
|
||||||
if (mode === "main" || mode === "preload") {
|
if (mode === 'main' || mode === 'preload') {
|
||||||
writer.writeLine("import * as is from 'electron-is';");
|
writer.writeLine("import * as is from 'electron-is';");
|
||||||
writer.writeLine('globalThis.electronIs = is;');
|
writer.writeLine('globalThis.electronIs = is;');
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ export const pluginVirtualModuleGenerator = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
function supportsPlatform({ platform }: { platform: string }) {
|
function supportsPlatform({ platform }: { platform: string }) {
|
||||||
if (typeof platform !== "number") return true;
|
if (typeof platform !== 'number') return true;
|
||||||
|
|
||||||
const is = globalThis.electronIs;
|
const is = globalThis.electronIs;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user