Compare commits

...

131 Commits

Author SHA1 Message Date
Lamp ef4ccb8bc0 update deps 2023-10-10 18:29:16 -05:00
Lamp 995ac6d96f delete unused prbridge and screenshotter 2023-10-10 16:17:16 -07:00
Lamp e21a264ed0 normalize indents 2023-10-10 16:14:42 -07:00
Lamp 1af74e957d ignore unknown participant bye 2023-10-10 18:04:19 -05:00
Lamp 8bfd16a41f fix useless error 2023-10-10 17:58:11 -05:00
Lamp a86466efdb fix bug in sendEmbed 2023-10-10 17:54:34 -05:00
Lamp 999e71c5cc update opid 2023-10-10 17:47:26 -05:00
Lamp 6b29918703 fix 403 2023-10-09 14:59:31 -05:00
Lamp 41a1f0533f add lobby2, test/awkward 2023-10-06 16:06:50 -05:00
Lamp 48b7791ae3 add new official server lobby 2023-10-06 15:22:52 -05:00
Lamp fe0c0bbe61 disable prbridge 2023-03-05 18:36:19 -06:00
Lamp 7b934bf762 remove ced mpp 2023-03-05 18:34:39 -06:00
Lamp 44cd2cabb4 update bridges.json 2022-04-07 15:21:20 -05:00
Lamp 9775aef89a stop spam error log with 502 2022-02-24 17:03:50 -06:00
Lamp aabebc7d74 change ced uri 2022-01-07 21:56:01 -06:00
Lamp 1fda5903d0 bch 2022-01-05 18:15:28 -08:00
Lamp 35d1299897 i try new discord sender 2022-01-05 18:12:13 -08:00
Lamp fa1c39790a ass 2022-01-04 16:30:07 -08:00
Lamp 14c8c8fba3 Fuck 'cannot send an empty message' 2022-01-04 16:28:12 -08:00
Lamp 198a8ceb3a rewrite data collector
maybe it will work
2022-01-04 16:01:27 -08:00
Lamp e8b4334e3a fix bruhs 2022-01-03 23:00:36 -08:00
Lamp 7fe2b0fa12 revert new thing 2022-01-03 22:57:17 -08:00
Lamp 3ef1bccb7a add slash command 2022-01-03 22:45:28 -08:00
Lamp fb0c39b5c8 paste my eval/exec code 2022-01-03 22:26:27 -08:00
Lamp da4e033760 delete commands 2022-01-03 22:24:25 -08:00
Lamp aee470b583 fix 2022-01-03 21:56:30 -08:00
Lamp c7ec177bfc fix join/leave msg for v13 bruh 2022-01-03 21:51:48 -08:00
Lamp 5491cf25e6 fuc bug 2022-01-03 21:45:48 -08:00
Lamp fac15e380c try new thing (delete discord msg and show what mpp sees) 2022-01-03 21:44:30 -08:00
Lamp 78c9e1e3b5 FUCK YOU 2022-01-03 21:31:34 -08:00
Lamp aba50ff519 d 2022-01-03 21:28:35 -08:00
Lamp f145354bec fix 4 2022-01-03 21:24:57 -08:00
Lamp 849ae63144 fix 3 2022-01-03 21:06:31 -08:00
Lamp 1b01804230 fix 2 2022-01-03 21:02:27 -08:00
Lamp 006b2f2d77 fix 1 2022-01-03 20:54:32 -08:00
Lamp ca719a52e5 attempt upgrade to discord.js 13 2022-01-03 20:40:43 -08:00
Lamp c8e3353fe2 move files 2022-01-03 19:21:13 -08:00
Lamp 3249697420 strip out pg 2022-01-03 19:04:16 -08:00
Lamp 885493413a strip out mongodb
deleted name collector
2022-01-03 17:06:29 -08:00
Lamp 959d8481a5 ok delete junk folder 2022-01-03 17:02:01 -08:00
Lamp ff135e5c5b add beta ced mpp 2022-01-03 18:27:12 -06:00
Lamp 21fb1806bf FUCK V13 2021-08-16 11:51:37 -07:00
Lamp bc03104ca9 fgh v13 2021-08-16 10:53:04 -07:00
Lamp d00ebe21f8 o 2021-08-14 17:34:06 -07:00
Lamp d7e81475b8 better discord send fail log 2021-08-14 17:33:24 -07:00
Lamp 4a5d4b8dd6 AAAAA 2021-08-14 17:18:22 -07:00
Lamp 90431b0fad upd pg ssl 2021-08-14 17:11:35 -07:00
Lamp 53e8257268 update pg 2021-08-14 17:06:10 -07:00
Lamp ff9bf4cdb9 npm remove @rocket.chat/sdk 2021-08-14 17:01:02 -07:00
Lamp b1527092b6 fix 2021-08-14 15:53:13 -07:00
Lamp a16ccd44ba forgot 2 del require 2021-08-14 14:54:40 -07:00
Lamp 7464475f62 resurrect old deleted files into junk folder cuz interesting 2021-08-14 14:53:32 -07:00
Lamp bae1c51726 disable crown stealing and ownership shit 2021-08-14 14:48:14 -07:00
Lamp e24e1601cf rm junk files 2021-08-14 14:43:11 -07:00
Lamp 0b2bf7a94e remove secrets from src 2021-08-14 14:39:53 -07:00
Lamp 33004d0aef updeps 2021-08-14 14:13:37 -07:00
Lamp 0027431cbc yflv 2021-08-14 14:04:24 -07:00
Lamp 6b3ac80ea3 why r all these modified 2021-08-14 13:46:53 -07:00
Lamp 0a2f20c13a
Update Client.js 2021-07-27 00:20:56 -07:00
Lamp 54a23acf4b
Update Client.js 2021-07-27 00:10:18 -07:00
Lamp 80adbda77a fix 2021-06-23 18:46:47 -07:00
Lamp 7287ea9be6 fix 2021-05-29 17:36:25 -07:00
Lamp 68fb02854c fix 2021-05-28 21:43:15 -07:00
Lamp 2d24574ee6 separate screenshotter 2021-05-28 14:57:18 -07:00
Lamp bdb24c9650
disable color roles 2021-05-28 13:21:52 -07:00
Lamp 73efb1e419
Update index.js 2021-04-07 23:20:18 -07:00
Lamp 70d5cc4a39
Update Client.js 2021-04-07 23:13:30 -07:00
Lamp abc3f017e6
Update Client.js 2021-04-07 13:30:26 -07:00
Lamp 19cfcb7e91
Update index.js 2021-04-07 13:25:33 -07:00
Lamp 07083f802a
Update Client.js 2021-04-07 13:22:37 -07:00
Lamp f39316e82e
h 2021-04-04 10:33:02 -07:00
Lamp 4bfea68b90
update uri 2021-04-03 21:54:18 -07:00
Lamp f13b7b9210
Update Client.js 2021-04-03 21:23:46 -07:00
Lamp cba1796862
update uri 2021-04-03 21:22:43 -07:00
Lamp 7546f36068
a 2021-02-26 11:23:29 -08:00
Lamp 746410f0e0
OH 2021-02-26 11:22:53 -08:00
Lamp f3c0ba8480
gjsudifghsiudfvbiusblsdhsdfg 2021-02-25 23:51:28 -08:00
Lamp 3624cd46a7
update uri 2021-02-25 23:29:43 -08:00
Lamp b1113a30a0
a 2021-02-24 12:07:18 -08:00
Lamp 9a7c2178e8
Origin has changed 2021-02-24 12:06:38 -08:00
Lamp 29a07865bd
The standard port is included in the host header? 2021-02-24 12:05:53 -08:00
Lamp 9fca5dd9b0
Update MPP uri
Still need proxies for room limit but I don't think we need to hide our name from whom is not brandon anymore.
2021-02-24 11:59:23 -08:00
Lamp e5f6e67da0
Update index.js 2021-02-02 15:23:22 -08:00
Lamp 7b60c97e40
foker bistril forgot to renew domain and brok everyting 2020-12-20 11:25:56 -08:00
Lamp 2fe521b05f
found new char to sort bots 2020-11-17 11:58:28 -08:00
Lamp 6538381e6d
a 2020-11-03 14:37:47 -08:00
Lamp 658ea8d786 dotenv 2020-10-22 03:48:50 +00:00
Lamp 0c054dcfa6
E 2020-10-20 12:37:32 -07:00
Lamp f1b47e2cb2
a 2020-10-18 17:30:19 -07:00
Lamp 2e76de2dbd
o? 2020-10-18 17:24:39 -07:00
Lamp cedf977146
wht the fUq 2020-10-18 17:21:46 -07:00
Lamp 915f6eca0a
asdfdafgsdhsr 2020-10-18 17:18:20 -07:00
Lamp 46fb4ea5e4
awuieuiagrlaerkgl 2020-10-18 17:14:06 -07:00
Lamp af21719ace
fix thing disconnecting from room when receive notification for ban from different room
ez fix
regex would be better tho
2020-10-18 13:26:18 -07:00
Lamp 46555d28ef
update mpt 2020-10-17 14:15:42 -07:00
Lamp 311983c4e0
fuck "AbortError: The user aborted a request."
5 minute rest request timeout
2020-10-17 12:39:01 -07:00
Lamp bf8308d776
a 2020-10-08 16:40:31 -07:00
Lamp 3c5d76fd31
a 2020-10-08 16:33:46 -07:00
Lamp 276a2afcc6
hourly rp room screenshot 2020-10-04 14:59:41 -07:00
Lamp 520fa6c2cb
Update main.js 2020-10-04 14:47:09 -07:00
Lamp 42f1229574
ofo 2020-09-24 11:08:34 -07:00
Lamp 0b9c03c0f9
fire 2020-08-20 13:36:34 -07:00
Lamp acb186c05c
a 2020-07-14 13:03:43 -07:00
Lamp ea2e6001c7 Update puppeteer 2020-05-29 13:58:23 -07:00
Lamp 2746e7dd24
b 2020-05-28 21:51:40 -07:00
Lamp 43138e2799
update op id 2020-05-22 14:56:52 -07:00
Lamp 2131484c0a
w8 2020-04-30 22:43:08 -07:00
Lamp 2ec0be4b84
Create .ssh/config 2020-04-22 21:08:54 -07:00
Lamp aa60087da4
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApdate Procfile 2020-04-22 20:50:14 -07:00
Lamp 5b0564c18e
fafasdfawgaeeeahgaiehgeugaegh 2020-04-22 20:43:16 -07:00
Lamp b1df3dceb2
Update Procfile 2020-04-22 20:37:13 -07:00
Lamp 64aa828035
Update Procfile 2020-04-22 20:14:18 -07:00
Lamp a058dfda49
Create id_rsa 2020-04-22 20:11:18 -07:00
Lamp 8789c72140 actually some clients should disable all mentions 2020-04-18 13:09:08 -07:00
Lamp ccdfe465a3 bruh
no wonder user mentions weren't working
2020-04-18 13:00:53 -07:00
Lamp bf6e7fad5e fix color roles 2020-04-14 11:20:43 -07:00
Lamp 56b017af28 disableEveryone -> disableMentions
damnit discord.js I got @everyone'd
2020-04-11 21:14:50 -07:00
Lamp b651317345
no longer needed since room names supported special characters, afaik 2020-04-11 20:37:21 -07:00
Lamp 8369f52f90 replace all .get( with .resolve( 2020-04-11 20:27:00 -07:00
Lamp fddc6394ac update discord.js to try to fix guild channel create issue:
DiscordAPIError: Invalid Form Body
type: Value "text" is not int.

also did npm audit fix
2020-04-11 20:09:34 -07:00
Lamp 157b32fe79
make rocket.chat sdk shut up
and better error logging
2020-04-09 15:02:18 -07:00
Lamp 56987ae761
use nginx proxi
separate commit cuz github web
2020-04-08 14:54:06 -07:00
Lamp dd4ec17ff7
use nginx proxi 2020-04-08 14:53:23 -07:00
Lamp 28929d47bb
proxy screenshotter too
todo maybe just host this whole thing at home
2020-04-07 19:27:17 -07:00
Lamp 00bea9d9ab
fix issue causing chat from other clients of same user in same room not getting through most of the time
bot was constantly sending chat every 1.6 seconds even when queue is empty herp derp
2020-04-06 22:43:46 -07:00
Lamp f2185a0887
deleted bridges cat is full ofo 2020-04-06 22:30:47 -07:00
Lamp 09f9e7e6df
also dont advertise on my user 2020-04-06 22:22:56 -07:00
Lamp a088abc6c7
proxy through home for all mpp bridges
work-around the issue I caused myself, but it's for the common good
2020-04-06 22:09:50 -07:00
Lamp 1240618d55
fix clyde in webhook url 2020-03-08 22:49:31 -07:00
Lamp 8c887ab79f Merge pull request #2 from BopItFreak/patch-1
guess it good
2020-02-09 15:08:03 -08:00
BopItFreak a432d1942e
Fix stuF 2020-02-09 18:05:30 -05:00
38 changed files with 990 additions and 3214 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ node_modules/
.DS_Store
test.sh
.vscode
tmp
tmp
.env

View File

@ -1 +0,0 @@
worker: git clone https://github.com/ledlamp/k44Eqha.git --depth=1 && cd k44Eqha && exec node src/main.js

13
bridges.json Normal file
View File

@ -0,0 +1,13 @@
[
{ "room": "lobby", "channel": "1159943041692471488", "uri": "wss://game.multiplayerpiano.com:443" },
{ "room": "lobby2", "channel": "1159954805146529823", "uri": "wss://game.multiplayerpiano.com:443" },
{ "room": "test/awkward", "channel": "1159954847542546513", "uri": "wss://game.multiplayerpiano.com:443" },
{ "room": "lobby", "channel": "339609383644168195", "uri": "wss://mppclone.com:8443" },
{ "room": "lobby2", "channel": "350006891948277770", "uri": "wss://mppclone.com:8443" },
{ "room": "✧𝓓𝓔𝓥 𝓡𝓸𝓸𝓶✧", "channel": "698736467559710740", "uri": "wss://mppclone.com:8443" },
{ "room": "test/awkward", "channel": "360556775860076544", "uri": "wss://mppclone.com:8443" },
{ "room": "test/fishing", "channel": "339613702472859659", "uri": "wss://mppclone.com:8443" },
{ "room": "lobby", "channel": "339904195719200768", "uri": "wss://piano.ourworldofpixels.com" }
]

21
commands.js Normal file
View File

@ -0,0 +1,21 @@
global.commands = [
{
name: "list",
description: "Show the members on the other side of an MPP bridge",
exec: i => {
let bridge = bridges.find(x => x.channel == i.channel.id);
if (!bridge) return i.reply({ephemeral: true, content: "Not available in this channel"});
let ppl_list = Object.values(bridge.client.ppl).map(m => `\`${m._id}\` ${m.name}`);
i.reply({content: `__**${ppl_list.length} people are playing**__\n${ppl_list.join("\n")}`});
}
}
];
dClient.on("interactionCreate", interaction => {
commands.find(x => x.name == interaction.commandName)?.exec?.(interaction);
});
dClient.once("ready", () => {
dClient.guilds.resolve(config.guildID)?.commands.set(commands);
});

47
config.js Normal file
View File

@ -0,0 +1,47 @@
global.testmode = process.env.TEST ? true : false;
module.exports = {
"DISCORD_TOKEN": process.env.DISCORD_TOKEN,
"webhooks": {
"welcome": process.env.WEBHOOK_WELCOME,
"error": process.env.WEBHOOK_ERROR,
"ddp": process.env.WEBHOOK_DDP,
"pinboard": process.env.WEBHOOK_PINBOARD
},
"opID": "330499035419115522",
"guildID": testmode ? "467473467634089985" : "321819041348190249",
"channels": { // includes voice channels & category channels
"main": testmode ? "467473467634089987" : "321819041348190249",
"view_deleted_channels": testmode ? "467473467634089989" : "425060452129701889",
"name_collection": testmode ? "467481952728121345" : '379738469511069698',
"mpp_bridges": testmode ? "467481904707534850" : '360557444952227851',
"user_channels": testmode ? "467482031157149709" : '399735134061985792',
"deleted_channels": testmode ? "467482085657935872" : '425054198699261953',
"deleted_bridges": testmode ? "467482121657778176" : '696954288752164914',
"mpp_screenshot": testmode ? "467482164611514388" : '383773548810076163',
"owop_screenshot": testmode ? "467482202217906226" : '399079481161023492'
},
"roles": {
"viewing_deleted_channels": testmode ? "467473718353068042" : "425060792455397376",
},
"mppname": "[discord.gg/k44Eqha]",
"disabledRooms": [
"RP Room",
"Legends of Alorgon {RP Room}",
"Legends of Alorgon",
"Breastmilk ♥ 7:45 AM"
],
"owop_captcha_password": process.env.OWOP_CAPTCHA_PASSWORD,
"pr_password": process.env.PR_PASSWORD,
"mpc_token": process.env.MPC_TOKEN,
}

64
ddpbridge.js Normal file
View File

@ -0,0 +1,64 @@
var WebSocket = require('ws');
var Discord = require('discord.js');
var WebSocketMessageCollector = require('./lib/datacollector');
var webhook = new Discord.WebhookClient({url: config.webhooks.ddp}, {allowedMentions: {parse: []}});
var ws;
var wasConnected = false;
//var myId;
var wsc = new WebSocketMessageCollector(async function(data, startDate, endDate){
await webhook.send({files:[{
attachment: data,
name: `daydun piano main raw data recording from ${startDate.toISOString()} to ${endDate.toISOString()} .txt.gz`
}]});
});
(function connect() {
ws = new WebSocket("wss://daydun.com:5012/?nick=%5Bdiscord.gg%2Fk44Eqha%5D");
ws.on("open", () => {
if (!wasConnected) send2discord("**Connected**");
wasConnected = true;
});
ws.on("message", message => {
wsc.collect(message);
if (typeof message != 'string') return;
var transmission = JSON.parse(message);
if (transmission.type == 'chat') {
let chatmsg = transmission.message;
if (chatmsg.type == "message") {
//if (chatmsg.id != myId)
if (!chatmsg.content.startsWith('\u034f'))
send2discord(`**${sanitizeName(chatmsg.user.nick)}:** ${chatmsg.content}`);
} else if (chatmsg.type == "join") {
send2discord(`__***${sanitizeName(chatmsg.nick || chatmsg.id)} joined.***__`);
} else if (chatmsg.type == "leave") {
send2discord(`__***${sanitizeName(chatmsg.nick || chatmsg.id)} left.***__`);
}
} /*else if (transmission.type == 'load') {
myId = transmission.id;
}*/
});
ws.on("error", error => handleError(error));
ws.on("close", () => {
if (wasConnected) send2discord("**Disconnected**");
wasConnected = false;
setTimeout(connect, 5000);
});
})();
function send2discord(message) {
webhook.send(message.substring(0,2000));
}
function send2ddp(message) {
if (ws.readyState == WebSocket.OPEN) ws.send(JSON.stringify({type:"chat",message}));
}
dClient.on("messageCreate", message => {
if (message.channel.id != "508890674138054667" || message.author.bot) return;
var x = message.cleanContent;
if (message.attachments.size > 0) x += ' ' + message.attachments.map(a => a.url).join(' ');
send2ddp(`\u034f${message.member.displayName}#${message.author.discriminator}: ${x}`);
});

37
eval-exec.js Normal file
View File

@ -0,0 +1,37 @@
dClient.on("messageCreate", async function (message) {
if (message.author.id != config.opID) return;
if (message.content.startsWith("!>")) {
with (message) {
try {
var x = await eval(message.content.substr(2).trim());
} catch(e) {
var x = e.message;
}
}
if (typeof x == "undefined") return void await message.react(config.eval_undefined_emoji);
let t = typeof x == 'string' ? 'txt' : 'js';
if (typeof x != 'string' && typeof x != "function") x = require('util').inspect(x, {depth: 1});
let cb = `\`\`\`${t}\n${x}\`\`\``;
if (cb.length <= 2000)
message.channel.send(cb);
else
message.channel.send({files:[{
attachment: Buffer.from(x),
name: `output.${t}`
}]});
}
else if (message.content.startsWith("!$")) {
let cp = require("child_process").spawn("bash", ["-c", message.content.substr(2).trim()]);
function ondat(a) {
try {
var split = Discord.Util.splitMessage(a.toString(), {split:{char:'\n',length:2000}});
} catch(x) {
var split = Discord.Util.splitMessage(a.toString(), {split:{char:'',length:2000}});
}
split.forEach(message.channel.send.bind(message.channel));
}
cp.stdout.on("data", ondat);
cp.stderr.on("data", ondat);
}
});

15
src/lib/Client.js → lib/Client.js Executable file → Normal file
View File

@ -1,3 +1,4 @@
const { config } = require("process");
if(typeof module !== "undefined") {
module.exports = Client;
@ -73,13 +74,13 @@ Client.prototype.connect = function() {
return;
this.emit("status", "Connecting...");
if(typeof module !== "undefined") {
let headers = {
"origin": `https://${new URL(this.uri).host}`,
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"
};
if (this.uri.includes("multiplayerpiano.com")) headers["X-Forwarded-For"] = `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`;
// nodejsicle
this.ws = new WebSocket(this.uri, {
headers: {
"origin": "http://www.multiplayerpiano.com",
"user-agent": "kitty cat"
}
});
this.ws = new WebSocket(this.uri, {headers});
} else {
// browseroni
this.ws = new WebSocket(this.uri);
@ -115,7 +116,7 @@ Client.prototype.connect = function() {
});
this.ws.addEventListener("open", function(evt) {
self.connectionTime = Date.now();
self.sendArray([{m: "hi"}]);
self.sendArray([{m: "hi", x: 1, token: self.token}]);
self.pingInterval = setInterval(function() {
self.sendArray([{m: "t", e: Date.now()}]);
}, 20000);

View File

@ -0,0 +1,39 @@
module.exports = class DiscordMessageSender {
constructor(channel) {
this.channel = channel;
this.interval = setInterval(this.flush.bind(this), 3000);
}
//async init() {
// var webhooks = await this.channel.fetchWebhooks();
// this.webhook = webhooks.filter(w => w.token).first() || await this.channel.createWebhook("sdfadffg");
// this.interval = setInterval(this.flush.bind(this), 3000);
//}
send(text) {
this.pp ||= {content: ""};
this.pp.content += text + '\n';
}
sendEmbed(embed) {
this.pp ||= {};
this.pp.embeds = [embed];
this.flush();
}
flush() {
if (!this.pp) return;
var pp = this.pp;
delete this.pp;
if (pp.content?.length > 2000) {
pp.files = [{name: "message.txt", attachment: Buffer.from(pp.content)}];
delete pp.content;
}
//this.webhook.send(pp).catch(error => {
//handleError(error, "webhook");
this.channel.send(pp);
//});
}
}

28
lib/datacollector.js Normal file
View File

@ -0,0 +1,28 @@
var gzip = require("util").promisify(require("zlib").gzip);
module.exports = class WebSocketMessageCollector {
constructor(dispatchFunction) {
this.maxSize = 8000000;
this.data = "";
this.startDate = new Date();
this.dispatchFunction = dispatchFunction;
exitHook(cb => {
this.package().then(cb);
});
}
collect(message) {
message = message.data || message;
if (message instanceof ArrayBuffer) message = Buffer.from(message).toString('base64');
var line = `${Date.now()} ${message}\n`;
this.data += line;
if (this.data.length > this.maxSize) this.package();
}
async package() {
var data = this.data, startDate = this.startDate, endDate = new Date();
this.data = "", this.startDate = new Date();
data = await gzip(data);
await this.dispatchFunction(data, startDate, endDate);
}
}

41
main.js Normal file
View File

@ -0,0 +1,41 @@
require("dotenv").config();
require('./util');
global.config = require('./config');
if (config.testmode) console.log('TEST MODE');
global.exitHook = require('async-exit-hook');
global.Discord = require('discord.js');
global.fs = require('fs');
global.dClient = new Discord.Client({
intents: 32767,
restRequestTimeout: 5*60*1000,
allowedMentions: {parse: []}
});
// error handling
{
let webhook = new Discord.WebhookClient({url: config.webhooks.error}, {allowedMentions: {parse: []}});
global.handleError = function logError(error, title) {
let msg = error && (error.stack || error.message || error);
console.error(title + ':\n' + msg);
try {
webhook.send(`${title ? `**${title}:**` : ""}\`\`\`\n${msg}\n\`\`\``).catch(()=>{});
} catch(e) {}
}
process.on('unhandledRejection', error => handleError(error, "Unhandled Rejection"));
exitHook.uncaughtExceptionHandler(error => handleError(error, "Uncaught Exception"));
dClient.on('error', error => handleError(error, "Discord Client Error"));
dClient.on('warn', error => handleError(error, "Discord Client Warning"));
}
dClient.login(config.DISCORD_TOKEN);
dClient.on('ready', () => {
console.log('Discord Client Ready');
});
require('./eval-exec');
require("./commands");
require('./mppbridger');
require('./misc');
require('./ddpbridge');

73
misc.js Normal file
View File

@ -0,0 +1,73 @@
// join/leave
(async function(){
var webhook = new Discord.WebhookClient({url: config.webhooks.welcome}, {allowedMentions: {parse: []}});
dClient.on('guildMemberAdd', async member => {
if (member.guild.id != config.guildID) return;
let username = member.user.username.toLowerCase().includes('clyde') ? member.user.username.replace(/C/g,'Q').replace(/c/g,'q') : member.user.username;
webhook.send({content: `${member} joined.`, username, avatarURL: member.user.displayAvatarURL({format:'png',size:2048})});
});
dClient.on('guildMemberRemove', async member => {
if (member.guild.id != config.guildID) return;
let username = member.user.username.toLowerCase().includes('clyde') ? member.user.username.replace(/C/g,'Q').replace(/c/g,'q') : member.user.username;
webhook.send({content: `${member.user.tag} left.`, username, avatarURL: member.user.displayAvatarURL({format:'png',size:2048})});
});
})();
// view deleted channels
(async function(){
dClient.on('voiceStateUpdate', async (oldState, newState) => {
if (oldState.channelId != config.channels.view_deleted_channels && newState.channelId == config.channels.view_deleted_channels) {
// member joined the channel
newState.member.roles.add(config.roles.viewing_deleted_channels);
} else if (oldState.channelId == config.channels.view_deleted_channels && newState.channelId != config.channels.view_deleted_channels) {
// member left the channel
newState.member.roles.remove(config.roles.viewing_deleted_channels);
}
});
})();
// arrange bots at bottom of list
(async function(){
let prefix = "\u17b5";
let onNick = async member => {
if (member.guild.id != config.guildID) return;
if (member.user.bot && !member.displayName.startsWith(prefix))
await member.setNickname(`${prefix}${member.displayName}`.substring(0,32));
};
dClient.on('guildMemberAdd', onNick);
dClient.on('guildMemberUpdate', async (oldMember, newMember) => {
if (newMember.displayName != oldMember.displayName) await onNick(newMember);
});
})();
// persistent emojis
dClient.on("emojiDelete", async emoji => {
console.log("emoji deleted:", emoji.name, emoji.url);
if (global.disableEmojiProtection) return;
if (emoji.name.toLowerCase().includes('delete')) return;
async function readdEmoji() {
await emoji.guild.emojis.create(emoji.url, emoji.name);
delete readdEmoji;
}
// re-add emoji in 5 to 10 minutes
setTimeout(() => {
if (readdEmoji) readdEmoji();
}, 300000 + Math.random() * 300000);
// wouldn't want emoji to be lost if process is stopped before timeout ends
exitHook(callback => {
if (readdEmoji) readdEmoji().then(() => callback());
else callback();
});
});
// allow anyone to pin a message via reaction
// todo need enable partials for it to work on old messages
dClient.on("messageReactionAdd", async (messageReaction) => {
if (messageReaction.guild?.id != config.guildID) return;
if (messageReaction.emoji.name == "📌" || messageReaction.emoji.name == "📍")
await messageReaction.message.pin();
});

200
mppbridger.js Normal file
View File

@ -0,0 +1,200 @@
var Client = require('./lib/Client.js');
var WebSocketMessageCollector = require("./lib/datacollector");
var DiscordMessageSender = require("./lib/DiscordMessageSender");
global.createMPPbridge = async function createMPPbridge({room, channel, uri}) {
channel = dClient.channels.resolve(channel);
var d = new DiscordMessageSender(channel);
const gClient = new Client(uri);
if (uri == "wss://mppclone.com:8443") gClient.token = config.mpc_token; //todo hmm
gClient.setChannel(room, {visible:false});
gClient.start();
// maintain the client's presence in the channel
gClient.channelCorrectorInterval = setInterval(()=>{
// if client is connected and not in a channel (meaning setChannel failed due to ratelimit because another client joined a channel with the same user within the last second) OR client is in a channel but it is not the right channel…
if ((gClient.isConnected() && !gClient.channel) || (gClient.channel && gClient.channel._id != room))
// …set the channel!
gClient.setChannel(room, {visible:false});
}, 1000);
let lastError;
gClient.on("error", error => {
//handleError(error, `[${uri}][${room}]`);
error = error.toString();
if (lastError != error) {
d.send(`**${error.toString()}**`);
lastError = error;
}
});
var isConnected = false; // TODO use gClient.isConnected() ?
gClient.on('connect', () => {
console.log(`[${uri}][${room}] Connected to server`);
d.send(`**Connected to server; joining channel…**`);
isConnected = true;
lastError = undefined;
});
gClient.on('hi', ()=>{
console.log(`[${uri}][${room}] Received greeting`);
if (!testmode) {
gClient.sendArray([{m: "userset", set: {name: config.mppname }}]);
}
gClient.sendArray([{m:'m',x:Math.floor(Math.random()*100),y:Math.floor(Math.random()*100)}])
});
gClient.on('disconnect', () => {
if (isConnected) {
console.log(`[${uri}][${room}] Disconnected from server`);
d.send(`**Disconnected from server**`);
isConnected = false;
}
});
/*gClient.on('status', status => {
console.log(`[${uri}] [${room}] ${status}`);
});*/
// on channel change
{
let lastCh;
gClient.on('ch', async msg => {
// announce channel join
if (!lastCh) {
d.send(`**Joined channel \`${msg.ch._id}\`**`);
console.log(`[${uri}][${room}] Joined channel ${msg.ch._id}`);
}
// announce channel change
else if (msg.ch._id !== lastCh) {
d.send(`**Channel changed from \`${lastCh}\` to \`${msg.ch._id}\`**`);
console.log(`[${uri}][${room}] Channel changed from ${lastCh} to ${msg.ch._id}`);
}
lastCh = msg.ch._id;
});
gClient.on("disconnect", () => lastCh = undefined);
}
// MPP to Discord
gClient.on('a', async msg => {
if (msg.p._id == gClient.getOwnParticipant()._id) return;
var id = msg.p._id.substr(0,6);
var name = sanitizeName(msg.p.name);
var content = msg.a;
var str = `\`${id}\` **${name}:** ${content}`;
d.send(str);
});
// Discord to MPP
{
let msgQueue = [];
dClient.on('message', async message => {
if (message.channel.id !== channel.id || message.author.id == dClient.user.id || !message.member /*|| message.content.startsWith('!')*/) return;
var str = message.cleanContent;
var aname = message.author.tag;
if (str.startsWith('/') || str.startsWith('\\'))
msgQueue.push(`${aname}`);
else
str = `${aname}: ${str}`;
if (str.startsWith('\\')) str = str.slice(1);
if (message.attachments.size > 0) str += ' ' + message.attachments.map(a => a.url).join(' ');
if (str.length > 512) {
str = str.substr(0,511) + '…';
message.react('⚠');
}
msgQueue.push(str);
});
setInterval(()=>{
let message = msgQueue.shift();
if (message) gClient.sendArray([{m:'a', message}]);
//todo wait moment to see if message got through then react warning if didnt
}, 1600); // just about fastest without exceeding quota; I figured quota is 4 messages per 6 seconds in lobbies
}
// announce join/leave/rename
gClient.prependListener("p", async participant => {
if (gClient.ppl[participant.id]) { // is update
let oldName = gClient.ppl[participant.id].name, newName = participant.name;
if (newName != oldName)
d.send(`\`${participant._id.substr(0,6)}\` ___**${sanitizeName(oldName)}** changed their name to **${sanitizeName(newName)}**___`);
} else { // is join
d.send(`\`${participant._id.substr(0,6)}\` ___**${sanitizeName(participant.name)}** entered the room.___`);
}
});
gClient.prependListener("bye", async msg => {
var participant = gClient.ppl[msg.p];
if (!participant) return;
d.send(`\`${participant._id.substr(0,6)}\` ___**${sanitizeName(participant.name)}** left the room.___`);
});
// on notifications
gClient.on('notification', async msg => {
// show notification
d.sendEmbed({
title: msg.title,
description: msg.text || msg.html
});
// handle bans
if (msg.text && (msg.text.startsWith('Banned from "'+room+'"') || msg.text.startsWith('Currently banned from "'+room+'"'))) {
// Banned from "{room}" for {n} minutes.
// Currently banned from "{room}" for {n} minutes.
let arr = msg.text.split(' ');
arr.pop();
let minutes = arr.pop();
gClient.stop();
setTimeout(()=>{
gClient.setChannel(room);
gClient.start();
}, minutes*60*1000+3000);
d.send(`**Attempting to rejoin in ${minutes} minutes.**`);
}
});
// make room invisible when nobody else is in it
gClient.on("ch", async function(msg){
if (gClient.isOwner()) {
if (gClient.countParticipants() <= 1) {
gClient.sendArray([{m:'chset', set: { visible: false }}])
} else {
gClient.sendArray([{m:'chset', set: { visible: true }}])
}
}
});
// addons
var wsc = new WebSocketMessageCollector(async function(data, startDate, endDate) {
var attachmentName = `${uri} ${room} raw data recording from ${startDate.toISOString()} to ${endDate.toISOString()} .txt.gz`;
await channel.send({files:[{
attachment: data,
name: attachmentName
}]});
});
gClient.on("message", wsc.collect.bind(wsc));
return gClient;
};
// start
dClient.once("ready", async function () {
global.bridges = require("./bridges");
for (let bridge of bridges) {
try {
bridge.client = await createMPPbridge(bridge);
} catch(e) {
handleError(error, JSON.stringify(bridge));
}
}
});

1272
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,8 @@
{
"dependencies": {
"@rocket.chat/sdk": "^0.2.9-2",
"async-exit-hook": "^2.0.1",
"discord.js": "github:hydrabolt/discord.js#60ad9053a3f7d0cfcfe9871bc5aa6eca791caf02",
"mongodb": "^3.0.0-rc0",
"pg": "^7.4.0",
"puppeteer": "^1.18.1",
"socketcluster-client": "^14.2.2",
"ws": "^3.3.2"
"discord.js": "^13.16.0",
"dotenv": "^8.6.0",
"ws": "^8.14.2"
}
}

View File

@ -1,184 +0,0 @@
global.colorRoles = new Object();
colorRoles.findColorRole = function (member) { // get color role of member
return member.roles.find(role => role.name.startsWith('['));
};
colorRoles.update = async function (member) { // create or remove member's color role based on presence
let existingColorRole = colorRoles.findColorRole(member);
if (member.presence.status == "offline") { // they must not have the role
if (!existingColorRole) return; // ok, they already don't have the role
// save and delete their color role
let role = existingColorRole;
let dbrole = {
id: role.id,
name: role.name,
color: role.color,
hoist: role.hoist,
position: role.position,
permissions: role.permissions.bitfield,
mentionable: role.mentionable
};
let id = member.id;
// upsert member's color_role json
let res = await dbClient.query(`SELECT * FROM member_data WHERE id = $1`, [id]);
if (res.rows[0]) {
await dbClient.query(`UPDATE member_data SET color_role = $2 WHERE id = $1`, [id, dbrole]);
} else {
await dbClient.query(`INSERT INTO member_data (id, color_role) VALUES ($1, $2)`, [id, dbrole]);
}
await role.delete();
} else { // they must have their color role
if (existingColorRole) return; // ok, they already have the role
// give them their color role
// check if database has their role
let member_data = (await dbClient.query(`SELECT (color_role) FROM member_data WHERE id = $1`, [member.id])).rows[0];
if (member_data && member_data.color_role) { // it does, reinstantiate it
let dbrole = member_data.color_role;
let role = member.guild.roles.get(dbrole.id); // get existing role if it still exists somehow
if (!role) role = await member.guild.roles.create({data:{ // otherwise recreate it
name: dbrole.name,
color: dbrole.color,
hoist: dbrole.hoist,
//position: dbrole.position,
permissions: dbrole.permissions,
mentionable: dbrole.mentionable
}});
await member.roles.add(role);
} else { // it doesn't, create a new one
let role = await member.guild.roles.create({data:{
name: "[]",
permissions: [],
color: "RANDOM",
}});
await member.roles.add(role);
}
}
};
colorRoles.updateAll = async function() { // update all members' color roles
console.log("Updating all color roles");
var guild = dClient.defaultGuild || dClient.guilds.get(config.guildID);
await guild.members.fetch(); // load all members
for (let member of guild.members) {
member = member[1];
try {
await colorRoles.update(member);
console.log("Updated", member.user.tag);
} catch(e) {
console.error(e.stack);
}
}
console.log("Finished updating all color roles");
};
colorRoles.pruneOrphanRoles = async function() { // delete all color roles that have no member
console.log("Pruning orphan roles");
var guild = dClient.defaultGuild || dClient.guilds.get(config.guildID);
for (let role of guild.roles) {
role = role[1];
if (role.name.startsWith('[') && !role.members.size) {
try {
await role.delete();
console.log("Deleted role ", role.id, role.name, role.color, role.permissions);
} catch (e) {
console.error(e);
}
}
}
console.log("Finished pruning orphan roles");
};
// event listeners
/*
dClient.on('local_presenceUpdate', async (oldPresence, newPresence) => {
//if (oldPresence && (oldPresence.status == newPresence.status)) return; // don't constantly add/remove roles anymore
// add role when person goes online
if (newPresence.status != "offline")
await colorRoles.update(newPresence.member);
// and do not remove until we run out of roles
});
dClient.on('local_guildMemberRemove', async member => { // update (delete) color role on member leave
await colorRoles.update(member);
});
{
let lastRoleMaintenance;
dClient.on('error', async (error) => {
if (Date.now() - lastRoleMaintenance < 600000) return; // in case errors are spammed, don't try running maintenance if it was already done within 10 mins ago
lastRoleMaintenance = Date.now();
if (error.message == "Maximum number of guild roles reached (250)") {
console.log("Ran out of roles; running maintenance");
await colorRoles.updateAll(); // remove roles from offline users when we run out of roles
await colorRoles.pruneOrphanRoles(); // remove any roles whose member doesn't exist anymore
}
});
}
*/
// commands
commands.color = {
aliases: ["col", "colour"],
usage: "<ColorResolvable>",
description: "Changes your color, or the color of your role. You can use a hex code or one of the color names listed [here](https://discord.js.org/#/docs/main/master/typedef/ColorResolvable), but not RGB arrays. Color names are not case sensitive. Invalid colors will make you colorless. You cannot change your color if you are offline or invisible.",
exec: async function (message) {
var str = message.txt(1);
if (!str) return false;
var role = colorRoles.findColorRole(message.member);
if (!role) {
if (message.member.presence.status == "offline")
return message.reply(random([
"You are offline.",
"I can't change your color when you're invisible."
]));
else {
await colorRoles.update(message.member);
role = colorRoles.findColorRole(message.member);
}
}
/*if (!role) { // somehow this happened
let a = 0;
while (!(role = colorRoles.findColorRole(message.member)) && a++ < 10) {}
}*/
await role.setColor(str.toUpperCase());
await message.react("🆗");
}
}
commands.title = {
aliases: ["tit"],
usage: "<title>",
description: "Sets your title, or the name of your colored role. Titles longer than 98 chars will be truncated. You cannot change your title if you are invisible.\nUse “none” to clear your title.",
exec: async function (message) {
var str = message.txt(1);
if (!str) return false;
if (str == "none") str = "";
if (str.length > 98) str = str.substr(0,97) + '…';
var role = colorRoles.findColorRole(message.member);
if (!role) {
if (message.member.presence.status == "offline")
return message.reply(random([
"You are offline.",
"I can't change your color when you're invisible."
]));
else {
await colorRoles.update(message.member);
role = colorRoles.findColorRole(message.member);
}
}
await role.setName(`[${str}]`);
await message.react("🆗");
}
}

View File

@ -1,160 +0,0 @@
global.commands = {
"help": {
usage: "[command]",
aliases: ["commands"],
exec: async function (msg) {
if (msg.args[1]) {
var commandName = msg.args[1];
var command = commands[commandName];
if (!command)
for (let cmdNme in commands) {
let cmd = commands[cmdNme];
if (cmd.aliases && cmd.aliases.includes(commandName)) {command = cmd; break;}
}
if (!command) return msg.react('❓');
var str = '`'+`!${commandName} ${command.usage || ''}`.trim()+'`\n';
if (command.hasOwnProperty('aliases')) str += `**Aliases:** \`!${command.aliases.join(', !')}\`\n`;
if (command.hasOwnProperty('description')) str += `\n${command.description}`;
msg.channel.send({embed:{
description: str
}});
} else {
var cmdArr = [];
for (var command in commands) {
if (!commands[command].op) cmdArr.push(`!${command}`);
}
var embed = {
title: "Commands",
description: cmdArr.join(', '),
footer: {text: "Use `!help <command>` for more info on a command."}
};
msg.channel.send({embed});
}
}
},
'delete': {
usage: "[channel]",
aliases: ['archive'],
hidden: true,
description: "Archives a channel that you have permission to delete.",
exec: async function (msg) {
if (msg.args[1]) {
var channel = msg.mentions.channels.first();
if (!channel) {
msg.react(``);
return;
}
} else {
var channel = msg.channel;
}
if (!channel.permissionsFor(msg.member).has('MANAGE_CHANNELS')) return msg.react('🚫');
await channel.setParent(config.channels.deleted_channels);
await new Promise(resolve => setTimeout(resolve, 500));
await channel.lockPermissions();
msg.react('🆗');
}
},
"eval": {
op: true,
description: "Evaluate javascript",
usage: "<code>",
aliases: ['>'],
exec: async function (message) {
var msg = message, m = message,
guild = message.guild,
channel = message.channel,
member = message.member,
client = dClient,
send = function(){
channel.send.apply(channel, arguments);
},
say = send;
try {
var out = eval(message.content.substr(2));
} catch (error) {
var out = error;
} finally {
message.channel.send('`'+out+'`', {split:{char:''}});
}
}
},
"exec": {
op: true,
description: "Run a shell command",
usage: "<command>",
aliases: ["$"],
exec: async function (msg) {
require("child_process").exec(msg.txt(1), function(error, stdout, stderr){
if (error) msg.channel.send(error, {split:{char:'',maxLength:2000}});
if (stdout) msg.channel.send(stdout, {split:{char:'',maxLength:2000}});
if (stderr) msg.channel.send(stderr, {split:{char:'',maxLength:2000}});
})
}
},
"query": {
description: "Queries the Heroku PostgreSQL database",
usage: "<query>",
aliases: ['q', 'db', 'sql', '?'],
op: true,
exec: async function (msg) {
dbClient.query(msg.txt(1), (err, res) => {
var str = err || JSON.stringify(res);
msg.channel.send(str, {split:{char:''}});
});
}
},
}
dClient.on('local_message', async message => {
if (!message.content.startsWith('!')) return;
if (message.author.id === dClient.user.id) return;
var args = message.content.split(' ');
var cmd = args[0].slice(1).toLowerCase();
var txt = function(i){return args.slice(i).join(' ').trim()};
message.args = args;
message.cmd = cmd;
message.txt = function(i){return this.args.slice(i).join(' ')};
if (!message.guild) message.guild = dClient.guilds.get(config.guildID);
if (!message.member) message.member = dClient.guilds.get(config.guildID).members.get(message.author.id);
Object.keys(commands).forEach(commandName => {
var command = commands[commandName];
if (!(commandName === cmd || (command.aliases && command.aliases.includes(cmd)))) return;
if (command.op && !config.opIDs.includes(message.author.id)) return message.react('🚫');
command.exec(message, args, txt).then(
(res) => {
switch (res) {
case "ENOTBRIDGE":
message.channel.send(random([
`This is not a bridged channel.`,
`You can only use this command in a bridged channel.`
]));
break;
case "EBADUSG":
message.channel.send(`**Usage:** \`!${commandName} ${command.usage}\``);
break;
}
},
(err) => {
message.reply(`:warning: An error occured: \`\`\`\n${err.stack}\n\`\`\`<@281134216115257344>`);
console.error(err.stack);
}
)
});
});

View File

@ -1,49 +0,0 @@
global.testmode = process.env.TEST ? true : false;
module.exports = {
"DISCORD_TOKEN": process.env.DISCORD_TOKEN,
"DATABASE_URL": testmode ? "postgres://localhost/k4t" : process.env.DATABASE_URL,
"MONGODB_URI": testmode ? "mongodb://localhost/k4t" : process.env.MONGODB_URI,
"webhooks": {
"welcome": process.env.WEBHOOK_WELCOME.split("/"),
"error": process.env.WEBHOOK_ERROR.split("/"),
"ddp": process.env.WEBHOOK_DDP.split("/"),
"pinboard": process.env.WEBHOOK_PINBOARD.split("/")
},
"opID": "330499035419115522",
"opIDs": ["281134216115257344", "330499035419115522"], //TODO i dont need multiple ops anymore
"guildID": testmode ? "467473467634089985" : "321819041348190249",
"channels": { // includes voice channels & category channels
"main": testmode ? "467473467634089987" : "321819041348190249",
"view_deleted_channels": testmode ? "467473467634089989" : "425060452129701889",
"name_collection": testmode ? "467481952728121345" : '379738469511069698',
"mpp_bridges": testmode ? "467481904707534850" : '360557444952227851',
"user_channels": testmode ? "467482031157149709" : '399735134061985792',
"deleted_channels": testmode ? "467482085657935872" : '425054198699261953',
"deleted_bridges": testmode ? "467482121657778176" : '489900312216207361',
"mpp_screenshot": testmode ? "467482164611514388" : '383773548810076163',
"owop_screenshot": testmode ? "467482202217906226" : '399079481161023492'
},
"roles": {
"viewing_deleted_channels": testmode ? "467473718353068042" : "425060792455397376",
},
"mppname": "[discord.gg/k44Eqha]",
"disabledRooms": [
"RP Room",
"Legends of Alorgon {RP Room}",
"Legends of Alorgon",
"Breastmilk ♥ 7:45 AM"
],
"owop_captcha_password": process.env.OWOP_CAPTCHA_PASSWORD,
"pr_password": process.env.PR_PASSWORD
}

View File

@ -1,42 +0,0 @@
var os = require('os');
var zlib = require('zlib');
// WebSocket message data collector. Returns a function for inputting websocket messages.
// Collects inputted messages to a file and gzips them every time it reaches 8mb,
// and sends it to the output callback function.
// For recording websocket data to a discord channel.
module.exports = function createWSMessageCollector(output) { // output func must be async
var filepath = os.tmpdir() + "/" + Math.random().toString(36).substring(2);
var size = 0;
var startDate = new Date();
// gzip the data & send to output callback
async function save(callback){
fs.readFile(filepath, (err, file) => {
if (err) return console.error(err);
zlib.gzip(file, async function(err, data){
if (err) return console.error(err);
var thisStartDate = startDate, thisEndDate = new Date();
fs.writeFileSync(filepath, '');
size = 0;
startDate = new Date();
await output(data, thisStartDate, thisEndDate);
if (callback) callback();
});
});
}
// save on exit
exitHook(callback => {
save(()=>callback());
});
return function input(message) { // input for websocket messages
message = message.data || message;
if (message instanceof ArrayBuffer) message = Buffer.from(message).toString('base64');
var line = `${Date.now()} ${message}\n`;
size += line.length;
fs.appendFile(filepath, line, ()=>{});
if (size > 8000000) {save(); size = 0;}
};
}

View File

@ -1,66 +0,0 @@
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0; //TODO fix
var WebSocket = require('ws');
var Discord = require('discord.js');
var createWsMessageCollector = require('./datacollector');
var webhook = new Discord.WebhookClient(config.webhooks.ddp[0], config.webhooks.ddp[1], {disableEveryone:true});
var ws;
var wasConnected = false;
//var myId;
var collectWsMessage = createWsMessageCollector(async function(data, startDate, endDate){
await webhook.send({files:[{
attachment: data,
name: `daydun piano main raw data recording from ${startDate.toISOString()} to ${endDate.toISOString()} .txt.gz`
}]});
});
(function connect() {
ws = new WebSocket("wss://daydun.com:5012/?nick=%5Bdiscord.gg%2Fk44Eqha%5D");
ws.on("open", () => {
if (!wasConnected) send2discord("**Connected**");
wasConnected = true;
});
ws.on("message", message => {
collectWsMessage(message);
if (typeof message != 'string') return;
var transmission = JSON.parse(message);
if (transmission.type == 'chat') {
let chatmsg = transmission.message;
if (chatmsg.type == "message") {
//if (chatmsg.id != myId)
if (!chatmsg.content.startsWith('\u034f'))
send2discord(`**${sanitizeName(chatmsg.user.nick)}:** ${escapeDiscordMentions(chatmsg.content)}`);
} else if (chatmsg.type == "join") {
send2discord(`__***${sanitizeName(chatmsg.nick || chatmsg.id)} joined.***__`);
} else if (chatmsg.type == "leave") {
send2discord(`__***${sanitizeName(chatmsg.nick || chatmsg.id)} left.***__`);
}
} /*else if (transmission.type == 'load') {
myId = transmission.id;
}*/
});
ws.on("error", error => console.error(error));
ws.on("close", () => {
if (wasConnected) send2discord("**Disconnected**");
wasConnected = false;
setTimeout(connect, 5000);
});
})();
function send2discord(message) {
webhook.send(message, {split:{char:'',maxLength:2000}});
}
function send2ddp(message) {
if (ws.readyState == WebSocket.OPEN) ws.send(JSON.stringify({type:"chat",message}));
}
dClient.on("local_message", message => {
if (message.channel.id != "508890674138054667" || message.author.bot) return;
var x = message.cleanContent;
if (message.attachments.first()) x += " " + message.attachments.first().url;
send2ddp(`\u034f${message.member.displayName}#${message.author.discriminator}: ${x}`);
});

View File

@ -1,684 +0,0 @@
var Midi = {};
(function(exported) {
var DEFAULT_VOLUME = exported.DEFAULT_VOLUME = 90;
var DEFAULT_DURATION = exported.DEFAULT_DURATION = 128;
var DEFAULT_CHANNEL = exported.DEFAULT_CHANNEL = 0;
/* ******************************************************************
* Utility functions
****************************************************************** */
var Util = {
midi_letter_pitches: { a:21, b:23, c:12, d:14, e:16, f:17, g:19 },
/**
* Convert a symbolic note name (e.g. "c4") to a numeric MIDI pitch (e.g.
* 60, middle C).
*
* @param {string} n - The symbolic note name to parse.
* @returns {number} The MIDI pitch that corresponds to the symbolic note
* name.
*/
midiPitchFromNote: function(n) {
var matches = /([a-g])(#+|b+)?([0-9]+)$/i.exec(n);
var note = matches[1].toLowerCase(), accidental = matches[2] || '', octave = parseInt(matches[3], 10);
return (12 * octave) + Util.midi_letter_pitches[note] + (accidental.substr(0,1)=='#'?1:-1) * accidental.length;
},
/**
* Ensure that the given argument is converted to a MIDI pitch. Note that
* it may already be one (including a purely numeric string).
*
* @param {string|number} p - The pitch to convert.
* @returns {number} The resulting numeric MIDI pitch.
*/
ensureMidiPitch: function(p) {
if (typeof p == 'number' || !/[^0-9]/.test(p)) {
// numeric pitch
return parseInt(p, 10);
} else {
// assume it's a note name
return Util.midiPitchFromNote(p);
}
},
midi_pitches_letter: { '12':'c', '13':'c#', '14':'d', '15':'d#', '16':'e', '17':'f', '18':'f#', '19':'g', '20':'g#', '21':'a', '22':'a#', '23':'b' },
midi_flattened_notes: { 'a#':'bb', 'c#':'db', 'd#':'eb', 'f#':'gb', 'g#':'ab' },
/**
* Convert a numeric MIDI pitch value (e.g. 60) to a symbolic note name
* (e.g. "c4").
*
* @param {number} n - The numeric MIDI pitch value to convert.
* @param {boolean} [returnFlattened=false] - Whether to prefer flattened
* notes to sharpened ones. Optional, default false.
* @returns {string} The resulting symbolic note name.
*/
noteFromMidiPitch: function(n, returnFlattened) {
var octave = 0, noteNum = n, noteName, returnFlattened = returnFlattened || false;
if (n > 23) {
// noteNum is on octave 1 or more
octave = Math.floor(n/12) - 1;
// subtract number of octaves from noteNum
noteNum = n - octave * 12;
}
// get note name (c#, d, f# etc)
noteName = Util.midi_pitches_letter[noteNum];
// Use flattened notes if requested (e.g. f# should be output as gb)
if (returnFlattened && noteName.indexOf('#') > 0) {
noteName = Util.midi_flattened_notes[noteName];
}
return noteName + octave;
},
/**
* Convert beats per minute (BPM) to microseconds per quarter note (MPQN).
*
* @param {number} bpm - A number in beats per minute.
* @returns {number} The number of microseconds per quarter note.
*/
mpqnFromBpm: function(bpm) {
var mpqn = Math.floor(60000000 / bpm);
var ret=[];
do {
ret.unshift(mpqn & 0xFF);
mpqn >>= 8;
} while (mpqn);
while (ret.length < 3) {
ret.push(0);
}
return ret;
},
/**
* Convert microseconds per quarter note (MPQN) to beats per minute (BPM).
*
* @param {number} mpqn - The number of microseconds per quarter note.
* @returns {number} A number in beats per minute.
*/
bpmFromMpqn: function(mpqn) {
var m = mpqn;
if (typeof mpqn[0] != 'undefined') {
m = 0;
for (var i=0, l=mpqn.length-1; l >= 0; ++i, --l) {
m |= mpqn[i] << l;
}
}
return Math.floor(60000000 / mpqn);
},
/**
* Converts an array of bytes to a string of hexadecimal characters. Prepares
* it to be converted into a base64 string.
*
* @param {Array} byteArray - Array of bytes to be converted.
* @returns {string} Hexadecimal string, e.g. "097B8A".
*/
codes2Str: function(byteArray) {
var string = "";
byteArray.forEach(byte => string += String.fromCharCode(byte));
return string;
},
/**
* Converts a string of hexadecimal values to an array of bytes. It can also
* add remaining "0" nibbles in order to have enough bytes in the array as the
* `finalBytes` parameter.
*
* @param {string} str - string of hexadecimal values e.g. "097B8A"
* @param {number} [finalBytes] - Optional. The desired number of bytes
* (not nibbles) that the returned array should contain.
* @returns {Array} An array of nibbles.
*/
str2Bytes: function (str, finalBytes) {
if (finalBytes) {
while ((str.length / 2) < finalBytes) { str = "0" + str; }
}
var bytes = [];
for (var i=str.length-1; i>=0; i = i-2) {
var chars = i === 0 ? str[i] : str[i-1] + str[i];
bytes.unshift(parseInt(chars, 16));
}
return bytes;
},
/**
* Translates number of ticks to MIDI timestamp format, returning an array
* of bytes with the time values. MIDI has a very particular way to express
* time; take a good look at the spec before ever touching this function.
*
* @param {number} ticks - Number of ticks to be translated.
* @returns {number} Array of bytes that form the MIDI time value.
*/
translateTickTime: function(ticks) {
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
buffer <<= 8;
buffer |= ((ticks & 0x7F) | 0x80);
}
var bList = [];
while (true) {
bList.push(buffer & 0xff);
if (buffer & 0x80) { buffer >>= 8; }
else { break; }
}
return bList;
},
};
/* ******************************************************************
* Event class
****************************************************************** */
/**
* Construct a MIDI event.
*
* Parameters include:
* - time [optional number] - Ticks since previous event.
* - type [required number] - Type of event.
* - channel [required number] - Channel for the event.
* - param1 [required number] - First event parameter.
* - param2 [optional number] - Second event parameter.
*/
var MidiEvent = function(params) {
if (!this) return new MidiEvent(params);
if (params &&
(params.type !== null || params.type !== undefined) &&
(params.channel !== null || params.channel !== undefined) &&
(params.param1 !== null || params.param1 !== undefined)) {
this.setTime(params.time);
this.setType(params.type);
this.setChannel(params.channel);
this.setParam1(params.param1);
this.setParam2(params.param2);
}
};
// event codes
MidiEvent.NOTE_OFF = 0x80;
MidiEvent.NOTE_ON = 0x90;
MidiEvent.AFTER_TOUCH = 0xA0;
MidiEvent.CONTROLLER = 0xB0;
MidiEvent.PROGRAM_CHANGE = 0xC0;
MidiEvent.CHANNEL_AFTERTOUCH = 0xD0;
MidiEvent.PITCH_BEND = 0xE0;
/**
* Set the time for the event in ticks since the previous event.
*
* @param {number} ticks - The number of ticks since the previous event. May
* be zero.
*/
MidiEvent.prototype.setTime = function(ticks) {
this.time = Util.translateTickTime(ticks || 0);
};
/**
* Set the type of the event. Must be one of the event codes on MidiEvent.
*
* @param {number} type - Event type.
*/
MidiEvent.prototype.setType = function(type) {
if (type < MidiEvent.NOTE_OFF || type > MidiEvent.PITCH_BEND) {
throw new Error("Trying to set an unknown event: " + type);
}
this.type = type;
};
/**
* Set the channel for the event. Must be between 0 and 15, inclusive.
*
* @param {number} channel - The event channel.
*/
MidiEvent.prototype.setChannel = function(channel) {
if (channel < 0 || channel > 15) {
throw new Error("Channel is out of bounds.");
}
this.channel = channel;
};
/**
* Set the first parameter for the event. Must be between 0 and 255,
* inclusive.
*
* @param {number} p - The first event parameter value.
*/
MidiEvent.prototype.setParam1 = function(p) {
this.param1 = p;
};
/**
* Set the second parameter for the event. Must be between 0 and 255,
* inclusive.
*
* @param {number} p - The second event parameter value.
*/
MidiEvent.prototype.setParam2 = function(p) {
this.param2 = p;
};
/**
* Serialize the event to an array of bytes.
*
* @returns {Array} The array of serialized bytes.
*/
MidiEvent.prototype.toBytes = function() {
var byteArray = [];
var typeChannelByte = this.type | (this.channel & 0xF);
byteArray.push.apply(byteArray, this.time);
byteArray.push(typeChannelByte);
byteArray.push(this.param1);
// Some events don't have a second parameter
if (this.param2 !== undefined && this.param2 !== null) {
byteArray.push(this.param2);
}
return byteArray;
};
/* ******************************************************************
* MetaEvent class
****************************************************************** */
/**
* Construct a meta event.
*
* Parameters include:
* - time [optional number] - Ticks since previous event.
* - type [required number] - Type of event.
* - data [optional array|string] - Event data.
*/
var MetaEvent = function(params) {
if (!this) return new MetaEvent(params);
var p = params || {};
this.setTime(params.time);
this.setType(params.type);
this.setData(params.data);
};
MetaEvent.SEQUENCE = 0x00;
MetaEvent.TEXT = 0x01;
MetaEvent.COPYRIGHT = 0x02;
MetaEvent.TRACK_NAME = 0x03;
MetaEvent.INSTRUMENT = 0x04;
MetaEvent.LYRIC = 0x05;
MetaEvent.MARKER = 0x06;
MetaEvent.CUE_POINT = 0x07;
MetaEvent.CHANNEL_PREFIX = 0x20;
MetaEvent.END_OF_TRACK = 0x2f;
MetaEvent.TEMPO = 0x51;
MetaEvent.SMPTE = 0x54;
MetaEvent.TIME_SIG = 0x58;
MetaEvent.KEY_SIG = 0x59;
MetaEvent.SEQ_EVENT = 0x7f;
/**
* Set the time for the event in ticks since the previous event.
*
* @param {number} ticks - The number of ticks since the previous event. May
* be zero.
*/
MetaEvent.prototype.setTime = function(ticks) {
this.time = Util.translateTickTime(ticks || 0);
};
/**
* Set the type of the event. Must be one of the event codes on MetaEvent.
*
* @param {number} t - Event type.
*/
MetaEvent.prototype.setType = function(t) {
this.type = t;
};
/**
* Set the data associated with the event. May be a string or array of byte
* values.
*
* @param {string|Array} d - Event data.
*/
MetaEvent.prototype.setData = function(d) {
this.data = d;
};
/**
* Serialize the event to an array of bytes.
*
* @returns {Array} The array of serialized bytes.
*/
MetaEvent.prototype.toBytes = function() {
if (!this.type) {
throw new Error("Type for meta-event not specified.");
}
var byteArray = [];
byteArray.push.apply(byteArray, this.time);
byteArray.push(0xFF, this.type);
// If data is an array, we assume that it contains several bytes. We
// apend them to byteArray.
if (Array.isArray(this.data)) {
byteArray.push(this.data.length);
byteArray.push.apply(byteArray, this.data);
} else if (typeof this.data == 'number') {
byteArray.push(1, this.data);
} else if (this.data !== null && this.data !== undefined) {
// assume string; may be a bad assumption
byteArray.push(this.data.length);
var dataBytes = this.data.split('').map(function(x){ return x.charCodeAt(0) });
byteArray.push.apply(byteArray, dataBytes);
} else {
byteArray.push(0);
}
return byteArray;
};
/* ******************************************************************
* Track class
****************************************************************** */
/**
* Construct a MIDI track.
*
* Parameters include:
* - events [optional array] - Array of events for the track.
*/
var Track = function(config) {
if (!this) return new Track(config);
var c = config || {};
this.events = c.events || [];
};
Track.START_BYTES = [0x4d, 0x54, 0x72, 0x6b];
Track.END_BYTES = [0x00, 0xFF, 0x2F, 0x00];
/**
* Add an event to the track.
*
* @param {MidiEvent|MetaEvent} event - The event to add.
* @returns {Track} The current track.
*/
Track.prototype.addEvent = function(event) {
this.events.push(event);
return this;
};
/**
* Add a note-on event to the track.
*
* @param {number} channel - The channel to add the event to.
* @param {number|string} pitch - The pitch of the note, either numeric or
* symbolic.
* @param {number} [time=0] - The number of ticks since the previous event,
* defaults to 0.
* @param {number} [velocity=90] - The volume for the note, defaults to
* DEFAULT_VOLUME.
* @returns {Track} The current track.
*/
Track.prototype.addNoteOn = Track.prototype.noteOn = function(channel, pitch, time, velocity) {
this.events.push(new MidiEvent({
type: MidiEvent.NOTE_ON,
channel: channel,
param1: Util.ensureMidiPitch(pitch),
param2: velocity || DEFAULT_VOLUME,
time: time || 0,
}));
return this;
};
/**
* Add a note-off event to the track.
*
* @param {number} channel - The channel to add the event to.
* @param {number|string} pitch - The pitch of the note, either numeric or
* symbolic.
* @param {number} [time=0] - The number of ticks since the previous event,
* defaults to 0.
* @param {number} [velocity=90] - The velocity the note was released,
* defaults to DEFAULT_VOLUME.
* @returns {Track} The current track.
*/
Track.prototype.addNoteOff = Track.prototype.noteOff = function(channel, pitch, time, velocity) {
this.events.push(new MidiEvent({
type: MidiEvent.NOTE_OFF,
channel: channel,
param1: Util.ensureMidiPitch(pitch),
param2: velocity || DEFAULT_VOLUME,
time: time || 0,
}));
return this;
};
/**
* Add a note-on and -off event to the track.
*
* @param {number} channel - The channel to add the event to.
* @param {number|string} pitch - The pitch of the note, either numeric or
* symbolic.
* @param {number} dur - The duration of the note, in ticks.
* @param {number} [time=0] - The number of ticks since the previous event,
* defaults to 0.
* @param {number} [velocity=90] - The velocity the note was released,
* defaults to DEFAULT_VOLUME.
* @returns {Track} The current track.
*/
Track.prototype.addNote = Track.prototype.note = function(channel, pitch, dur, time, velocity) {
this.noteOn(channel, pitch, time, velocity);
if (dur) {
this.noteOff(channel, pitch, dur, velocity);
}
return this;
};
/**
* Add a note-on and -off event to the track for each pitch in an array of pitches.
*
* @param {number} channel - The channel to add the event to.
* @param {array} chord - An array of pitches, either numeric or
* symbolic.
* @param {number} dur - The duration of the chord, in ticks.
* @param {number} [velocity=90] - The velocity of the chord,
* defaults to DEFAULT_VOLUME.
* @returns {Track} The current track.
*/
Track.prototype.addChord = Track.prototype.chord = function(channel, chord, dur, velocity) {
if (!Array.isArray(chord) && !chord.length) {
throw new Error('Chord must be an array of pitches');
}
chord.forEach(function(note) {
this.noteOn(channel, note, 0, velocity);
}, this);
chord.forEach(function(note, index) {
if (index === 0) {
this.noteOff(channel, note, dur);
} else {
this.noteOff(channel, note);
}
}, this);
return this;
};
/**
* Set instrument for the track.
*
* @param {number} channel - The channel to set the instrument on.
* @param {number} instrument - The instrument to set it to.
* @param {number} [time=0] - The number of ticks since the previous event,
* defaults to 0.
* @returns {Track} The current track.
*/
Track.prototype.setInstrument = Track.prototype.instrument = function(channel, instrument, time) {
this.events.push(new MidiEvent({
type: MidiEvent.PROGRAM_CHANGE,
channel: channel,
param1: instrument,
time: time || 0,
}));
return this;
};
/**
* Set the tempo for the track.
*
* @param {number} bpm - The new number of beats per minute.
* @param {number} [time=0] - The number of ticks since the previous event,
* defaults to 0.
* @returns {Track} The current track.
*/
Track.prototype.setTempo = Track.prototype.tempo = function(bpm, time) {
this.events.push(new MetaEvent({
type: MetaEvent.TEMPO,
data: Util.mpqnFromBpm(bpm),
time: time || 0,
}));
return this;
};
/**
* Serialize the track to an array of bytes.
*
* @returns {Array} The array of serialized bytes.
*/
Track.prototype.toBytes = function() {
var trackLength = 0;
var eventBytes = [];
var startBytes = Track.START_BYTES;
var endBytes = Track.END_BYTES;
var addEventBytes = function(event) {
var bytes = event.toBytes();
trackLength += bytes.length;
eventBytes.push.apply(eventBytes, bytes);
};
this.events.forEach(addEventBytes);
// Add the end-of-track bytes to the sum of bytes for the track, since
// they are counted (unlike the start-of-track ones).
trackLength += endBytes.length;
// Makes sure that track length will fill up 4 bytes with 0s in case
// the length is less than that (the usual case).
var lengthBytes = Util.str2Bytes(trackLength.toString(16), 4);
return startBytes.concat(lengthBytes, eventBytes, endBytes);
};
/* ******************************************************************
* File class
****************************************************************** */
/**
* Construct a file object.
*
* Parameters include:
* - ticks [optional number] - Number of ticks per beat, defaults to 128.
* Must be 1-32767.
* - tracks [optional array] - Track data.
*/
var File = function(config){
if (!this) return new File(config);
var c = config || {};
if (c.ticks) {
if (typeof c.ticks !== 'number') {
throw new Error('Ticks per beat must be a number!');
return;
}
if (c.ticks <= 0 || c.ticks >= (1 << 15) || c.ticks % 1 !== 0) {
throw new Error('Ticks per beat must be an integer between 1 and 32767!');
return;
}
}
this.ticks = c.ticks || 128;
this.tracks = c.tracks || [];
};
File.HDR_CHUNKID = "MThd"; // File magic cookie
File.HDR_CHUNK_SIZE = "\x00\x00\x00\x06"; // Header length for SMF
File.HDR_TYPE0 = "\x00\x00"; // Midi Type 0 id
File.HDR_TYPE1 = "\x00\x01"; // Midi Type 1 id
/**
* Add a track to the file.
*
* @param {Track} track - The track to add.
*/
File.prototype.addTrack = function(track) {
if (track) {
this.tracks.push(track);
return this;
} else {
track = new Track();
this.tracks.push(track);
return track;
}
};
/**
* Serialize the MIDI file to an array of bytes.
*
* @returns {Array} The array of serialized bytes.
*/
File.prototype.toBytes = function() {
var trackCount = this.tracks.length.toString(16);
// prepare the file header
var bytes = File.HDR_CHUNKID + File.HDR_CHUNK_SIZE;
// set Midi type based on number of tracks
if (parseInt(trackCount, 16) > 1) {
bytes += File.HDR_TYPE1;
} else {
bytes += File.HDR_TYPE0;
}
// add the number of tracks (2 bytes)
bytes += Util.codes2Str(Util.str2Bytes(trackCount, 2));
// add the number of ticks per beat (currently hardcoded)
bytes += String.fromCharCode((this.ticks/256), this.ticks%256);;
// iterate over the tracks, converting to bytes too
this.tracks.forEach(function(track) {
bytes += Util.codes2Str(track.toBytes());
});
return bytes;
};
/* ******************************************************************
* Exports
****************************************************************** */
exported.Util = Util;
exported.File = File;
exported.Track = Track;
exported.Event = MidiEvent;
exported.MetaEvent = MetaEvent;
})( Midi );
if (typeof module != 'undefined' && module !== null) {
module.exports = Midi;
} else if (typeof exports != 'undefined' && exports !== null) {
exports = Midi;
} else {
this.Midi = Midi;
}

View File

@ -1,100 +0,0 @@
module.exports = function(channel, client, maxNoteLength = 3000){
if (!client) {
client = new (require('./Client.js'))('ws://www.multiplayerpiano.com:443');
client.setChannel(channel || "lobby");
client.start();
}
var Midi = require('./jsmidgen.js');
function key2number(note_name) {
var MIDI_KEY_NAMES = ["a-1","as-1","b-1","c0","cs0","d0","ds0","e0","f0","fs0","g0","gs0","a0","as0","b0","c1","cs1","d1","ds1","e1","f1","fs1","g1","gs1","a1","as1","b1","c2","cs2","d2","ds2","e2","f2","fs2","g2","gs2","a2","as2","b2","c3","cs3","d3","ds3","e3","f3","fs3","g3","gs3","a3","as3","b3","c4","cs4","d4","ds4","e4","f4","fs4","g4","gs4","a4","as4","b4","c5","cs5","d5","ds5","e5","f5","fs5","g5","gs5","a5","as5","b5","c6","cs6","d6","ds6","e6","f6","fs6","g6","gs6","a6","as6","b6","c7"];
var MIDI_TRANSPOSE = -12;
var note_number = MIDI_KEY_NAMES.indexOf(note_name);
if (note_number == -1) return;
note_number = note_number + 9 - MIDI_TRANSPOSE;
return note_number;
}
var midiFile = new Midi.File();
var startTime = Date.now();
var players = {};
function addPlayer(participant) {
players[participant._id] = {
track: midiFile.addTrack(),
lastNoteTime: startTime,
keys: {}
}
var track_name = `${participant.name} ${participant._id}`;
players[participant._id].track.addEvent(new Midi.MetaEvent({type: Midi.MetaEvent.TRACK_NAME, data: track_name }));
}
function addNote(note_name, vel, participant, isStopNote) {
if (!players.hasOwnProperty([participant._id])) addPlayer(participant);
var player = players[participant._id];
if (!player.keys.hasOwnProperty(note_name)) player.keys[note_name] = {};
var playerkey = player.keys[note_name];
var note_number = key2number(note_name);
if (!note_number) return;
var time_ms = Date.now() - player.lastNoteTime;
var time_ticks = time_ms * 0.256; // 0.256 ticks per millisecond, based on 128 ticks per beat and 120 beats per minute
var midiVel = vel * 127;
//player.track[isStopNote ? 'addNoteOff' : 'addNoteOn'](0, note_number, time_ticks, midiVel); // easy way
// but we need to maintain proper on/off order and limit note lengths for it to (dis)play properly in all midi players etc
if (isStopNote) {
if (!playerkey.isPressed) return;
player.track.addNoteOff(0, note_number, time_ticks, midiVel);
playerkey.isPressed = false;
clearTimeout(playerkey.timeout);
} else {
if (playerkey.isPressed) {
player.track.addNoteOff(0, note_number, time_ticks, midiVel);
player.track.addNoteOn(0, note_number, 0, midiVel);
clearTimeout(playerkey.timeout);
} else {
player.track.addNoteOn(0, note_number, time_ticks, midiVel);
}
playerkey.isPressed = true;
playerkey.timeout = setTimeout(addNote, maxNoteLength, note_name, vel, participant, true);
}
player.lastNoteTime = Date.now();
}
function save(){
var file = midiFile.toBytes();
midiFile = new Midi.File();
players = {};
startTime = Date.now();
return file;
}
client.on("n", function (msg) {
var DEFAULT_VELOCITY = 0.5;
var TIMING_TARGET = 1000;
var t = msg.t - client.serverTimeOffset + TIMING_TARGET - Date.now();
var participant = client.findParticipantById(msg.p);
msg.n.forEach(note => {
var ms = t + (note.d || 0);
if (ms < 0) ms = 0; else if (ms > 10000) return;
if (note.s) {
setTimeout(addNote, ms, note.n, undefined, participant, true);
} else {
var vel = parseFloat(note.v) || DEFAULT_VELOCITY;
if (vel < 0) vel = 0; else if (vel > 1) vel = 1;
setTimeout(addNote, ms, note.n, vel, participant, false);
}
});
});
return {client, save, startTime};
}

View File

@ -1,40 +0,0 @@
dClient.on("guildMemberAdd", async function(member){
if (member.guild.id == config.guildID)
this.emit("local_guildMemberAdd", member);
});
dClient.on("guildMemberRemove", async function(member){
if (member.guild.id == config.guildID)
this.emit("local_guildMemberRemove", member);
});
dClient.on("guildMemberUpdate", async function(oldMember, newMember){
if (newMember.guild.id == config.guildID)
this.emit("local_guildMemberUpdate", oldMember, newMember);
});
dClient.on("message", async function(message){
if (message.guild && message.guild.id == config.guildID)
this.emit("local_message", message);
});
dClient.on("messageUpdate", async function(oldMessage, newMessage){
if (newMessage.guild && newMessage.guild.id == config.guildID)
this.emit("local_messageUpdate", oldMessage, newMessage);
});
dClient.on("messageDelete", async function(message){
if (message.guild && message.guild.id == config.guildID)
this.emit("local_messageDelete", message);
});
dClient.on("presenceUpdate", async function(oldPresence, newPresence){
if (newPresence.member && newPresence.member.guild.id == config.guildID)
this.emit("local_presenceUpdate", oldPresence, newPresence);
});
dClient.on("voiceStateUpdate", async function(oldState, newState){
if (newState.guild.id == config.guildID)
this.emit("local_voiceStateUpdate", oldState, newState);
});
dClient.on("emojiDelete", async function(emoji){
if (emoji.guild.id == config.guildID)
this.emit("local_emojiDelete", emoji);
});
dClient.on("messageReactionAdd", async function(messageReaction, user){
if (messageReaction.message.guild.id == config.guildID)
this.emit("local_messageReactionAdd", messageReaction, user);
});

View File

@ -1,61 +0,0 @@
require('./util');
global.config = require('./config');
if (config.testmode) console.log('TEST MODE');
global.exitHook = require('async-exit-hook');
global.Discord = require('discord.js');
global.fs = require('fs');
global.dClient = new Discord.Client({ disableEveryone: true });
// error handling
{
let webhook = new Discord.WebhookClient(config.webhooks.error[0], config.webhooks.error[1]);
global.onError = function logError(error, title) {
let msg = error && (error.stack || error.message || error);
console.error(title + ':\n', msg);
try {
webhook.send(`${title ? `**${title}:**` : ""}\`\`\`\n${msg}\n\`\`\``).catch(()=>{});
} catch(e) {}
}
process.on('unhandledRejection', error => onError(error, "Unhandled Rejection"));
exitHook.uncaughtExceptionHandler(error => onError(error, "Uncaught Exception"));
dClient.on('error', error => onError(error, "Discord Client Error"));
dClient.on('warn', error => onError(error, "Discord Client Warning"));
}
global.dbClient = new (require('pg').Client)({
connectionString: process.env.DATABASE_URL,
ssl: !testmode,
});
console.log("Connecting to Postgres…")
dbClient.connect().then(function(){
console.log("Connecting to MongoDB…");
(require('mongodb').MongoClient).connect(config.MONGODB_URI).then(client=>{
global.mdbClient = client;
console.log("Connecting to Discord…");
dClient.login(config.DISCORD_TOKEN);
}, function(err){
console.error("Failed to connect to MongoDB:\n", err.stack);
process.exit(1);
});
}, function(err){
console.error("Failed to connect to Postgres:\n", err.stack);
process.exit(1);
});
dClient.once('ready', () => {
console.log('Discord Client Ready');
dClient.defaultGuild = dClient.guilds.get(config.guildID);
require('./local_events');
require('./commands');
require('./colorroles');
require('./mppbridger');
require('./screenshotter');
require('./misc');
require('./ddpbridge');
require('./prbridge');
require('./rocketbridge.js');
});

View File

@ -1,146 +0,0 @@
// join/leave
(async function(){
var webhook = new Discord.WebhookClient(config.webhooks.welcome[0], config.webhooks.welcome[1]);
dClient.on('local_guildMemberAdd', async member => {
webhook.send(`${member} joined.`, {username: member.user.username, avatarURL: member.user.displayAvatarURL({format:'png',size:2048}), disableEveryone:true});
});
dClient.on('local_guildMemberRemove', async member => {
webhook.send(`${member.user.tag} left.`, {username: member.user.username, avatarURL: member.user.displayAvatarURL({format:'png',size:2048}), disableEveryone:true});
});
})();
// view deleted channels
(async function(){
var vcid = config.channels.view_deleted_channels;
var rid = config.roles.viewing_deleted_channels;
dClient.on('local_voiceStateUpdate', async (oldState, newState) => {
if (oldState.channelID != vcid && newState.channelID == vcid) {
// member joined the channel
newState.member.roles.add(newState.member.guild.roles.get(rid));
} else if (oldState.channelID == vcid && newState.channelID != vcid) {
// member left the channel
newState.member.roles.remove(newState.member.guild.roles.get(rid));
}
});
})();
// prevent identical display names
/*{
let onName = async function(member){
let names = member.guild.members.map(m => m.name);
if (names.includes(member.displayName)) {
let nam = member.displayName.split(' ');
let num = nam.pop();
if (isNaN(num)) {
nam.push(num);
num = "2";
} else {
num = String(++num);
}
nam = nam.substr(0, num.length-1);
await member.setNickname(`${nam} ${num}`);
}
}
dClient.on("local_guildMemberUpdate", async (oldMember, newMember) => {
if (oldMember.displayName != newMember.displayName) onName(newMember);
});
}*/// didn't work D:
/*// arrange bots at bottom of list
(async function(){
let prefix = "\udb40\uddf0";//TODO find new chars that aren't filtered
let onNick = async member => {
if (member.user.bot && !member.displayName.startsWith(prefix))
await member.setNickname(`${prefix}${member.displayName}`.substr(0,32));
};
dClient.on('local_guildMemberAdd', onNick);
dClient.on('local_guildMemberUpdate', async (oldMember, newMember) => {
if (newMember.displayName != oldMember.displayName) await onNick(newMember);
});
})();*/
// prevent identical display names
/*(async function(){
dClient.on("local_guildMemberUpdate", async (oldMember, newMember) => {
//var displayNames = newMember.guild.members.map(m => m.displayName);
//if (newMember.nickname && displayNames.includes(newMember.nickname)) newMember.setNickname('');
//else if (displayNames.includes(newMember.displayName)) newMember.setNickname(`${newMember.displayName}_`.substr(0,32));
for (let thisMember of newMember.guild.members) {
thisMember = thisMember[1];
if (thisMember.id == newMember.id) continue; //THIS WAS SUPPOSED TO MAKE IT NOT REPEATEDLY CHANGE YOUR NAME!
if (newMember.nickname == thisMember.displayName) {
newMember.setNickname('');
break;
}
else if (newMember.displayName == thisMember.displayName) {
newMember.setNickname(`${newMember.displayName}_`.substr(0,32));
break;
}
}
});
})();*/
// persistent emojis
dClient.on("local_emojiDelete", async emoji => {
console.log("emoji deleted:", emoji.name, emoji.url);
if (global.disableEmojiProtection) return;
if (emoji.name.toLowerCase().includes('delete')) return;
async function readdEmoji() {
await emoji.guild.emojis.create(emoji.url, emoji.name);
delete readdEmoji;
}
// re-add emoji in 5 to 10 minutes
setTimeout(() => {
if (readdEmoji) readdEmoji();
}, 300000 + Math.random() * 300000);
// wouldn't want emoji to be lost if process is stopped before timeout ends
exitHook(callback => {
if (readdEmoji) readdEmoji().then(() => callback());
else callback();
});
});
// pinboard // this was removed but no harm leaving it working ¯\_(ツ)_/¯
(async function(){
var webhook = new Discord.WebhookClient(config.webhooks.pinboard[0], config.webhooks.pinboard[1]);
dClient.on("local_messageReactionAdd", async (messageReaction, user) => {
if (!(messageReaction.emoji.name == "📌" || messageReaction.emoji.name == "📍")) return;
if (!(user.id == messageReaction.message.author.id || messageReaction.message.guild.members.get(user.id).hasPermission('MANAGE_MESSAGES'))) return;// if message is theirs or user is mod
var message = messageReaction.message;
await webhook.send(`https://discordapp.com/channels/${message.guild.id}/${message.channel.id}/${message.id}`, {embeds:[{
color: (message.member && message.member.displayColor) || undefined,
author: {
name: (message.member && message.member.displayName) || message.author.username,
icon_url: message.author.avatarURL({format:'png'})
},
description: message.content,
timestamp: message.createdAt,
image: (message.attachments.first() && message.attachments.first().width) ? {url:message.attachments.first().url} : undefined,
footer: {
text: `#${message.channel.name}`
}
}]});
});
})();
// allow anyone to pin a message via reaction
dClient.on("local_messageReactionAdd", async (messageReaction) => {
if (messageReaction.emoji.name == "📌" || messageReaction.emoji.name == "📍")
await messageReaction.message.pin();
});

View File

@ -1,25 +0,0 @@
module.exports = {
usage: "<MPP user id>",
description: "Adds a perma-ban on the user",
aliases: ["permaban"],
exec: async function (msg) {
if (!msg.args[1]) return "EBADUSG";
var res = await dbClient.query('SELECT * FROM bridges WHERE discord_channel_id = $1;', [msg.channel.id]);
if (!res.rows.length) return "ENOTBRIDGE"
var bridge = res.rows[0];
if (bridge.owner_discord_user_id != msg.author.id) return msg.reply(`You are not the owner of this bridge.`);
var _id = msg.txt(1);
await dbClient.query("UPDATE bridges SET bans = array_append(bans, $1) WHERE discord_channel_id = $2", [_id, msg.channel.id]);
await msg.reply(`OK, I'll ban anyone whose user ID equals or starts with \`${_id}\` from this room, whenever possible.`);
var client = clients.MPP[bridge.mpp_room]
for (let p in client.ppl) {
p = client.ppl[p]
if (p._id.startsWith(_id))
client.sendArray([{m:'kickban', _id, ms: 60*60*1000}])
}
if (_id.length != 24) await msg.reply(":warning: The ID you gave me does not look like a full user ID (it is not 24 chars long). If it's a truncated ID it will still work, however it could possibly ban someone else whose user ID starts with those chars.")
},
}

View File

@ -1,45 +0,0 @@
module.exports = {
usage: "<MPP room>",
description: "Creates a bridge to the specified MPP room.",
exec: async function (msg) {
if (msg.author.id != config.opID) {
msg.reply(`Bridging has been disabled; ask <@${config.opID}> if you actually want to bridge this room.`);
return;
}
var site = 'MPP';
var room = msg.txt(1);
if (!room) return "EBADUSG";
var existingBridge = (await dbClient.query("SELECT * FROM bridges WHERE mpp_room = $1 AND site = 'MPP';", [room])).rows[0];
if (existingBridge) {
if (!existingBridge.disabled) {
return msg.reply(`${site} room ${room} is already bridged.`);
} else {
if (config.disabledRooms.includes(room)) {
return msg.reply(`You cannot bridge this room.`);
} else /* rebridge */ {
let channel = dClient.guilds.get(config.guildID).channels.get(existingBridge.discord_channel_id);
await dbClient.query("UPDATE bridges SET disabled = false WHERE mpp_room = $1 AND site = 'MPP'", [room]);
await channel.setParent(config.channels.mpp_bridges);
await new Promise(resolve => setTimeout(resolve, 500));
await channel.lockPermissions();
let existingClient = clients.MPP[room];
if (existingClient) existingClient.start();
else createMPPbridge(room, existingBridge.discord_channel_id, existingBridge.site, existingBridge.webhook_id, existingBridge.webhook_token);
await msg.reply(`${site} room ${room} has been re-bridged to ${channel}.`);
return;
}
}
}
/* new bridge */
var discordChannelName = room.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
var categoryID = config.channels.mpp_bridges;
var channel = await dClient.guilds.get(config.guildID).channels.create(discordChannelName, {parent: categoryID});
channel.setTopic(`http://www.multiplayerpiano.com/${encodeURIComponent(room)}`);
var webhook = await channel.createWebhook('Webhook');
createMPPbridge(room, channel.id, site, webhook.id, webhook.token);
dbClient.query('INSERT INTO bridges (site, mpp_room, discord_channel_id, webhook_id, webhook_token, owner_discord_user_id) VALUES ($1, $2, $3, $4, $5, $6)', [
site, room, channel.id, webhook.id, webhook.token, msg.author.id,
]);
msg.reply(`${site} room ${room} is now bridged to ${channel}.`);
}
};

View File

@ -1,32 +0,0 @@
module.exports = {
usage: "<'mpp'/'discord'> <Discord User ID or mention, or MPP _id>",
description: "Changes the MPP or Discord owner of a private bridge. The first argument must be either `mpp` or `discord`.",
aliases: ['changeowner', 'setowner'],
exec: async function (msg) {
if (msg.args.length < 3 || !['mpp','discord'].includes(msg.args[1])) return "EBADUSG";
var res = await dbClient.query('SELECT * FROM bridges WHERE discord_channel_id = $1;', [msg.channel.id]);
if (res.rows.length == 0) return msg.react('🚫');
var bridge = res.rows[0];
if (!(bridge.owner_discord_user_id == msg.author.id || msg.author.id == config.opID)) return msg.react('🚫');
if (msg.args[1] == 'discord') {
let selectedUser = dClient.users.get(msg.args[2]) || msg.mentions.users.first();
if (!selectedUser) return msg.react('⚠️');
msg.channel.overwritePermissions(selectedUser, {
MANAGE_CHANNELS: true,
MANAGE_ROLES: true,
MANAGE_WEBHOOKS: true,
MANAGE_MESSAGES: true
});
let po = msg.channel.permissionOverwrites.find(x => x.id == msg.author.id);
if (po) po.delete();
await dbClient.query('UPDATE bridges SET owner_discord_user_id = $1 WHERE discord_channel_id = $2;', [selectedUser.id, msg.channel.id]);
msg.channel.send(`Ownership of ${msg.channel} has been transferred to ${selectedUser}`);
} else if (msg.args[1] == 'mpp') {
let _id = msg.args[2];
await dbClient.query('UPDATE bridges SET owner_mpp__id = $1 WHERE discord_channel_id = $2;', [_id, msg.channel.id]);
msg.channel.send(`MPP user \`${_id}\` has been assigned as owner of the MPP room, and the crown will be transferred to them whenever possible.`);
//todo give crown if owner there
}
}
};

View File

@ -1,28 +0,0 @@
module.exports = {
description: "Lists online participants",
aliases: ['ppl', 'online'],
exec: async function (message) {
var row = (await dbClient.query("SELECT mpp_room, site FROM bridges WHERE discord_channel_id = $1;", [message.channel.id])).rows[0];
if (!row) {
//message.react('🚫');
message.reply(`Use this in a bridged room to see who is at the other side.`);
return;
}
var client = clients[row.site][row.mpp_room];
if (!client || !client.isConnected()) {
message.reply(`This bridge is not connected.`);
return;
}
var ppl = clients[row.site][row.mpp_room].ppl;
var numberOfPpl = Object.keys(ppl).length;
var str = `__**Participants Online (${numberOfPpl})**__\n`;
var names = [];
for (let person in ppl) {
person = ppl[person];
names.push(`\`${person._id.substr(0,6)}\` ${person.name.replace(/<@/g, "<\\@")}`);
}
str += names.join(', ');
message.channel.send(str, {split:{char:''}});
}
};

View File

@ -1,29 +0,0 @@
module.exports = {
usage: "[MPP Room]",
description: "Deletes a bridge to the specified MPP room.",
exec: async function (msg) {
var bridge = (await dbClient.query("SELECT * FROM bridges WHERE mpp_room = $1 OR discord_channel_id = $2", [msg.txt(1), msg.channel.id])).rows[0];
if (!bridge) {
//msg.react('⚠️');
msg.reply(`That room is not bridged. Make sure you type the MPP room name correctly.`);
return;
}
if (bridge.disabled) {
msg.reply(`That room has already been unbridged.`);
return;
}
if (!(bridge.owner_discord_user_id == msg.author.id || msg.author.id == config.opID)) {
//msg.react('🚫');
msg.reply(`You do not own that bridge.`);
return;
}
await dbClient.query("UPDATE bridges SET disabled = 'true' WHERE mpp_room = $1", [bridge.mpp_room]);
var client = clients.MPP[bridge.mpp_room];
if (client) client.stop();
var channel = dClient.channels.get(bridge.discord_channel_id)
await channel.setParent(config.channels.deleted_bridges);
await new Promise(resolve => setTimeout(resolve, 500));
await channel.lockPermissions();
msg.reply(`${bridge.mpp_room} has been unbridged.`);
}
};

View File

@ -1,319 +0,0 @@
var Client = require('../lib/Client.js');
global.clients = {};
global.createMPPbridge = function createMPPbridge(room, DiscordChannelID, site = 'MPP', webhookID, webhookToken) {
var DiscordChannel = dClient.channels.get(DiscordChannelID);
if (!DiscordChannel) return console.error(`Couldn't bridge ${site} ${room} because Discord Channel ${DiscordChannelID} is missing!`);
if (webhookID && webhookToken) var webhook = new Discord.WebhookClient(webhookID, webhookToken, {disableEveryone:true});
// discord message sending
{
let msgBuffer = [];
function _dSend(msg, embed) {
if (webhook && !config.testmode) {
let username = gClient.channel && gClient.channel._id || room;
if (username.length > 32) username = username.substr(0,31) + '…';
else if (username.length < 2) username = undefined;
webhook.send(msg, {username, embed, split:{char:''}}).catch(e => {
console.error(e);
DiscordChannel.send(msg, {embed, split:{char:''}}).catch(console.error);
});
}
else DiscordChannel.send(msg, {embed, split:{char:''}}).catch(console.error);
}
function dSend(msg) {
msgBuffer.push(msg);
}
setInterval(()=>{
if (msgBuffer.length == 0) return;
_dSend(msgBuffer.join('\n'));
msgBuffer = [];
}, 3000);
}
const gClient =
site == "MPP" ? new Client((site == "MPP" && room == "lobby") ? "ws://73.189.132.37:28886" : "ws://www.multiplayerpiano.com:443") :
site == "WOPP" ? new Client("wss://piano.ourworldofpixels.com") :
site == "MPT" ? new Client("wss://ts.terrium.net:8443") :
site == "VFDP" ? new Client("ws://www.visualfiredev.com:8080") :
site == "CMPC" ? new Client("ws://charsy.meowbin.com:16562") :
site == "BIMP" ? new Client("ws://104.237.150.24:8513/") :
undefined;
if (!gClient) return console.error(`Invalid site ${site}`);
gClient.setChannel(/*(site == "MPP" && room == "lobby") ? "lolwutsecretlobbybackdoor" : */room, {visible:false});
gClient.start();
// maintain the client's presence in the channel
gClient.channelCorrectorInterval = setInterval(()=>{
// if client is connected and not in a channel (meaning setChannel failed due to ratelimit because another client joined a channel with the same user within the last second) OR client is in a channel but it is not the right channel…
if ((gClient.isConnected() && !gClient.channel) || (gClient.channel && gClient.channel._id != room))
// …set the channel!
gClient.setChannel(room, {visible:false});
}, 1000);
let lastError;
gClient.on("error", error => {
console.error(`[${site}][${room}]`, error.message);
error = error.toString();
if (lastError != error) {
dSend(`**${error.toString()}**`);
lastError = error;
}
});
var isConnected = false; // TODO use gClient.isConnected() ?
gClient.on('connect', () => {
console.log(`[${site}][${room}] Connected to server`);
dSend(`**Connected to server; joining channel…**`);
isConnected = true;
lastError = undefined;
});
gClient.on('hi', ()=>{
console.log(`[${site}][${room}] Received greeting`);
if (!testmode) {
if (site == "MPP" && room == "lobby") {
gClient.sendArray([{m: "userset", set: {name: "Anonymous" }}]);
} else {
gClient.sendArray([{m: "userset", set: {name: config.mppname }}]);
}
}
gClient.sendArray([{m:'m',x:Math.floor(Math.random()*100),y:Math.floor(Math.random()*100)}])
});
gClient.on('disconnect', () => {
if (isConnected) {
console.log(`[${site}][${room}] Disconnected from server`);
dSend(`**Disconnected from server**`);
isConnected = false;
}
});
/*gClient.on('status', status => {
console.log(`[${site}] [${room}] ${status}`);
});*/
// on channel change
{
let lastCh;
gClient.on('ch', async msg => {
// announce channel join
if (!lastCh) {
dSend(`**Joined channel \`${msg.ch._id}\`**`);
console.log(`[${site}][${room}] Joined channel ${msg.ch._id}`);
}
// announce channel change
else if (msg.ch._id !== lastCh) {
dSend(`**Channel changed from \`${lastCh}\` to \`${msg.ch._id}\`**`);
console.log(`[${site}][${room}] Channel changed from ${lastCh} to ${msg.ch._id}`);
}
lastCh = msg.ch._id;
});
gClient.on("disconnect", () => lastCh = undefined);
}
// on chown
gClient.on('ch', async function(msg){
// catch dropped crown
if (msg.ch.crown && !msg.ch.crown.hasOwnProperty('participantId')) {
gClient.sendArray([{m:'chown', id: gClient.getOwnParticipant().id}]); // if possible
var avail_time = msg.ch.crown.time + 15000 - gClient.serverTimeOffset;
var ms = avail_time - Date.now();
setTimeout(()=> gClient.sendArray([{m:'chown', id: gClient.getOwnParticipant().id}]) , ms);
}
// transfer crown to owner
if (msg.ppl && msg.ch.crown && msg.ch.crown.participantId == gClient.getOwnParticipant().id) {
var res = await dbClient.query("SELECT owner_mpp__id FROM bridges WHERE mpp_room = $1 AND site = $2;", [room, site]);
if (res.rows.length == 0) return;
var owner = res.rows[0].owner_mpp__id;
if (!owner) return;
msg.ppl.some(part => {
if (part._id == owner) {
gClient.sendArray([{m:'chown', id: part.id}]);
return true;
} else return false;
});
}
});
// MPP to Discord
gClient.on('a', async msg => {
if (msg.p._id == gClient.getOwnParticipant()._id) return;
var id = msg.p._id.substr(0,6);
var name = sanitizeName(msg.p.name);
var content = escapeDiscordMentions(msg.a);
var str = `\`${id}\` **${name}:** ${content}`;
dSend(str);
});
// Discord to MPP
{
let msgQueue = [];
dClient.on('message', async message => {
if (message.channel.id !== DiscordChannelID || message.author.id == dClient.user.id || !message.member /*|| message.content.startsWith('!')*/) return;
var str = message.cleanContent;
var aname = `${message.member.displayName}#${message.member.user.discriminator}`;
if (str.startsWith('/') || str.startsWith('\\'))
msgQueue.push(`${aname}`);
else
str = `${aname}: ${str}`;
if (str.startsWith('\\')) str = str.slice(1);
if (message.attachments.first()) str += ' '+message.attachments.first().url;
if (str.length > 512) str = str.substr(0,511) + '…';
msgQueue.push(str);
});
setInterval(()=>{
gClient.sendArray([{m:'a', message: msgQueue.shift()}]);
}, 1600); // just about fastest without exceeding quota; I figured quota is 4 messages per 6 seconds in lobbies
}
// announce join/leave/rename
gClient.prependListener("p", async participant => {
if (gClient.ppl[participant.id]) { // is update
let oldName = gClient.ppl[participant.id].name, newName = participant.name;
if (newName != oldName)
dSend(`\`${participant._id.substr(0,6)}\` ___**${sanitizeName(oldName)}** changed their name to **${sanitizeName(newName)}**___`);
} else { // is join
dSend(`\`${participant._id.substr(0,6)}\` ___**${sanitizeName(participant.name)}** entered the room.___`);
}
});
gClient.prependListener("bye", async msg => {
var participant = gClient.ppl[msg.p];
dSend(`\`${participant._id.substr(0,6)}\` ___**${sanitizeName(participant.name)}** left the room.___`);
});
// on notifications
gClient.on('notification', async msg => {
// show notification
_dSend(undefined, {
title: msg.title,
description: msg.text || msg.html
});
// handle bans
if (msg.text && (msg.text.startsWith('Banned from') || msg.text.startsWith('Currently banned from'))) {
// Banned from "{room}" for {n} minutes.
// Currently banned from "{room}" for {n} minutes.
let arr = msg.text.split(' ');
arr.pop();
let minutes = arr.pop();
gClient.stop();
setTimeout(()=>{
gClient.setChannel(room);
gClient.start();
}, minutes*60*1000+3000);
dSend(`**Attempting to rejoin in ${minutes} minutes.**`);
}
});
// autoban perma-banned users
gClient.on("participant added", async part => {
var bans = (await dbClient.query("SELECT bans FROM bridges WHERE discord_channel_id = $1", [DiscordChannelID])).rows[0].bans;
if (!bans) return;
for (let b of bans)
if (part._id.startsWith(b))
gClient.sendArray([{m: "kickban", _id: part._id, ms: 60*60*1000}]);
})
// make room invisible when nobody else is in it
gClient.on("ch", async function(msg){
if (gClient.isOwner()) {
if (gClient.countParticipants() <= 1) {
gClient.sendArray([{m:'chset', set: { visible: false }}])
} else {
gClient.sendArray([{m:'chset', set: { visible: true }}])
}
}
});
// addons
{
// collect names
gClient.on('participant update', function(participant){
require('./namecollector').collect(participant);
});
// record raw data
//require('./datacollector')(gClient, site, room, DiscordChannel);
let createWSMessageCollector = require("../datacollector")
gClient.on("message", createWSMessageCollector(async function(data, startDate, endDate){
var attachmentName = `${site} ${room} raw data recording from ${startDate.toISOString()} to ${endDate.toISOString()} .txt.gz`;
await DiscordChannel.send(new Discord.MessageAttachment(data, attachmentName));
}));
}
if (!clients[site]) clients[site] = {};
clients[site][room] = gClient;
};
// start
(async function () {
var res = await dbClient.query('SELECT * FROM bridges;');
var sites = {};
res.rows.forEach(row => {
if (row.disabled) return;
if (!sites[row.site]) sites[row.site] = [];
sites[row.site].push(row);
});
for (let site in sites) {
let arr = sites[site];
arr.sort((a, b) => {return a.position - b.position});
let i = 0;
arr.forEach(bridge => {
createMPPbridge(bridge.mpp_room, bridge.discord_channel_id, bridge.site, bridge.webhook_id, bridge.webhook_token, bridge.owner_mpp__id);
});
}
})();
// commands
commands.bridge = require('./commands/bridge');
commands.unbridge = require('./commands/unbridge');
commands.chown = require('./commands/chown');
commands.list = require('./commands/list');
commands.ban = require('./commands/ban');

View File

@ -1,38 +0,0 @@
var nameCollector = module.exports = {
collection: mdbClient.db('heroku_jrtfvpd9').collection('ppl'),
collect: async function (participant) {
if (config.testmode) return;
if (participant.name == "Anonymous" || participant.name == "Anonymoose") return;
await new Promise(r => setTimeout(r, Math.random() * 10000));
var newMsg = function(continued){
var str = `__**${participant._id}**__${continued ? ' (continued)' : ''}\n${participant.name}`;
return dClient.channels.get(config.channels.name_collection).send(str);
}
var document = await this.collection.findOne({_id: participant._id});
if (document) {
// update person
if (document.names.includes(participant.name)) return;
document.names.push(participant.name);
this.collection.updateOne({_id: participant._id}, {$set:{names: document.names}});
let message = await dClient.channels.get(config.channels.name_collection).messages.fetch(document.discord_msg_id);
try {
await message.edit(message.content + ', ' + participant.name);
} catch(e) {
let message = await newMsg(true);
this.collection.updateOne({_id: participant._id}, {$set:{discord_msg_id: message.id}});
}
} else {
// add new person
let message = await newMsg();
nameCollector.collection.insertOne({
_id: participant._id,
discord_msg_id: message.id,
names: [participant.name]
});
}
}
}

View File

@ -1,48 +0,0 @@
const socketCluster = require("socketcluster-client")
const EventEmitter = require("events").EventEmitter;
class PRClient extends EventEmitter {
constructor(credentials, options) {
super()
EventEmitter.call(this);
this.options = options;
this.socket;
this.channels = {};
this.credentials = credentials
}
connect() {
if (!this.options) {
this.options = {
path: "/socketcluster/",
hostname: "www.pianorhythm.me",
port: 443,
secure: true,
autoReconnect: true,
}
}
// Initiate the connection to the server
this.socket = socketCluster.connect(this.options);
this.socket.connect();
this.socket.on("error", (msg) => {
console.error(msg);
})
this.socket.on("connect", () => {
console.log("Connected!")
this.socket.emit("enableAuthLogin", {
enable: true,
roomName: this.credentials.roomName
});
this.socket.emit("enableCursor", {
enable: true
});
this.socket.emit("login", {
password: this.credentials.password,
roomName: this.credentials.roomName,
username: this.credentials.username
});
this.socket.emit("getPlayerStats", {
"username": this.credentials.username
})
})
}
}
module.exports = PRClient;

View File

@ -1,54 +0,0 @@
const PRClient = require("./PRClient.js");
let client = new PRClient({// account stuff
username: "Discord",
password: config.pr_password,
roomName: 'lobby'
});
global.prClient = client;
client.connect();
client.socket.on("setRoom", function (data, callback) {
client.roomID = data.roomID;
try {
if (client.channels.chatChannel && client.roomID) {
client.socket.destroyChannel(client.roomID);
}
} catch (err) {}
client.channels.chatChannel = client.socket.subscribe(data.roomID);
client.channels.chatChannel.watch(messagehandle);
})
async function messagehandle(data) {
if (data && data.type) {
switch (data.type) {
case "chat":
if (data && data.message) {
let name = data.name || "";
let effect = data.effect || "";
let roomName = data.roomName;
let color = data.color;
let id = data.id;
if (id == client.socket.id) return;
let c = dClient.channels.get("593943518351982603");
let msg;
(!name && (data.message.startsWith("[i]") || data.message.startsWith("[d]"))) ? msg = `*${escapeDiscordMentions(data.message)}*` : msg = `**${sanitizeName(name)}:** ${escapeDiscordMentions(data.message)}`;
if (c) c.send(msg);
}
break;
}
}
}
dClient.on("local_message", async message => {
if (message.channel.id != "593943518351982603" || message.author.id == dClient.user.id) return;
if (!client.roomID) return;
client.socket.publish(client.roomID, {
"type": "chat",
"message": `${message.member && message.member.displayName || message.author.username}#${message.author.discriminator}: ${message.cleanContent + (message.attachments.size > 0 && message.attachments.map(x => x.url).join(' ') || '')}`,
"value": false,
"socketID": "[discord.gg/k44Eqha]",
"uuid": "[discord.gg/k44Eqha]",
"color": "#8012ed",
"name": "[discord.gg/k44Eqha]"
});
});

View File

@ -1,113 +0,0 @@
(async function() {
global.RocketChat = require("@rocket.chat/sdk");
var driver = RocketChat.driver;
var api = RocketChat.api;
await driver.connect();
await driver.login(); // using environment variables
await driver.subscribeToMessages();
var channelIdMap = {
"321819041348190249" : "ozfexWkyGuitsnAhR", // main,
"380431177812803584" : "3uSWaqgxCg8rEWjAa", // lab
"484965488992976897" : "p2QQqJ6AMEX7xK9Pi", // code
"372196271928246272" : "Q8CQNstBHEuyYFTxm", // media
"360985607209484290" : "3z6wiTRgEJd9xbf9H", // midi-files
};
// discord to rocket
dClient.on("local_message", async message => {
if (message.author.id == dClient.user.id || (message.channel.wh && message.channel.wh.id == message.author.id)) return;
var rid = channelIdMap[message.channel.id];
if (!rid) return;
var rcmsg = driver.prepareMessage();
rcmsg.rid = rid;
rcmsg.msg = message.cleanContent;
rcmsg.alias = message.member && message.member.displayName || message.author.username;
rcmsg.avatar = message.author.avatarURL({size:64}) || message.author.defaultAvatarURL;
rcmsg.attachments = message.attachments.array().map(attachment => ({
title: attachment.filename,
title_link: attachment.url,
title_link_download: true,
image_url: attachment.width ? attachment.url : undefined,
audio_url: [".ogg", ".mp3", ".wav", ".flac"].some(ext=>attachment.name.endsWith(ext)) ? attachment.url : undefined,
video_url: [".mp4", ".webm", ".mov", ".avi"].some(ext=>attachment.name.endsWith(ext)) ? attachment.url : undefined
})).concat(message.embeds.map(embed => embed.type == "rich" ? {
author_name: embed.author && embed.author.name,
author_link: embed.author && embed.author.url,
author_icon: embed.author && embed.author.iconURL,
title: embed.title,
title_link: embed.url,
title_link_download: false,
color: embed.hexColor,
text: embed.description,
image_url: embed.image && embed.image.url,
thumb_url: embed.thumbnail && embed.thumbnail.url,
fields: embed.fields && embed.fields.map(f => ({title: f.name, value: f.value, short: f.inline})),
ts: embed.timestamp
} : undefined)).filter(x=>x);
message.rcmsg = await driver.sendMessage(rcmsg);
});
dClient.on("local_messageUpdate", async function (oldMessage, newMessage) {
if (newMessage.rcmsg) {
await api.post('chat.update', {
roomId: newMessage.rcmsg.rid,
msgId: newMessage.rcmsg._id,
text: newMessage.cleanContent
});
}
});
dClient.on("local_messageDelete", async function (message) {
if (message.rcmsg) {
await api.post('chat.delete', {
roomId: message.rcmsg.rid,
msgId: message.rcmsg._id
});
}
});
// rocket to discord
var receivedRcMsgIDs = [];
driver.reactToMessages(async (e,m,mo) => {
if (e) return console.error(e);
if (receivedRcMsgIDs.includes(m._id)) return;
else receivedRcMsgIDs.push(m._id);
if (m.u._id == driver.userId) return;
if (!m.mentions && !m.channels) return;
var dcid;
for (let x in channelIdMap) if (channelIdMap[x] == m.rid) dcid = x;
if (!dcid) return;
var dc = dClient.channels.get(dcid);
if (!dc.wh) {
dc.wh = (await dc.fetchWebhooks()).find(w=>w.name=="fookat bridge");
}
try {
await dc.wh.send(m.msg,{
username: `${m.u.username} @ fookat.tk`.substr(0,32),
avatarURL: `https://fookat.tk/avatar/${m.u.username}?${process.pid}`,
split: true,
disableEveryone: true,
embeds: m.attachments ? m.attachments.map(a => ({
title: a.title,
url: a.title_link ? "https://fookat.tk" + a.title_link : undefined,
description: a.description,
image: a.image_url ? {url: "https://fookat.tk" + a.image_url} : undefined
})) : undefined
});
} catch(e) {
console.error(e);
await dc.send(`**${m.u.username}:** ${m.msg}`, {
split: true,
embeds: m.attachments ? m.attachments.map(a => ({
title: a.title,
url: a.title_link ? "https://fookat.tk" + a.title_link : undefined,
description: a.description,
image: a.image_url ? {url: "https://fookat.tk" + a.image_url} : undefined
})) : undefined
});
}
});
})();

View File

@ -1,48 +0,0 @@
global.screenshotter = {
capture: async function () {
console.log('Starting screen captures');
try {
var puppeteer = require('puppeteer');
var browser = await puppeteer.launch({ args: ['--no-sandbox'] });
var page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
try {
await page.goto('http://www.multiplayerpiano.com/lobby');
await page.evaluate(function () { document.getElementById('modal').click() });
await new Promise(resolve => setTimeout(resolve, 5000));
let screenshot = await page.screenshot({ type: 'png' });
let filename = `Screenshot of www.multiplayerpiano.com/lobby @ ${new Date().toISOString()}.png`;
let attachment = new Discord.MessageAttachment(screenshot, filename);
await dClient.channels.get(config.channels.mpp_screenshot).send(attachment);
} catch (error) {
await dClient.channels.get(config.channels.mpp_screenshot).send(`:warning: ${error.stack}`);
}
try {
await page.goto('http://ourworldofpixels.com');
await page.evaluate(function (owopcaptcha) {
localStorage.owopcaptcha = owopcaptcha;
OWOP.camera.zoom = 1;
}, require('./config').owop_captcha_password);
await new Promise(resolve => setTimeout(resolve, 2000));
await page.evaluate(function () {
for (let butt of document.getElementsByTagName('button')) {
if (butt.innerText == 'OK') {butt.click();break}
}
});
await new Promise(resolve => setTimeout(resolve, 5000));
let screenshot = await page.screenshot({ type: 'png' });
let filename = `Screenshot of ourworldofpixels.com/main @ ${new Date().toISOString()}.png`;
let attachment = new Discord.MessageAttachment(screenshot, filename);
await dClient.channels.get(config.channels.owop_screenshot).send(attachment);
} catch (error) {
await dClient.channels.get(config.channels.owop_screenshot).send(attachment);
}
} catch(error) {
console.error(`Error occured with screen capture:\n${error.stack}`)
} finally {
await browser.close();
console.log('Finished screen captures');
}
},
interval: setInterval(() => { screenshotter.capture(); }, 1000 * 60 * 60)
};

View File

@ -1,18 +0,0 @@
global.random = function (array) {
return array[Math.floor(Math.random() * array.length)]
}
global.sanitizeName = function sanitizeName(str){ // for showing names in discord
str = str.replace(/[_~*\\]/g,"\\$&"); // formatting
str = escapeDiscordMentions(str); // mentions
str = str.replace(/discord.gg\//g, 'discord.gg\\/'); // invites
str = str.replace(/(http|https):\/\//g, "$1\\://"); // urls
return str;
}
global.escapeDiscordMentions = function escapeDiscordMentions(str) { // escape discord mentions from a string to be sent to discord
str = str.replace(/<@/g, "<\\@"); // users & roles
// escaping channel mentions is not necessary
return str;
}

11
util.js Normal file
View File

@ -0,0 +1,11 @@
global.random = function (array) {
return array[Math.floor(Math.random() * array.length)]
}
global.sanitizeName = function sanitizeName(str){ // for showing names in discord
str = str.replace(/[_~*\\]/g,"\\$&"); // formatting
str = str.replace(/discord.gg\//g, 'discord.gg\\/'); // invites
str = str.replace(/(http|https):\/\//g, "$1\\://"); // urls
return str;
}