Skip to content

Commit

Permalink
Discord add reconnecting functionality
Browse files Browse the repository at this point in the history
Clear rpc on disconnect
Add menu button to reconnect
  • Loading branch information
cpiber committed Aug 27, 2021
1 parent 36bc9c6 commit b5fd6b4
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 77 deletions.
181 changes: 125 additions & 56 deletions plugins/discord/back.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,140 @@
const Discord = require("discord-rpc");
const { dev } = require("electron-is")

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());
};

const connect = () => {
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);
});
};

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

module.exports = (win, {activityTimoutEnabled, activityTimoutTime}) => {
// 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
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(() => 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);
};

// If the page is ready, register the callback
win.once("ready-to-show", () => {
rpc.once("ready", () => {
// Register the callback
//
// We get multiple events
// Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1)
// Skip time: PAUSE(N), PLAY(N)
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(' || '),
};

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

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

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().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;
}

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

// Startup the rpc client
rpc.login({ clientId }).catch(console.error);
registerCallback(updateActivity);
connect();
});
};

module.exports.clear = () => rpc.clearActivity();
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] } }), {}));
52 changes: 31 additions & 21 deletions plugins/discord/menu.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
const { setOptions } = require("../../config/plugins");
const { edit } = require("../../config");
const { clear } = require("./back");
const { clear, info, connect, registerRefresh } = require("./back");

module.exports = (win, options) => [
{
label: "Clear activity",
click: () => {
clear();
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: "Clear activity after timeout",
type: "checkbox",
checked: options.activityTimoutEnabled,
click: (item) => {
options.activityTimoutEnabled = item.checked;
setOptions('discord', options);
},
},
},
{
label: "Set timeout time in config",
click: () => {
{
label: "Set timeout time in config",
// open config.json
edit();
click: edit,
},
},
];
];
};
11 changes: 11 additions & 0 deletions providers/song-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const getArtist = async (win) => {
}

// Fill songInfo with empty values
/**
* @typedef {songInfo} SongInfo
*/
const songInfo = {
title: "",
artist: "",
Expand Down Expand Up @@ -71,6 +74,14 @@ const handleData = async (responseText, win) => {
const callbacks = [];

// This function will allow plugins to register callback that will be triggered when data changes
/**
* @callback songInfoCallback
* @param {songInfo} songInfo
* @returns {void}
*/
/**
* @param {songInfoCallback} callback
*/
const registerCallback = (callback) => {
callbacks.push(callback);
};
Expand Down

0 comments on commit b5fd6b4

Please sign in to comment.