Compare commits
131 Commits
f6c71c94bd
...
ef4ccb8bc0
Author | SHA1 | Date |
---|---|---|
Lamp | ef4ccb8bc0 | |
Lamp | 995ac6d96f | |
Lamp | e21a264ed0 | |
Lamp | 1af74e957d | |
Lamp | 8bfd16a41f | |
Lamp | a86466efdb | |
Lamp | 999e71c5cc | |
Lamp | 6b29918703 | |
Lamp | 41a1f0533f | |
Lamp | 48b7791ae3 | |
Lamp | fe0c0bbe61 | |
Lamp | 7b934bf762 | |
Lamp | 44cd2cabb4 | |
Lamp | 9775aef89a | |
Lamp | aabebc7d74 | |
Lamp | 1fda5903d0 | |
Lamp | 35d1299897 | |
Lamp | fa1c39790a | |
Lamp | 14c8c8fba3 | |
Lamp | 198a8ceb3a | |
Lamp | e8b4334e3a | |
Lamp | 7fe2b0fa12 | |
Lamp | 3ef1bccb7a | |
Lamp | fb0c39b5c8 | |
Lamp | da4e033760 | |
Lamp | aee470b583 | |
Lamp | c7ec177bfc | |
Lamp | 5491cf25e6 | |
Lamp | fac15e380c | |
Lamp | 78c9e1e3b5 | |
Lamp | aba50ff519 | |
Lamp | f145354bec | |
Lamp | 849ae63144 | |
Lamp | 1b01804230 | |
Lamp | 006b2f2d77 | |
Lamp | ca719a52e5 | |
Lamp | c8e3353fe2 | |
Lamp | 3249697420 | |
Lamp | 885493413a | |
Lamp | 959d8481a5 | |
Lamp | ff135e5c5b | |
Lamp | 21fb1806bf | |
Lamp | bc03104ca9 | |
Lamp | d00ebe21f8 | |
Lamp | d7e81475b8 | |
Lamp | 4a5d4b8dd6 | |
Lamp | 90431b0fad | |
Lamp | 53e8257268 | |
Lamp | ff9bf4cdb9 | |
Lamp | b1527092b6 | |
Lamp | a16ccd44ba | |
Lamp | 7464475f62 | |
Lamp | bae1c51726 | |
Lamp | e24e1601cf | |
Lamp | 0b2bf7a94e | |
Lamp | 33004d0aef | |
Lamp | 0027431cbc | |
Lamp | 6b3ac80ea3 | |
Lamp | 0a2f20c13a | |
Lamp | 54a23acf4b | |
Lamp | 80adbda77a | |
Lamp | 7287ea9be6 | |
Lamp | 68fb02854c | |
Lamp | 2d24574ee6 | |
Lamp | bdb24c9650 | |
Lamp | 73efb1e419 | |
Lamp | 70d5cc4a39 | |
Lamp | abc3f017e6 | |
Lamp | 19cfcb7e91 | |
Lamp | 07083f802a | |
Lamp | f39316e82e | |
Lamp | 4bfea68b90 | |
Lamp | f13b7b9210 | |
Lamp | cba1796862 | |
Lamp | 7546f36068 | |
Lamp | 746410f0e0 | |
Lamp | f3c0ba8480 | |
Lamp | 3624cd46a7 | |
Lamp | b1113a30a0 | |
Lamp | 9a7c2178e8 | |
Lamp | 29a07865bd | |
Lamp | 9fca5dd9b0 | |
Lamp | e5f6e67da0 | |
Lamp | 7b60c97e40 | |
Lamp | 2fe521b05f | |
Lamp | 6538381e6d | |
Lamp | 658ea8d786 | |
Lamp | 0c054dcfa6 | |
Lamp | f1b47e2cb2 | |
Lamp | 2e76de2dbd | |
Lamp | cedf977146 | |
Lamp | 915f6eca0a | |
Lamp | 46fb4ea5e4 | |
Lamp | af21719ace | |
Lamp | 46555d28ef | |
Lamp | 311983c4e0 | |
Lamp | bf8308d776 | |
Lamp | 3c5d76fd31 | |
Lamp | 276a2afcc6 | |
Lamp | 520fa6c2cb | |
Lamp | 42f1229574 | |
Lamp | 0b9c03c0f9 | |
Lamp | acb186c05c | |
Lamp | ea2e6001c7 | |
Lamp | 2746e7dd24 | |
Lamp | 43138e2799 | |
Lamp | 2131484c0a | |
Lamp | 2ec0be4b84 | |
Lamp | aa60087da4 | |
Lamp | 5b0564c18e | |
Lamp | b1df3dceb2 | |
Lamp | 64aa828035 | |
Lamp | a058dfda49 | |
Lamp | 8789c72140 | |
Lamp | ccdfe465a3 | |
Lamp | bf6e7fad5e | |
Lamp | 56b017af28 | |
Lamp | b651317345 | |
Lamp | 8369f52f90 | |
Lamp | fddc6394ac | |
Lamp | 157b32fe79 | |
Lamp | 56987ae761 | |
Lamp | dd4ec17ff7 | |
Lamp | 28929d47bb | |
Lamp | 00bea9d9ab | |
Lamp | f2185a0887 | |
Lamp | 09f9e7e6df | |
Lamp | a088abc6c7 | |
Lamp | 1240618d55 | |
Lamp | 8c887ab79f | |
BopItFreak | a432d1942e |
|
@ -3,4 +3,5 @@ node_modules/
|
|||
.DS_Store
|
||||
test.sh
|
||||
.vscode
|
||||
tmp
|
||||
tmp
|
||||
.env
|
||||
|
|
1
Procfile
1
Procfile
|
@ -1 +0,0 @@
|
|||
worker: git clone https://github.com/ledlamp/k44Eqha.git --depth=1 && cd k44Eqha && exec node src/main.js
|
|
@ -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" }
|
||||
]
|
|
@ -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);
|
||||
});
|
|
@ -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,
|
||||
|
||||
}
|
|
@ -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}`);
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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);
|
|
@ -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);
|
||||
//});
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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');
|
|
@ -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();
|
||||
});
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("🆗");
|
||||
}
|
||||
}
|
160
src/commands.js
160
src/commands.js
|
@ -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);
|
||||
}
|
||||
)
|
||||
});
|
||||
});
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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;}
|
||||
};
|
||||
}
|
|
@ -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}`);
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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};
|
||||
}
|
|
@ -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);
|
||||
});
|
61
src/main.js
61
src/main.js
|
@ -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');
|
||||
|
||||
});
|
146
src/misc.js
146
src/misc.js
|
@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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.")
|
||||
|
||||
},
|
||||
}
|
|
@ -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}.`);
|
||||
}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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:''}});
|
||||
}
|
||||
};
|
|
@ -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.`);
|
||||
}
|
||||
};
|
|
@ -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');
|
||||
|
|
@ -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]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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]"
|
||||
});
|
||||
});
|
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
|
@ -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)
|
||||
};
|
18
src/util.js
18
src/util.js
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue