Skip to content

Commit

Permalink
Merge branch 'master' into custom-electron-prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
Araxeus authored Oct 19, 2021
2 parents 65eaaec + f2e04f9 commit 1cd4f53
Show file tree
Hide file tree
Showing 20 changed files with 834 additions and 229 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Setup NodeJS
uses: actions/setup-node@v1
with:
node-version: "12.x"
node-version: "14.x"

- name: Get yarn cache directory path
id: yarn-cache-dir-path
Expand Down
3 changes: 2 additions & 1 deletion config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ const defaultConfig = {
discord: {
enabled: false,
activityTimoutEnabled: true, // if enabled, the discord rich presence gets cleared when music paused after the time specified below
activityTimoutTime: 10 * 60 * 1000 // 10 minutes
activityTimoutTime: 10 * 60 * 1000, // 10 minutes
listenAlong: true, // add a "listen along" button to rich presence
},
notifications: {
enabled: false,
Expand Down
8 changes: 6 additions & 2 deletions config/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ const Store = require("electron-store");
const defaults = require("./defaults");

const migrations = {
/** Update shortcuts format from array to object */
">=1.12.0": (store) => {
">=1.13.0": (store) => {
if (store.get("plugins.discord.listenAlong") === undefined) {
store.set("plugins.discord.listenAlong", true);
}
},
">=1.12.0": (store) => {
const options = store.get("plugins.shortcuts")
let updated = false;
for (const optionType of ["global", "local"]) {
Expand Down
14 changes: 12 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,18 @@ app.once("browser-window-created", (event, win) => {
// Force user-agent "Firefox Windows" for Google OAuth to work
// From https://github.com/firebase/firebase-js-sdk/issues/2478#issuecomment-571356751
// Only set on accounts.google.com, otherwise querySelectors in preload scripts fail (?)
const userAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0";
// Uses custom user agent to Google alert with a correct device type (https://github.com/th-ch/youtube-music/issues/327)
// User agents are from https://developers.whatismybrowser.com/useragents/explore/
const userAgents = {
mac: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0",
windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0",
linux: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0",
}

const userAgent =
is.macOS() ? userAgents.mac :
is.windows() ? userAgents.windows :
userAgents.linux;

win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
details.requestHeaders["User-Agent"] = userAgent;
Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "youtube-music",
"productName": "YouTube Music",
"version": "1.12.2",
"version": "1.13.0",
"description": "YouTube Music Desktop App - including custom plugins",
"license": "MIT",
"repository": "th-ch/youtube-music",
Expand Down Expand Up @@ -60,15 +60,15 @@
"release:win": "yarn run clean && electron-builder --win -p always"
},
"engines": {
"node": ">=12.20",
"node": ">=14.0.0",
"npm": "Please use yarn and not npm"
},
"dependencies": {
"@cliqz/adblocker-electron": "^1.22.1",
"@cliqz/adblocker-electron": "^1.22.6",
"@ffmpeg/core": "^0.10.0",
"@ffmpeg/ffmpeg": "^0.10.0",
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
"async-mutex": "^0.3.1",
"async-mutex": "^0.3.2",
"browser-id3-writer": "^4.4.0",
"custom-electron-prompt": "^1.1.0",
"chokidar": "^3.5.2",
Expand All @@ -80,16 +80,17 @@
"electron-localshortcut": "^3.2.1",
"electron-store": "^7.0.3",
"electron-unhandled": "^3.0.2",
"electron-updater": "^4.3.10",
"electron-updater": "^4.4.6",
"filenamify": "^4.3.0",
"md5": "^2.3.0",
"node-fetch": "^2.6.1",
"mpris-service": "^2.1.2",
"node-fetch": "^2.6.2",
"node-notifier": "^9.0.1",
"ytdl-core": "^4.9.1",
"ytpl": "^2.2.3"
},
"devDependencies": {
"electron": "^12.0.8",
"electron": "^12.1.0",
"electron-builder": "^22.10.5",
"electron-devtools-installer": "^3.1.1",
"electron-icon-maker": "0.0.5",
Expand Down
183 changes: 138 additions & 45 deletions plugins/discord/back.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,151 @@
const Discord = require("discord-rpc");
const { dev } = require("electron-is");
const { dialog } = require("electron");

const registerCallback = require("../../providers/song-info");

const rpc = new Discord.Client({
transport: "ipc",
});

// Application ID registered by @semvis123
const clientId = "790655993809338398";

/**
* @typedef {Object} Info
* @property {import('discord-rpc').Client} rpc
* @property {boolean} ready
* @property {import('../../providers/song-info').SongInfo} lastSongInfo
*/
/**
* @type {Info}
*/
const info = {
rpc: null,
ready: false,
lastSongInfo: null,
};
/**
* @type {(() => void)[]}
*/
const refreshCallbacks = [];
const resetInfo = () => {
info.rpc = null;
info.ready = false;
clearTimeout(clearActivity);
if (dev()) console.log("discord disconnected");
refreshCallbacks.forEach(cb => cb());
};

let window;
const connect = (showErr = false) => {
if (info.rpc) {
if (dev())
console.log('Attempted to connect with active RPC object');
return;
}

info.rpc = new Discord.Client({
transport: "ipc",
});
info.ready = false;

info.rpc.once("connected", () => {
if (dev()) console.log("discord connected");
refreshCallbacks.forEach(cb => cb());
});
info.rpc.once("ready", () => {
info.ready = true;
if (info.lastSongInfo) updateActivity(info.lastSongInfo)
});
info.rpc.once("disconnected", resetInfo);

// Startup the rpc client
info.rpc.login({ clientId }).catch(err => {
resetInfo();
if (dev()) console.error(err);
if (showErr) dialog.showMessageBox(window, { title: 'Connection failed', message: err.message || String(err), type: 'error' });
});
};

let clearActivity;
/**
* @type {import('../../providers/song-info').songInfoCallback}
*/
let updateActivity;

module.exports = (win, {activityTimoutEnabled, activityTimoutTime, listenAlong}) => {
window = win;
// We get multiple events
// Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1)
// Skip time: PAUSE(N), PLAY(N)
updateActivity = songInfo => {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
return;
}
info.lastSongInfo = songInfo;

// stop the clear activity timout
clearTimeout(clearActivity);

// stop early if discord connection is not ready
// do this after clearTimeout to avoid unexpected clears
if (!info.rpc || !info.ready) {
return;
}

// clear directly if timeout is 0
if (songInfo.isPaused && activityTimoutEnabled && activityTimoutTime === 0) {
info.rpc.clearActivity().catch(console.error);
return;
}

// Song information changed, so lets update the rich presence
// @see https://discord.com/developers/docs/topics/gateway#activity-object
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
const activityInfo = {
type: 2, // Listening, addressed in https://github.com/discordjs/RPC/pull/149
details: songInfo.title,
state: songInfo.artist,
largeImageKey: "logo",
largeImageText: [
songInfo.uploadDate,
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views",
].join(' || '),
buttons: listenAlong ? [
{ label: "Listen Along", url: songInfo.url },
] : undefined,
};

if (songInfo.isPaused) {
// Add an idle icon to show that the song is paused
activityInfo.smallImageKey = "idle";
activityInfo.smallImageText = "idle/paused";
// Set start the timer so the activity gets cleared after a while if enabled
if (activityTimoutEnabled)
clearActivity = setTimeout(() => info.rpc.clearActivity().catch(console.error), activityTimoutTime ?? 10000);
} else {
// Add the start and end time of the song
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp =
songStartTime + songInfo.songDuration * 1000;
}

info.rpc.setActivity(activityInfo).catch(console.error);
};

module.exports = (win, {activityTimoutEnabled, activityTimoutTime}) => {
// If the page is ready, register the callback
win.once("ready-to-show", () => {
rpc.once("ready", () => {
// Register the callback
registerCallback((songInfo) => {
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
return;
}
// Song information changed, so lets update the rich presence
const activityInfo = {
details: songInfo.title,
state: songInfo.artist,
largeImageKey: "logo",
largeImageText: [
songInfo.uploadDate,
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views"
].join(' || '),
};

if (songInfo.isPaused) {
// Add an idle icon to show that the song is paused
activityInfo.smallImageKey = "idle";
activityInfo.smallImageText = "idle/paused";
// Set start the timer so the activity gets cleared after a while if enabled
if (activityTimoutEnabled)
clearActivity = setTimeout(()=>rpc.clearActivity(), activityTimoutTime||10000);
} else {
// stop the clear activity timout
clearTimeout(clearActivity);
// Add the start and end time of the song
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
activityInfo.startTimestamp = songStartTime;
activityInfo.endTimestamp =
songStartTime + songInfo.songDuration * 1000;
}

rpc.setActivity(activityInfo);
});
});

// Startup the rpc client
rpc.login({ clientId }).catch(console.error);
registerCallback(updateActivity);
connect();
});
win.on("close", () => module.exports.clear());
};

module.exports.clear = () => {
if (info.rpc) info.rpc.clearActivity();
clearTimeout(clearActivity);
};
module.exports.connect = connect;
module.exports.registerRefresh = (cb) => refreshCallbacks.push(cb);
/**
* @type {Info}
*/
module.exports.info = Object.defineProperties({}, Object.keys(info).reduce((o, k) => ({ ...o, [k]: { enumerable: true, get: () => info[k] } }), {}));
47 changes: 47 additions & 0 deletions plugins/discord/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { setOptions } = require("../../config/plugins");
const { edit } = require("../../config");
const { clear, info, connect, registerRefresh } = require("./back");

let hasRegisterred = false;

module.exports = (win, options, refreshMenu) => {
if (!hasRegisterred) {
registerRefresh(refreshMenu);
hasRegisterred = true;
}

return [
{
label: info.rpc !== null ? "Connected" : "Reconnect",
enabled: info.rpc === null,
click: connect,
},
{
label: "Clear activity",
click: clear,
},
{
label: "Clear activity after timeout",
type: "checkbox",
checked: options.activityTimoutEnabled,
click: (item) => {
options.activityTimoutEnabled = item.checked;
setOptions('discord', options);
},
},
{
label: "Listen Along",
type: "checkbox",
checked: options.listenAlong,
click: (item) => {
options.listenAlong = item.checked;
setOptions('discord', options);
},
},
{
label: "Set timeout time in config",
// open config.json
click: edit,
},
];
};
8 changes: 8 additions & 0 deletions plugins/downloader/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
cursor: pointer;
}

.menu-item > .yt-simple-endpoint:hover {
background-color: var(--ytmusic-menu-item-hover-background-color);
}

.menu-icon {
flex: var(--ytmusic-menu-item-icon_-_flex);
margin: var(--ytmusic-menu-item-icon_-_margin);
fill: var(--ytmusic-menu-item-icon_-_fill);
stroke: var(--iron-icon-stroke-color, none);
width: var(--iron-icon-width, 24px);
height: var(--iron-icon-height, 24px);
animation: var(--iron-icon_-_animation);
}
Loading

0 comments on commit 1cd4f53

Please sign in to comment.