230 lines
7.4 KiB
JavaScript
230 lines
7.4 KiB
JavaScript
'use strict';
|
|
|
|
const { EventEmitter } = require('node:events');
|
|
const ProcessServer = require('./process/index.js');
|
|
const IPCServer = require('./transports/ipc.js');
|
|
const WSServer = require('./transports/websocket.js');
|
|
const { RichPresence } = require('../../structures/RichPresence.js');
|
|
const { NitroType } = require('../Constants.js');
|
|
|
|
// eslint-disable-next-line no-useless-escape
|
|
const checkUrl = url => /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/.test(url);
|
|
|
|
let socketId = 0;
|
|
module.exports = class RPCServer extends EventEmitter {
|
|
constructor(client, debug = false) {
|
|
super();
|
|
Object.defineProperty(this, 'client', { value: client });
|
|
return (async () => {
|
|
this.debug = debug;
|
|
this.onConnection = this.onConnection.bind(this);
|
|
this.onMessage = this.onMessage.bind(this);
|
|
this.onClose = this.onClose.bind(this);
|
|
|
|
const handlers = {
|
|
connection: this.onConnection,
|
|
message: this.onMessage,
|
|
close: this.onClose,
|
|
};
|
|
|
|
this.ipc = await new IPCServer(handlers, this.debug);
|
|
this.ws = await new WSServer(handlers, this.debug);
|
|
this.process = await new ProcessServer(handlers, this.debug);
|
|
|
|
return this;
|
|
})();
|
|
}
|
|
|
|
onConnection(socket) {
|
|
socket.send({
|
|
cmd: 'DISPATCH',
|
|
evt: 'READY',
|
|
|
|
data: {
|
|
v: 1,
|
|
// Needed otherwise some stuff errors out parsing json strictly
|
|
user: {
|
|
// Mock user data using arRPC app/bot
|
|
id: this.client?.user?.id ?? '1045800378228281345',
|
|
username: this.client?.user?.username ?? 'arRPC',
|
|
discriminator: this.client?.user?.discriminator ?? '0000',
|
|
avatar: this.client?.user?.avatar,
|
|
flags: this.client?.user?.flags?.bitfield ?? 0,
|
|
premium_type: this.client?.user?.nitroType ? NitroType[this.client?.user?.nitroType] : 0,
|
|
},
|
|
config: {
|
|
api_endpoint: '//discord.com/api',
|
|
cdn_host: 'cdn.discordapp.com',
|
|
environment: 'production',
|
|
},
|
|
},
|
|
});
|
|
|
|
socket.socketId = socketId++;
|
|
|
|
this.emit('connection', socket);
|
|
}
|
|
|
|
onClose(socket) {
|
|
this.emit('activity', {
|
|
activity: null,
|
|
pid: socket.lastPid,
|
|
socketId: socket.socketId.toString(),
|
|
});
|
|
|
|
this.emit('close', socket);
|
|
}
|
|
|
|
async onMessage(socket, { cmd, args, nonce }) {
|
|
this.emit('message', { socket, cmd, args, nonce });
|
|
|
|
switch (cmd) {
|
|
case 'SET_ACTIVITY':
|
|
if (!socket.clientInfo || !socket.clientAssets) {
|
|
// https://discord.com/api/v9/oauth2/applications/:id/rpc
|
|
socket.clientInfo = await this.client.api.oauth2.applications(socket.clientId).rpc.get();
|
|
socket.clientAssets = await this.client.api.oauth2.applications(socket.clientId).assets.get();
|
|
}
|
|
// eslint-disable-next-line no-case-declarations
|
|
const { activity, pid } = args; // Translate given parameters into what discord dispatch expects
|
|
|
|
if (!activity) {
|
|
return this.emit('activity', {
|
|
activity: null,
|
|
pid,
|
|
socketId: socket.socketId.toString(),
|
|
});
|
|
}
|
|
// eslint-disable-next-line no-case-declarations
|
|
const { buttons, timestamps, instance, assets } = activity;
|
|
|
|
socket.lastPid = pid ?? socket.lastPid;
|
|
|
|
// eslint-disable-next-line no-case-declarations
|
|
const metadata = {};
|
|
// eslint-disable-next-line no-case-declarations
|
|
const extra = {};
|
|
if (buttons) {
|
|
// Map buttons into expected metadata
|
|
metadata.button_urls = buttons.map(x => x.url);
|
|
extra.buttons = buttons.map(x => x.label);
|
|
}
|
|
|
|
if (assets?.large_image) {
|
|
if (checkUrl(assets.large_image)) {
|
|
assets.large_image = assets.large_image
|
|
.replace('https://cdn.discordapp.com/', 'mp:')
|
|
.replace('http://cdn.discordapp.com/', 'mp:')
|
|
.replace('https://media.discordapp.net/', 'mp:')
|
|
.replace('http://media.discordapp.net/', 'mp:');
|
|
if (!assets.large_image.startsWith('mp:')) {
|
|
// Fetch
|
|
const data = await RichPresence.getExternal(this.client, socket.clientId, assets.large_image);
|
|
assets.large_image = data[0].external_asset_path;
|
|
}
|
|
}
|
|
if (/^[0-9]{17,19}$/.test(assets.large_image)) {
|
|
// ID Assets
|
|
}
|
|
if (
|
|
assets.large_image.startsWith('mp:') ||
|
|
assets.large_image.startsWith('youtube:') ||
|
|
assets.large_image.startsWith('spotify:')
|
|
) {
|
|
// Image
|
|
}
|
|
if (assets.large_image.startsWith('external/')) {
|
|
assets.large_image = `mp:${assets.large_image}`;
|
|
} else {
|
|
const l = socket.clientAssets.find(o => o.name == assets.large_image);
|
|
if (l) assets.large_image = l.id;
|
|
}
|
|
}
|
|
|
|
if (assets?.small_image) {
|
|
if (checkUrl(assets.small_image)) {
|
|
assets.small_image = assets.small_image
|
|
.replace('https://cdn.discordapp.com/', 'mp:')
|
|
.replace('http://cdn.discordapp.com/', 'mp:')
|
|
.replace('https://media.discordapp.net/', 'mp:')
|
|
.replace('http://media.discordapp.net/', 'mp:');
|
|
if (!assets.small_image.startsWith('mp:')) {
|
|
// Fetch
|
|
const data = await RichPresence.getExternal(this.client, socket.clientId, assets.small_image);
|
|
assets.small_image = data[0].external_asset_path;
|
|
}
|
|
}
|
|
if (/^[0-9]{17,19}$/.test(assets.small_image)) {
|
|
// ID Assets
|
|
}
|
|
if (
|
|
assets.small_image.startsWith('mp:') ||
|
|
assets.small_image.startsWith('youtube:') ||
|
|
assets.small_image.startsWith('spotify:')
|
|
) {
|
|
// Image
|
|
}
|
|
if (assets.small_image.startsWith('external/')) {
|
|
assets.small_image = `mp:${assets.small_image}`;
|
|
} else {
|
|
const l = socket.clientAssets.find(o => o.name == assets.small_image);
|
|
if (l) assets.small_image = l.id;
|
|
}
|
|
}
|
|
|
|
if (timestamps) {
|
|
for (const x in timestamps) {
|
|
// Translate s -> ms timestamps
|
|
if (Date.now().toString().length - timestamps[x].toString().length > 2) {
|
|
timestamps[x] = Math.floor(1000 * timestamps[x]);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.emit('activity', {
|
|
activity: {
|
|
application_id: socket.clientId,
|
|
type: 0,
|
|
name: socket.clientInfo.name,
|
|
metadata,
|
|
assets,
|
|
flags: instance ? 1 << 0 : 0,
|
|
...activity,
|
|
...extra,
|
|
},
|
|
pid,
|
|
socketId: socket.socketId.toString(),
|
|
});
|
|
|
|
socket.send?.({
|
|
cmd,
|
|
data: null,
|
|
evt: null,
|
|
nonce,
|
|
});
|
|
|
|
break;
|
|
|
|
case 'GUILD_TEMPLATE_BROWSER':
|
|
case 'INVITE_BROWSER':
|
|
// eslint-disable-next-line no-case-declarations
|
|
const { code } = args;
|
|
socket.send({
|
|
cmd,
|
|
data: {
|
|
code,
|
|
},
|
|
nonce,
|
|
});
|
|
|
|
this.emit(cmd === 'INVITE_BROWSER' ? 'invite' : 'guild-template', code);
|
|
break;
|
|
|
|
case 'DEEP_LINK':
|
|
this.emit('link', args.params);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|