fix: remove xo, migration to eslint

This commit is contained in:
JellyBrick
2023-08-29 17:22:38 +09:00
parent 31a7588cee
commit c722896a73
142 changed files with 17210 additions and 18409 deletions

View File

@ -1,46 +1,49 @@
const { Notification } = require("electron");
const is = require("electron-is");
const registerCallback = require("../../providers/song-info");
const { notificationImage } = require("./utils");
const config = require("./config");
const { Notification } = require('electron');
const is = require('electron-is');
const { notificationImage } = require('./utils');
const config = require('./config');
const registerCallback = require('../../providers/song-info');
const notify = (info) => {
// Fill the notification with content
const notification = {
title: info.title || 'Playing',
body: info.artist,
icon: notificationImage(info),
silent: true,
urgency: config.get('urgency'),
};
// Fill the notification with content
const notification = {
title: info.title || "Playing",
body: info.artist,
icon: notificationImage(info),
silent: true,
urgency: config.get('urgency'),
};
// Send the notification
const currentNotification = new Notification(notification);
currentNotification.show();
// Send the notification
const currentNotification = new Notification(notification);
currentNotification.show()
return currentNotification;
return currentNotification;
};
const setup = () => {
let oldNotification;
let currentUrl;
let oldNotification;
let currentUrl;
registerCallback(songInfo => {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || config.get('unpauseNotification'))) {
// Close the old notification
oldNotification?.close();
currentUrl = songInfo.url;
// This fixes a weird bug that would cause the notification to be updated instead of showing
setTimeout(() => { oldNotification = notify(songInfo) }, 10);
}
});
}
registerCallback((songInfo) => {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || config.get('unpauseNotification'))) {
// Close the old notification
oldNotification?.close();
currentUrl = songInfo.url;
// This fixes a weird bug that would cause the notification to be updated instead of showing
setTimeout(() => {
oldNotification = notify(songInfo);
}, 10);
}
});
};
/** @param {Electron.BrowserWindow} win */
module.exports = (win, options) => {
// Register the callback for new song information
is.windows() && options.interactive ?
require("./interactive")(win) :
setup();
// Register the callback for new song information
is.windows() && options.interactive
? require('./interactive')(win)
: setup();
};

View File

@ -1,5 +1,5 @@
const { PluginConfig } = require("../../config/dynamic");
const { PluginConfig } = require('../../config/dynamic');
const config = new PluginConfig("notifications");
const config = new PluginConfig('notifications');
module.exports = { ...config };

View File

@ -1,151 +1,170 @@
const { notificationImage, icons, save_temp_icons, secondsToMinutes, ToastStyles } = require("./utils");
const path = require('node:path');
const { Notification, app, ipcMain } = require('electron');
const { notificationImage, icons, save_temp_icons, secondsToMinutes, ToastStyles } = require('./utils');
const config = require('./config');
const getSongControls = require('../../providers/song-controls');
const registerCallback = require("../../providers/song-info");
const { changeProtocolHandler } = require("../../providers/protocol-handler");
const { setTrayOnClick, setTrayOnDoubleClick } = require("../../tray");
const registerCallback = require('../../providers/song-info');
const { changeProtocolHandler } = require('../../providers/protocol-handler');
const { setTrayOnClick, setTrayOnDoubleClick } = require('../../tray');
const { Notification, app, ipcMain } = require("electron");
const path = require('path');
const config = require("./config");
let songControls;
let savedNotification;
/** @param {Electron.BrowserWindow} win */
module.exports = (win) => {
songControls = getSongControls(win);
songControls = getSongControls(win);
let currentSeconds = 0;
ipcMain.on('apiLoaded', () => win.webContents.send('setupTimeChangedListener'));
let currentSeconds = 0;
ipcMain.on('apiLoaded', () => win.webContents.send('setupTimeChangedListener'));
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
ipcMain.on('timeChanged', (_, t) => currentSeconds = t);
if (app.isPackaged) save_temp_icons();
if (app.isPackaged) {
save_temp_icons();
}
let savedSongInfo;
let lastUrl;
let savedSongInfo;
let lastUrl;
// Register songInfoCallback
registerCallback(songInfo => {
if (!songInfo.artist && !songInfo.title) return;
savedSongInfo = { ...songInfo };
if (!songInfo.isPaused &&
(songInfo.url !== lastUrl || config.get("unpauseNotification"))
) {
lastUrl = songInfo.url
sendNotification(songInfo);
}
});
if (config.get("trayControls")) {
setTrayOnClick(() => {
if (savedNotification) {
savedNotification.close();
savedNotification = undefined;
} else if (savedSongInfo) {
sendNotification({
...savedSongInfo,
elapsedSeconds: currentSeconds
})
}
});
setTrayOnDoubleClick(() => {
if (win.isVisible()) {
win.hide();
} else win.show();
})
// Register songInfoCallback
registerCallback((songInfo) => {
if (!songInfo.artist && !songInfo.title) {
return;
}
savedSongInfo = { ...songInfo };
if (!songInfo.isPaused
&& (songInfo.url !== lastUrl || config.get('unpauseNotification'))
) {
lastUrl = songInfo.url;
sendNotification(songInfo);
}
});
app.once("before-quit", () => {
savedNotification?.close();
if (config.get('trayControls')) {
setTrayOnClick(() => {
if (savedNotification) {
savedNotification.close();
savedNotification = undefined;
} else if (savedSongInfo) {
sendNotification({
...savedSongInfo,
elapsedSeconds: currentSeconds,
});
}
});
setTrayOnDoubleClick(() => {
if (win.isVisible()) {
win.hide();
} else {
win.show();
}
});
}
changeProtocolHandler(
(cmd) => {
if (Object.keys(songControls).includes(cmd)) {
songControls[cmd]();
if (config.get("refreshOnPlayPause") && (
cmd === 'pause' ||
(cmd === 'play' && !config.get("unpauseNotification"))
)
) {
setImmediate(() =>
sendNotification({
...savedSongInfo,
isPaused: cmd === 'pause',
elapsedSeconds: currentSeconds
})
);
}
}
app.once('before-quit', () => {
savedNotification?.close();
});
changeProtocolHandler(
(cmd) => {
if (Object.keys(songControls).includes(cmd)) {
songControls[cmd]();
if (config.get('refreshOnPlayPause') && (
cmd === 'pause'
|| (cmd === 'play' && !config.get('unpauseNotification'))
)
) {
setImmediate(() =>
sendNotification({
...savedSongInfo,
isPaused: cmd === 'pause',
elapsedSeconds: currentSeconds,
}),
);
}
)
}
}
},
);
};
function sendNotification(songInfo) {
const iconSrc = notificationImage(songInfo);
const iconSrc = notificationImage(songInfo);
savedNotification?.close();
savedNotification?.close();
savedNotification = new Notification({
title: songInfo.title || "Playing",
body: songInfo.artist,
icon: iconSrc,
silent: true,
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-schema
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml
// https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.toasttemplatetype
toastXml: get_xml(songInfo, iconSrc),
});
savedNotification = new Notification({
title: songInfo.title || 'Playing',
body: songInfo.artist,
icon: iconSrc,
silent: true,
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-schema
// https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml
// https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.toasttemplatetype
toastXml: get_xml(songInfo, iconSrc),
});
savedNotification.on("close", (_) => {
savedNotification = undefined;
});
savedNotification.on('close', (_) => {
savedNotification = undefined;
});
savedNotification.show();
savedNotification.show();
}
const get_xml = (songInfo, iconSrc) => {
switch (config.get("toastStyle")) {
default:
case ToastStyles.logo:
case ToastStyles.legacy:
return xml_logo(songInfo, iconSrc);
case ToastStyles.banner_top_custom:
return xml_banner_top_custom(songInfo, iconSrc);
case ToastStyles.hero:
return xml_hero(songInfo, iconSrc);
case ToastStyles.banner_bottom:
return xml_banner_bottom(songInfo, iconSrc);
case ToastStyles.banner_centered_bottom:
return xml_banner_centered_bottom(songInfo, iconSrc);
case ToastStyles.banner_centered_top:
return xml_banner_centered_top(songInfo, iconSrc);
};
}
switch (config.get('toastStyle')) {
default:
case ToastStyles.logo:
case ToastStyles.legacy: {
return xml_logo(songInfo, iconSrc);
}
const iconLocation = app.isPackaged ?
path.resolve(app.getPath("userData"), 'icons') :
path.resolve(__dirname, '..', '..', 'assets/media-icons-black');
case ToastStyles.banner_top_custom: {
return xml_banner_top_custom(songInfo, iconSrc);
}
case ToastStyles.hero: {
return xml_hero(songInfo, iconSrc);
}
case ToastStyles.banner_bottom: {
return xml_banner_bottom(songInfo, iconSrc);
}
case ToastStyles.banner_centered_bottom: {
return xml_banner_centered_bottom(songInfo, iconSrc);
}
case ToastStyles.banner_centered_top: {
return xml_banner_centered_top(songInfo, iconSrc);
}
}
};
const iconLocation = app.isPackaged
? path.resolve(app.getPath('userData'), 'icons')
: path.resolve(__dirname, '..', '..', 'assets/media-icons-black');
const display = (kind) => {
if (config.get("toastStyle") === ToastStyles.legacy) {
return `content="${icons[kind]}"`;
} else {
return `\
content="${config.get("hideButtonText") ? "" : kind.charAt(0).toUpperCase() + kind.slice(1)}"\
if (config.get('toastStyle') === ToastStyles.legacy) {
return `content="${icons[kind]}"`;
}
return `\
content="${config.get('hideButtonText') ? '' : kind.charAt(0).toUpperCase() + kind.slice(1)}"\
imageUri="file:///${path.resolve(__dirname, iconLocation, `${kind}.png`)}"
`;
}
}
};
const getButton = (kind) =>
`<action ${display(kind)} activationType="protocol" arguments="youtubemusic://${kind}"/>`;
`<action ${display(kind)} activationType="protocol" arguments="youtubemusic://${kind}"/>`;
const getButtons = (isPaused) => `\
<actions>
@ -173,7 +192,6 @@ const xml_image = ({ title, artist, isPaused }, imgSrc, placement) => toast(`\
<text id="2">${artist}</text>\
`, isPaused);
const xml_logo = (songInfo, imgSrc) => xml_image(songInfo, imgSrc, 'placement="appLogoOverride"');
const xml_hero = (songInfo, imgSrc) => xml_image(songInfo, imgSrc, 'placement="hero"');
@ -194,8 +212,8 @@ const xml_banner_top_custom = (songInfo, imgSrc) => toast(`\
const xml_more_data = ({ album, elapsedSeconds, songDuration }) => `\
<subgroup hint-textStacking="bottom">
${album ?
`<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${album}</text>` : ''}
${album
? `<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${album}</text>` : ''}
<text hint-style="captionSubtle" hint-wrap="true" hint-align="right">${secondsToMinutes(elapsedSeconds)} / ${secondsToMinutes(songDuration)}</text>
</subgroup>\
`;
@ -223,13 +241,17 @@ const xml_banner_centered_top = ({ title, artist, isPaused }, imgSrc) => toast(`
`, isPaused);
const titleFontPicker = (title) => {
if (title.length <= 13) {
return 'Header';
} else if (title.length <= 22) {
return 'Subheader';
} else if (title.length <= 26) {
return 'Title';
} else {
return 'Subtitle';
}
}
if (title.length <= 13) {
return 'Header';
}
if (title.length <= 22) {
return 'Subheader';
}
if (title.length <= 26) {
return 'Title';
}
return 'Subtitle';
};

View File

@ -1,80 +1,81 @@
const { urgencyLevels, ToastStyles, snakeToCamel } = require("./utils");
const is = require("electron-is");
const config = require("./config");
const is = require('electron-is');
const { urgencyLevels, ToastStyles, snakeToCamel } = require('./utils');
const config = require('./config');
module.exports = (_win, options) => [
...(is.linux()
? [
{
label: "Notification Priority",
submenu: urgencyLevels.map((level) => ({
label: level.name,
type: "radio",
checked: options.urgency === level.value,
click: () => config.set("urgency", level.value),
})),
},
]
: []),
...(is.windows()
? [
{
label: "Interactive Notifications",
type: "checkbox",
checked: options.interactive,
// doesn't update until restart
click: (item) => config.setAndMaybeRestart("interactive", item.checked),
},
{
// submenu with settings for interactive notifications (name shouldn't be too long)
label: "Interactive Settings",
submenu: [
{
label: "Open/Close on tray click",
type: "checkbox",
checked: options.trayControls,
click: (item) => config.set("trayControls", item.checked),
},
{
label: "Hide Button Text",
type: "checkbox",
checked: options.hideButtonText,
click: (item) => config.set("hideButtonText", item.checked),
},
{
label: "Refresh on Play/Pause",
type: "checkbox",
checked: options.refreshOnPlayPause,
click: (item) => config.set("refreshOnPlayPause", item.checked),
}
]
},
{
label: "Style",
submenu: getToastStyleMenuItems(options)
},
]
: []),
{
label: "Show notification on unpause",
type: "checkbox",
checked: options.unpauseNotification,
click: (item) => config.set("unpauseNotification", item.checked),
},
...(is.linux()
? [
{
label: 'Notification Priority',
submenu: urgencyLevels.map((level) => ({
label: level.name,
type: 'radio',
checked: options.urgency === level.value,
click: () => config.set('urgency', level.value),
})),
},
]
: []),
...(is.windows()
? [
{
label: 'Interactive Notifications',
type: 'checkbox',
checked: options.interactive,
// Doesn't update until restart
click: (item) => config.setAndMaybeRestart('interactive', item.checked),
},
{
// Submenu with settings for interactive notifications (name shouldn't be too long)
label: 'Interactive Settings',
submenu: [
{
label: 'Open/Close on tray click',
type: 'checkbox',
checked: options.trayControls,
click: (item) => config.set('trayControls', item.checked),
},
{
label: 'Hide Button Text',
type: 'checkbox',
checked: options.hideButtonText,
click: (item) => config.set('hideButtonText', item.checked),
},
{
label: 'Refresh on Play/Pause',
type: 'checkbox',
checked: options.refreshOnPlayPause,
click: (item) => config.set('refreshOnPlayPause', item.checked),
},
],
},
{
label: 'Style',
submenu: getToastStyleMenuItems(options),
},
]
: []),
{
label: 'Show notification on unpause',
type: 'checkbox',
checked: options.unpauseNotification,
click: (item) => config.set('unpauseNotification', item.checked),
},
];
function getToastStyleMenuItems(options) {
const arr = new Array(Object.keys(ToastStyles).length);
const array = Array.from({ length: Object.keys(ToastStyles).length });
// ToastStyles index starts from 1
for (const [name, index] of Object.entries(ToastStyles)) {
arr[index - 1] = {
label: snakeToCamel(name),
type: "radio",
checked: options.toastStyle === index,
click: () => config.set("toastStyle", index),
};
}
// ToastStyles index starts from 1
for (const [name, index] of Object.entries(ToastStyles)) {
array[index - 1] = {
label: snakeToCamel(name),
type: 'radio',
checked: options.toastStyle === index,
click: () => config.set('toastStyle', index),
};
}
return arr;
return array;
}

View File

@ -1,93 +1,107 @@
const path = require("path");
const { app } = require("electron");
const fs = require("fs");
const config = require("./config");
const path = require('node:path');
const icon = "assets/youtube-music.png";
const userData = app.getPath("userData");
const tempIcon = path.join(userData, "tempIcon.png");
const tempBanner = path.join(userData, "tempBanner.png");
const fs = require('node:fs');
const { cache } = require("../../providers/decorators")
const { app } = require('electron');
const config = require('./config');
const icon = 'assets/youtube-music.png';
const userData = app.getPath('userData');
const temporaryIcon = path.join(userData, 'tempIcon.png');
const temporaryBanner = path.join(userData, 'tempBanner.png');
const { cache } = require('../../providers/decorators');
module.exports.ToastStyles = {
logo: 1,
banner_centered_top: 2,
hero: 3,
banner_top_custom: 4,
banner_centered_bottom: 5,
banner_bottom: 6,
legacy: 7
}
logo: 1,
banner_centered_top: 2,
hero: 3,
banner_top_custom: 4,
banner_centered_bottom: 5,
banner_bottom: 6,
legacy: 7,
};
module.exports.icons = {
play: "\u{1405}", // ᐅ
pause: "\u{2016}", // ‖
next: "\u{1433}", //
previous: "\u{1438}" //
}
play: '\u{1405}', // ᐅ
pause: '\u{2016}', // ‖
next: '\u{1433}', //
previous: '\u{1438}', //
};
module.exports.urgencyLevels = [
{ name: "Low", value: "low" },
{ name: "Normal", value: "normal" },
{ name: "High", value: "critical" },
{ name: 'Low', value: 'low' },
{ name: 'Normal', value: 'normal' },
{ name: 'High', value: 'critical' },
];
const nativeImageToLogo = cache((nativeImage) => {
const tempImage = nativeImage.resize({ height: 256 });
const margin = Math.max(tempImage.getSize().width - 256, 0);
const temporaryImage = nativeImage.resize({ height: 256 });
const margin = Math.max(temporaryImage.getSize().width - 256, 0);
return tempImage.crop({
x: Math.round(margin / 2),
y: 0,
width: 256,
height: 256,
});
return temporaryImage.crop({
x: Math.round(margin / 2),
y: 0,
width: 256,
height: 256,
});
});
module.exports.notificationImage = (songInfo) => {
if (!songInfo.image) return icon;
if (!config.get("interactive")) return nativeImageToLogo(songInfo.image);
if (!songInfo.image) {
return icon;
}
switch (config.get("toastStyle")) {
case module.exports.ToastStyles.logo:
case module.exports.ToastStyles.legacy:
return this.saveImage(nativeImageToLogo(songInfo.image), tempIcon);
default:
return this.saveImage(songInfo.image, tempBanner);
};
if (!config.get('interactive')) {
return nativeImageToLogo(songInfo.image);
}
switch (config.get('toastStyle')) {
case module.exports.ToastStyles.logo:
case module.exports.ToastStyles.legacy: {
return this.saveImage(nativeImageToLogo(songInfo.image), temporaryIcon);
}
default: {
return this.saveImage(songInfo.image, temporaryBanner);
}
}
};
module.exports.saveImage = cache((img, save_path) => {
try {
fs.writeFileSync(save_path, img.toPNG());
} catch (err) {
console.log(`Error writing song icon to disk:\n${err.toString()}`)
return icon;
}
return save_path;
try {
fs.writeFileSync(save_path, img.toPNG());
} catch (error) {
console.log(`Error writing song icon to disk:\n${error.toString()}`);
return icon;
}
return save_path;
});
module.exports.save_temp_icons = () => {
for (const kind of Object.keys(module.exports.icons)) {
const destinationPath = path.join(userData, 'icons', `${kind}.png`);
if (fs.existsSync(destinationPath)) continue;
const iconPath = path.resolve(__dirname, "../../assets/media-icons-black", `${kind}.png`);
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
fs.copyFile(iconPath, destinationPath, () => { });
}
for (const kind of Object.keys(module.exports.icons)) {
const destinationPath = path.join(userData, 'icons', `${kind}.png`);
if (fs.existsSync(destinationPath)) {
continue;
}
const iconPath = path.resolve(__dirname, '../../assets/media-icons-black', `${kind}.png`);
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
fs.copyFile(iconPath, destinationPath, () => {
});
}
};
module.exports.snakeToCamel = (str) => {
return str.replace(/([-_][a-z]|^[a-z])/g, (group) =>
group.toUpperCase()
.replace('-', ' ')
.replace('_', ' ')
);
}
module.exports.snakeToCamel = (string_) => string_.replaceAll(/([-_][a-z]|^[a-z])/g, (group) =>
group.toUpperCase()
.replace('-', ' ')
.replace('_', ' '),
);
module.exports.secondsToMinutes = (seconds) => {
const minutes = Math.floor(seconds / 60);
const secondsLeft = seconds % 60;
return `${minutes}:${secondsLeft < 10 ? '0' : ''}${secondsLeft}`;
}
const minutes = Math.floor(seconds / 60);
const secondsLeft = seconds % 60;
return `${minutes}:${secondsLeft < 10 ? '0' : ''}${secondsLeft}`;
};