Publish
This commit is contained in:
commit
79227ee843
|
@ -0,0 +1,2 @@
|
|||
/local/
|
||||
/src/config.json
|
|
@ -0,0 +1,2 @@
|
|||
/src/
|
||||
/local/
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
(async function(){
|
||||
global.dbClient = new (require('pg').Client)({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: true,
|
||||
});
|
||||
await dbClient.connect();
|
||||
|
||||
var data = (await dbClient.query("SELECT content FROM files WHERE name = 'files.zip'")).rows[0].content;
|
||||
var buff = Buffer.from(data, 'base64');
|
||||
await (require('decompress'))(buff, 'files');
|
||||
|
||||
require('./files/src/main.js');
|
||||
|
||||
global['files.zip'] = buff;
|
||||
})().catch(error => {console.error(error.stack); process.exit(1);});
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"discord.js": "github:hydrabolt/discord.js",
|
||||
"pg": "^7.4.0",
|
||||
"puppeteer": "^0.13.0",
|
||||
"ws": "^3.3.2",
|
||||
"mongodb": "^3.0.0-rc0",
|
||||
"decompress": "^4.2.0",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"socket.io-client": "^2.0.4",
|
||||
"striptags": "~3.1.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
global.awakensBridge = {}
|
||||
awakensBridge.connect = function (uri, options) {
|
||||
var io = require('socket.io-client');
|
||||
var channel = new Discord.WebhookClient('342850770594562060', config.webhooks.awakens, {disableEveryone:true} );
|
||||
this.channel = channel;
|
||||
//test//var channel = new Discord.WebhookClient('399378912020529153', 'wdVr8ZvssmX9IF4cqS9dq3pxTUX9a9dNGN6Pusu5AzX60DQqBsWe6qxLagrFPgxksJQI', {disableEveryone:true} );
|
||||
var socket = io(uri||`http://this.awakens.me`, options||{
|
||||
extraHeaders: {
|
||||
'cf-connecting-ip': randomIp()
|
||||
}
|
||||
});
|
||||
this.socket = socket;
|
||||
|
||||
socket.on('connect', function() {
|
||||
console.log('Connected to awakens.me');
|
||||
var ip = socket.io && socket.io.opts && socket.io.opts.extraHeaders && socket.io.opts.extraHeaders['cf-connecting-ip'];
|
||||
channel.send(ip ? `**Connected with fake IP address \`${ip}\`**` : '**Connected**');
|
||||
socket.emit('requestJoin');
|
||||
});
|
||||
socket.on('disconnect', function() {
|
||||
console.log('Disconnected from awakens.me');
|
||||
channel.send('**Disconnected**');
|
||||
});
|
||||
|
||||
var online = {};
|
||||
socket.on('channeldata', (channel) => {
|
||||
if (channel.users) {
|
||||
channel.users.forEach(user => {
|
||||
online[user.id] = user.nick;
|
||||
});
|
||||
}
|
||||
});
|
||||
socket.on('nick', (id, newNick) => {
|
||||
var str = `**\\*\\* ${online[id]} is now known as ${newNick} \\*\\***`;
|
||||
//console.log(str);
|
||||
channel.send(str);
|
||||
online[id] = newNick;
|
||||
});
|
||||
socket.on('joined', (id, nick) => {
|
||||
var str = `**\\*\\* ${nick} has joined \\*\\***`;
|
||||
//console.log(str);
|
||||
channel.send(str);
|
||||
online[id] = nick;
|
||||
});
|
||||
socket.on('left', (id, part) => {
|
||||
var str = `**\\*\\* ${online[id]} has left${part ? ": "+part : ""} \\*\\***`;
|
||||
//console.log(str);
|
||||
channel.send(str);
|
||||
});
|
||||
|
||||
socket.on('message', function(messageData) {
|
||||
switch (messageData.messageType) {
|
||||
default: {
|
||||
if (typeof messageData.message != 'string') return console.error(messageData);
|
||||
let msg = messageData.nick ? `**${messageData.nick}:** ${filter(messageData.message)}` : `**\\*\\* ${messageData.message} \\*\\***`;
|
||||
//console.log(msg);
|
||||
channel.send(msg, {split:{char:''}});
|
||||
|
||||
/*if (messageData.message.startsWith("You've been kicked")) {
|
||||
console.log('Kicked from ', socket.io.uri);
|
||||
}
|
||||
if (messageData.message.startsWith("You've been banned")) {
|
||||
console.log('Banned from ', socket.io.uri);
|
||||
}*/
|
||||
|
||||
if (messageData.message.startsWith("You've been kicked") || messageData.message.startsWith("You've been banned")) {
|
||||
let ms = Math.random()*1000000;
|
||||
setTimeout(function(){
|
||||
awakensBridge.connect(); // create new socket with different ip header
|
||||
}, ms);
|
||||
channel.send(`**Reconnecting in \`${ms/60000}\` minutes.**`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "chat-image": {
|
||||
let msg = `**${messageData.nick}:**`;
|
||||
let img = Buffer.from(messageData.message.img, 'binary');
|
||||
let attachment = new Discord.MessageAttachment(img, 'image.'+messageData.message.type.split('/')[1]);
|
||||
channel.send(msg, attachment);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*client.on('message', message => {
|
||||
if (message.author !== client.user && message.channel === channel) {
|
||||
socket.emit('message', `/*${message.member.displayName}:| ${message.content}`);
|
||||
}
|
||||
});*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
awakensBridge.connect();
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function filter(str) {
|
||||
// escape
|
||||
// Convert chars to html codes
|
||||
//str = str.replace(/\n/g, '\\n');
|
||||
// str = str.replace(/&/gi, '&');
|
||||
// str = str.replace(/>/gi, '>');
|
||||
//str = str.replace(/</gi, '<');
|
||||
//str = str.replace(/"/gi, '"');
|
||||
//str = str.replace(/#/gi, '#');
|
||||
//str = str.replace(/\\n/g, '<br>');
|
||||
//str = str.replace(/\$/gi, '$');
|
||||
//str = str.replace(/'/gi, ''');
|
||||
//str = str.replace(/~/gi, '~');
|
||||
|
||||
//convert spaces
|
||||
//str = str.replace(/\s{2}/gi, ' ');
|
||||
|
||||
//str = str.replace(/(<br>)(.+)/g, '<div style="display:block;padding-left:3.5em;">$2</div>');
|
||||
|
||||
var coloreg = 'yellowgreen|yellow|whitesmoke|white|wheat|violet|turquoise|tomato|thistle|teal|tan|steelblue|springgreen|snow|slategray|slateblue|skyblue|silver|sienna|seashell|seagreen|sandybrown|salmon|saddlebrown|royalblue|rosybrown|red|rebeccapurple|purple|powderblue|plum|pink|peru|peachpuff|papayawhip|palevioletred|paleturquoise|palegreen|palegoldenrod|orchid|orangered|orange|olivedrab|olive|oldlace|navy|navajowhite|moccasin|mistyrose|mintcream|midnightblue|mediumvioletred|mediumturquoise|mediumspringgreen|mediumslateblue|mediumseagreen|mediumpurple|mediumorchid|mediumblue|mediumaquamarine|maroon|magenta|linen|limegreen|lime|lightyellow|lightsteelblue|lightslategray|lightskyblue|lightseagreen|lightsalmon|lightpink|lightgreen|lightgray|lightgoldenrodyellow|lightcyan|lightcoral|lightblue|lemonchiffon|lawngreen|lavenderblush|lavender|khaki|ivory|indigo|indianred|hotpink|honeydew|greenyellow|green|gray|goldenrod|gold|ghostwhite|gainsboro|fuchsia|forestgreen|floralwhite|firebrick|dodgerblue|dimgray|deepskyblue|deeppink|darkviolet|darkturquoise|darkslategray|darkslateblue|darkseagreen|darksalmon|darkred|darkorchid|darkorange|darkolivegreen|darkmagenta|darkkhaki|darkgreen|darkgray|darkgoldenrod|darkcyan|darkblue|cyan|crimson|cornsilk|cornflowerblue|coral|chocolate|chartreuse|cadetblue|transparent|burlywood|brown|blueviolet|blue|blanchedalmond|black|bisque|beige|azure|aquamarine|aqua|antiquewhite|aliceblue';
|
||||
|
||||
// fonts
|
||||
str = str.replace(/(\$|($))([\w \-\,®]*)\|(.*)$/g, "$4");
|
||||
str = str.replace(/(\£|(£))([\w \-\,®]*)\|(.*)$/g, "$4");
|
||||
|
||||
// colors
|
||||
str = str.replace(/###([\da-f]{6}|[\da-f]{3})(.+)$/gi, '$2');
|
||||
str = str.replace(/##([\da-f]{6}|[\da-f]{3})(.+)$/gi, '$2');
|
||||
str = str.replace(/#([\da-f]{6}|[\da-f]{3})(.+)$/gi, '$2');
|
||||
str = str.replace(RegExp('###(' + coloreg + ')(.+)$', 'gi'), '$2');
|
||||
str = str.replace(RegExp('##(' + coloreg + ')(.+)$', 'gi'), '$2');
|
||||
str = str.replace(RegExp('#(' + coloreg + ')(.+)$', 'gi'), '$2');
|
||||
|
||||
// styles
|
||||
str = str.replace(/\/\%%([^\%%]+)\%%/g, '$1');
|
||||
str = str.replace(/\/\^([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\*([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\%([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\_([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\-([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\~([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\#([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\+([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\!([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\$([^\|]+)\|?/g, '$1');
|
||||
str = str.replace(/\/\@([^\|]+)\|?/g, '$1');
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
function filter(str) {
|
||||
var multiple = function (str, mtch, rep, limit) {
|
||||
var ct = 0;
|
||||
limit = limit || 3000;
|
||||
while (str.match(mtch) !== null && ct++ < limit) {
|
||||
str = str.replace(mtch, rep);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
var coloreg = 'yellowgreen|yellow|whitesmoke|white|wheat|violet|turquoise|tomato|thistle|teal|tan|steelblue|springgreen|snow|slategray|slateblue|skyblue|silver|sienna|seashell|seagreen|sandybrown|salmon|saddlebrown|royalblue|rosybrown|red|rebeccapurple|purple|powderblue|plum|pink|peru|peachpuff|papayawhip|palevioletred|paleturquoise|palegreen|palegoldenrod|orchid|orangered|orange|olivedrab|olive|oldlace|navy|navajowhite|moccasin|mistyrose|mintcream|midnightblue|mediumvioletred|mediumturquoise|mediumspringgreen|mediumslateblue|mediumseagreen|mediumpurple|mediumorchid|mediumblue|mediumaquamarine|maroon|magenta|linen|limegreen|lime|lightyellow|lightsteelblue|lightslategray|lightskyblue|lightseagreen|lightsalmon|lightpink|lightgreen|lightgray|lightgoldenrodyellow|lightcyan|lightcoral|lightblue|lemonchiffon|lawngreen|lavenderblush|lavender|khaki|ivory|indigo|indianred|hotpink|honeydew|greenyellow|green|gray|goldenrod|gold|ghostwhite|gainsboro|fuchsia|forestgreen|floralwhite|firebrick|dodgerblue|dimgray|deepskyblue|deeppink|darkviolet|darkturquoise|darkslategray|darkslateblue|darkseagreen|darksalmon|darkred|darkorchid|darkorange|darkolivegreen|darkmagenta|darkkhaki|darkgreen|darkgray|darkgoldenrod|darkcyan|darkblue|cyan|crimson|cornsilk|cornflowerblue|coral|chocolate|chartreuse|cadetblue|transparent|burlywood|brown|blueviolet|blue|blanchedalmond|black|bisque|beige|azure|aquamarine|aqua|antiquewhite|aliceblue';
|
||||
|
||||
// fonts
|
||||
str = multiple(str, /(\$|($))([\w \-\,®]*)\|(.*)$/, '$4');
|
||||
str = multiple(str, /(\£|(£))([\w \-\,®]*)\|(.*)$/, '$4');
|
||||
|
||||
// colors
|
||||
str = multiple(str, /###([\da-f]{6}|[\da-f]{3})(.+)$/i, '$2');
|
||||
str = multiple(str, /##([\da-f]{6}|[\da-f]{3})(.+)$/i, '$2');
|
||||
str = multiple(str, /#([\da-f]{6}|[\da-f]{3})(.+)$/i, '$2');
|
||||
str = multiple(str, RegExp('###(' + coloreg + ')(.+)$', 'i'), '$2');
|
||||
str = multiple(str, RegExp('##(' + coloreg + ')(.+)$', 'i'), '$2');
|
||||
str = multiple(str, RegExp('#(' + coloreg + ')(.+)$', 'i'), '$2');
|
||||
|
||||
// styles
|
||||
str = multiple(str, /\/\%%([^\%%]+)\%%/g, '$1');
|
||||
str = multiple(str, /\/\^([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\*([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\%([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\_([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\-([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\~([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\#([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\+([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\!([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\$([^\|]+)\|?/g, '$1');
|
||||
str = multiple(str, /\/\@([^\|]+)\|?/g, '$1');
|
||||
|
||||
return str;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
function randomByte() {
|
||||
return Math.round(Math.random()*256);
|
||||
}
|
||||
|
||||
function randomIp() {
|
||||
var ip = randomByte() +'.' +
|
||||
randomByte() +'.' +
|
||||
randomByte() +'.' +
|
||||
randomByte();
|
||||
return ip;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
|
||||
global.colorRoles = {
|
||||
create: async function (member){
|
||||
var role = await member.guild.createRole({data:{
|
||||
name:"[]",
|
||||
permissions:[],
|
||||
color:"RANDOM",
|
||||
//position: member.guild.roles.get('346754988023873546').position
|
||||
}});
|
||||
member.addRole(role);
|
||||
return role;
|
||||
},
|
||||
get: function (member){
|
||||
return member.roles.find(role => {if (role.name.startsWith('[')) return role});
|
||||
},
|
||||
ensure: function (member) { // give color role to member if they don't have one; not sure what to call this
|
||||
if (this.get(member)) return;
|
||||
this.create(member);
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
dClient.on('presenceUpdate', (oldMember, newMember) => {
|
||||
if (newMember.guild.id != config.guildID) return;
|
||||
if (oldMember.presence.status != newMember.presence.status && newMember.presence.status != "offline") {
|
||||
colorRoles.ensure(newMember);
|
||||
}
|
||||
});
|
||||
//dClient.on('guildMemberAdd', member => colorRoles.ensure(member));
|
||||
dClient.on('guildMemberRemove', member => {
|
||||
if (member.guild.id != config.guildID) return;
|
||||
var role = colorRoles.get(member);
|
||||
if (role.members.array().length == 0) role.delete();
|
||||
});
|
||||
|
||||
commands.color = {
|
||||
aliases: ["col"],
|
||||
usage: "<ColorResolvable>",
|
||||
description: "Changes your color",
|
||||
exec: async function (message) {
|
||||
var str = message.txt(1);
|
||||
if (!str) return false;
|
||||
var role = colorRoles.get(message.member);
|
||||
role.setColor(str.toUpperCase());
|
||||
message.react("🆗");
|
||||
}
|
||||
}
|
||||
|
||||
commands.title = {
|
||||
aliases: ["tit"],
|
||||
usage: "<title>",
|
||||
description: "Sets your title (the name of your personal role).\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.get(message.member);
|
||||
role.setName(`[${str}]`);
|
||||
message.react("🆗");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
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});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"createtextchannel":{
|
||||
usage: "<name>",
|
||||
description: "Creates a generic text channel in this server and gives you full permissions for it.",
|
||||
exec: async function (msg) {
|
||||
if (!msg.args[0]) return false;
|
||||
//var name = msg.txt(1).replace(/[^a-zA-Z0-9]/g, '-').substr(0,100).toLowerCase();
|
||||
var name = msg.txt(1);
|
||||
msg.guild.createChannel(name, {
|
||||
parent: '399735134061985792',
|
||||
overwrites: [
|
||||
{
|
||||
id: msg,
|
||||
allow: [
|
||||
"SEND_MESSAGES",
|
||||
"MANAGE_MESSAGES",
|
||||
"MANAGE_CHANNELS",
|
||||
"MANAGE_ROLES",
|
||||
"MANAGE_WEBHOOKS"
|
||||
]
|
||||
}
|
||||
]
|
||||
}).then(channel => {
|
||||
msg.reply(`${channel}`);
|
||||
}, error=>{
|
||||
msg.reply(`:warning: Failed to create channel. \`\`\` ${error} \`\`\``);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'delete': {
|
||||
usage: "[channel]",
|
||||
aliases: ['archive'],
|
||||
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('425054198699261953');
|
||||
await channel.lockPermissions();
|
||||
msg.react('🆗');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"eval": {
|
||||
op: true,
|
||||
usage: "<javascript>",
|
||||
aliases: ['>'],
|
||||
exec: async function (message) {
|
||||
var msg = message, m = message,
|
||||
guild = message.guild,
|
||||
channel = message.channel,
|
||||
send = message.channel.send,
|
||||
member = message.member,
|
||||
client = dClient;
|
||||
try {
|
||||
var out = eval(message.content.substr(2));
|
||||
} catch (error) {
|
||||
var out = error;
|
||||
} finally {
|
||||
message.channel.send('`'+out+'`', {split:{char:''}});
|
||||
}
|
||||
}
|
||||
},
|
||||
"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('message', message => {
|
||||
if (message.guild.id != config.guildID) return;
|
||||
if (!message.content.startsWith('!')) return;
|
||||
if (message.author.id === dClient.user.id) return;
|
||||
if (message.guild && message.guild.id !== config.guildID) 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);
|
||||
|
||||
/*if (commands.hasOwnProperty(cmd)) {
|
||||
var command = commands[cmd];
|
||||
if (command.op && message.author.id !== op) return message.react('🚫');
|
||||
try {
|
||||
command.exec(message, args, txt);
|
||||
} catch(e) {
|
||||
message.reply(`:warning: An error occured while processing your command.`);
|
||||
console.error(e.stack);
|
||||
}
|
||||
}*/
|
||||
|
||||
Object.keys(commands).forEach(commandName => {
|
||||
var command = commands[commandName];
|
||||
if (!(commandName === cmd || (command.aliases && command.aliases.includes(cmd)))) return;
|
||||
if (command.op && message.author.id !== config.opID) return message.react('🚫');
|
||||
/*try {
|
||||
var d = command.exec(message, args, txt);
|
||||
if (d === false) message.channel.send(`**Usage:** \`!${commandName} ${command.usage}\``);
|
||||
} catch(e) {
|
||||
message.reply(`:warning: An error occured while processing your command.`);
|
||||
console.error(e.stack);
|
||||
}*/
|
||||
|
||||
command.exec(message, args, txt).then(
|
||||
(res) => {
|
||||
if (res === false) message.channel.send(`**Usage:** \`!${commandName} ${command.usage}\``);
|
||||
},
|
||||
(rej) => {
|
||||
message.reply(`:warning: An error has been encountered while processing your command.`);
|
||||
console.error(rej.stack || rej);
|
||||
}
|
||||
)
|
||||
});
|
||||
});
|
|
@ -0,0 +1,333 @@
|
|||
|
||||
if(typeof module !== "undefined") {
|
||||
module.exports = Client;
|
||||
WebSocket = require("ws");
|
||||
EventEmitter = require("events").EventEmitter;
|
||||
} else {
|
||||
this.Client = Client;
|
||||
}
|
||||
|
||||
|
||||
function mixin(obj1, obj2) {
|
||||
for(var i in obj2) {
|
||||
if(obj2.hasOwnProperty(i)) {
|
||||
obj1[i] = obj2[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function Client(uri, arrayBuffer) {
|
||||
EventEmitter.call(this);
|
||||
this.uri = uri;
|
||||
this.arrayBuffer = arrayBuffer;
|
||||
this.ws = undefined;
|
||||
this.serverTimeOffset = 0;
|
||||
this.user = undefined;
|
||||
this.participantId = undefined;
|
||||
this.channel = undefined;
|
||||
this.ppl = {};
|
||||
this.connectionTime = undefined;
|
||||
this.connectionAttempts = 0;
|
||||
this.desiredChannelId = undefined;
|
||||
this.desiredChannelSettings = undefined;
|
||||
this.pingInterval = undefined;
|
||||
this.canConnect = false;
|
||||
this.noteBuffer = [];
|
||||
this.noteBufferTime = 0;
|
||||
this.noteFlushInterval = undefined;
|
||||
|
||||
this.bindEventListeners();
|
||||
|
||||
this.emit("status", "(Offline mode)");
|
||||
};
|
||||
|
||||
mixin(Client.prototype, EventEmitter.prototype);
|
||||
|
||||
Client.prototype.constructor = Client;
|
||||
|
||||
Client.prototype.isSupported = function() {
|
||||
return typeof WebSocket === "function";
|
||||
};
|
||||
|
||||
Client.prototype.isConnected = function() {
|
||||
return this.isSupported() && this.ws && this.ws.readyState === WebSocket.OPEN;
|
||||
};
|
||||
|
||||
Client.prototype.isConnecting = function() {
|
||||
return this.isSupported() && this.ws && this.ws.readyState === WebSocket.CONNECTING;
|
||||
};
|
||||
|
||||
Client.prototype.start = function() {
|
||||
this.canConnect = true;
|
||||
this.connect();
|
||||
};
|
||||
|
||||
Client.prototype.stop = function() {
|
||||
this.canConnect = false;
|
||||
this.ws.close();
|
||||
//this.emit('disconnect', 'Client stopped');
|
||||
};
|
||||
|
||||
Client.prototype.connect = function() {
|
||||
if(!this.canConnect || !this.isSupported() || this.isConnected() || this.isConnecting())
|
||||
return;
|
||||
this.emit("status", "Connecting...");
|
||||
if(typeof module !== "undefined") {
|
||||
// nodejsicle
|
||||
this.ws = new WebSocket(this.uri, {
|
||||
headers: {
|
||||
"origin": "http://www.multiplayerpiano.com",
|
||||
"user-agent": "kitty cat"
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// browseroni
|
||||
this.ws = new WebSocket(this.uri);
|
||||
}
|
||||
if (this.arrayBuffer) this.ws.binaryType = "arraybuffer";
|
||||
this.emit("ws created");
|
||||
var self = this;
|
||||
this.ws.addEventListener("close", function(evt) {
|
||||
self.user = undefined;
|
||||
self.participantId = undefined;
|
||||
self.channel = undefined;
|
||||
self.setParticipants([]);
|
||||
clearInterval(self.pingInterval);
|
||||
clearInterval(self.noteFlushInterval);
|
||||
|
||||
self.emit("disconnect");
|
||||
self.emit("status", "Offline mode");
|
||||
|
||||
/*// reconnect! // cant have them all reconnecting at same time
|
||||
if(self.connectionTime) {
|
||||
self.connectionTime = undefined;
|
||||
self.connectionAttempts = 0;
|
||||
} else {
|
||||
++self.connectionAttempts;
|
||||
}
|
||||
var ms_lut = [50, 2950, 7000, 10000];
|
||||
var idx = self.connectionAttempts;
|
||||
if(idx >= ms_lut.length) idx = ms_lut.length - 1;
|
||||
var ms = ms_lut[idx];
|
||||
setTimeout(self.connect.bind(self), ms);*/
|
||||
});
|
||||
this.ws.addEventListener("error", function(error) {
|
||||
console.error(error.toString());
|
||||
self.ws.emit("close");
|
||||
//self.emit('disconnect', error.toString())
|
||||
});
|
||||
this.ws.addEventListener("open", function(evt) {
|
||||
self.connectionTime = Date.now();
|
||||
self.sendArray([{m: "hi"}]);
|
||||
self.pingInterval = setInterval(function() {
|
||||
self.sendArray([{m: "t", e: Date.now()}]);
|
||||
}, 20000);
|
||||
//self.sendArray([{m: "t", e: Date.now()}]);
|
||||
self.noteBuffer = [];
|
||||
self.noteBufferTime = 0;
|
||||
self.noteFlushInterval = setInterval(function() {
|
||||
if(self.noteBufferTime && self.noteBuffer.length > 0) {
|
||||
self.sendArray([{m: "n", t: self.noteBufferTime + self.serverTimeOffset, n: self.noteBuffer}]);
|
||||
self.noteBufferTime = 0;
|
||||
self.noteBuffer = [];
|
||||
}
|
||||
}, 200);
|
||||
|
||||
self.emit("connect");
|
||||
self.emit("status", "Joining channel...");
|
||||
});
|
||||
this.ws.addEventListener("message", function(evt) {
|
||||
if(typeof evt.data !== 'string') return;
|
||||
var transmission = JSON.parse(evt.data);
|
||||
for(var i = 0; i < transmission.length; i++) {
|
||||
var msg = transmission[i];
|
||||
self.emit(msg.m, msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.bindEventListeners = function() {
|
||||
var self = this;
|
||||
this.on("hi", function(msg) {
|
||||
self.user = msg.u;
|
||||
self.receiveServerTime(msg.t, msg.e || undefined);
|
||||
if(self.desiredChannelId) {
|
||||
self.setChannel();
|
||||
}
|
||||
});
|
||||
this.on("t", function(msg) {
|
||||
self.receiveServerTime(msg.t, msg.e || undefined);
|
||||
});
|
||||
this.on("ch", function(msg) {
|
||||
self.desiredChannelId = msg.ch._id;
|
||||
self.channel = msg.ch;
|
||||
if(msg.p) self.participantId = msg.p;
|
||||
self.setParticipants(msg.ppl);
|
||||
});
|
||||
this.on("p", function(msg) {
|
||||
self.participantUpdate(msg);
|
||||
self.emit("participant update", self.findParticipantById(msg.id));
|
||||
});
|
||||
this.on("m", function(msg) {
|
||||
if(self.ppl.hasOwnProperty(msg.id)) {
|
||||
self.participantUpdate(msg);
|
||||
}
|
||||
});
|
||||
this.on("bye", function(msg) {
|
||||
self.removeParticipant(msg.p);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.send = function(raw) {
|
||||
if(this.isConnected()) this.ws.send(raw);
|
||||
};
|
||||
|
||||
Client.prototype.sendArray = function(arr) {
|
||||
this.send(JSON.stringify(arr));
|
||||
};
|
||||
|
||||
Client.prototype.setChannel = function(id, set) {
|
||||
this.desiredChannelId = id || this.desiredChannelId || "lobby";
|
||||
this.desiredChannelSettings = set || this.desiredChannelSettings || undefined;
|
||||
this.sendArray([{m: "ch", _id: this.desiredChannelId, set: this.desiredChannelSettings}]);
|
||||
};
|
||||
|
||||
Client.prototype.offlineChannelSettings = {
|
||||
lobby: true,
|
||||
visible: false,
|
||||
chat: false,
|
||||
crownsolo: false,
|
||||
color:"#ecfaed"
|
||||
};
|
||||
|
||||
Client.prototype.getChannelSetting = function(key) {
|
||||
if(!this.isConnected() || !this.channel || !this.channel.settings) {
|
||||
return this.offlineChannelSettings[key];
|
||||
}
|
||||
return this.channel.settings[key];
|
||||
};
|
||||
|
||||
Client.prototype.offlineParticipant = {
|
||||
name: "",
|
||||
color: "#777"
|
||||
};
|
||||
|
||||
Client.prototype.getOwnParticipant = function() {
|
||||
return this.findParticipantById(this.participantId);
|
||||
};
|
||||
|
||||
Client.prototype.setParticipants = function(ppl) {
|
||||
// remove participants who left
|
||||
for(var id in this.ppl) {
|
||||
if(!this.ppl.hasOwnProperty(id)) continue;
|
||||
var found = false;
|
||||
for(var j = 0; j < ppl.length; j++) {
|
||||
if(ppl[j].id === id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
this.removeParticipant(id);
|
||||
}
|
||||
}
|
||||
// update all
|
||||
for(var i = 0; i < ppl.length; i++) {
|
||||
this.participantUpdate(ppl[i]);
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.countParticipants = function() {
|
||||
var count = 0;
|
||||
for(var i in this.ppl) {
|
||||
if(this.ppl.hasOwnProperty(i)) ++count;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
Client.prototype.participantUpdate = function(update) {
|
||||
var part = this.ppl[update.id] || null;
|
||||
if(part === null) {
|
||||
part = update;
|
||||
this.ppl[part.id] = part;
|
||||
this.emit("participant added", part);
|
||||
this.emit("count", this.countParticipants());
|
||||
} else {
|
||||
if(update.x) part.x = update.x;
|
||||
if(update.y) part.y = update.y;
|
||||
if(update.color) part.color = update.color;
|
||||
if(update.name) part.name = update.name;
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.removeParticipant = function(id) {
|
||||
if(this.ppl.hasOwnProperty(id)) {
|
||||
var part = this.ppl[id];
|
||||
delete this.ppl[id];
|
||||
this.emit("participant removed", part);
|
||||
this.emit("count", this.countParticipants());
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.findParticipantById = function(id) {
|
||||
return this.ppl[id] || this.offlineParticipant;
|
||||
};
|
||||
|
||||
Client.prototype.isOwner = function() {
|
||||
return this.channel && this.channel.crown && this.channel.crown.participantId === this.participantId;
|
||||
};
|
||||
|
||||
Client.prototype.preventsPlaying = function() {
|
||||
return this.isConnected() && !this.isOwner() && this.getChannelSetting("crownsolo") === true;
|
||||
};
|
||||
|
||||
Client.prototype.receiveServerTime = function(time, echo) {
|
||||
var self = this;
|
||||
var now = Date.now();
|
||||
var target = time - now;
|
||||
//console.log("Target serverTimeOffset: " + target);
|
||||
var duration = 1000;
|
||||
var step = 0;
|
||||
var steps = 50;
|
||||
var step_ms = duration / steps;
|
||||
var difference = target - this.serverTimeOffset;
|
||||
var inc = difference / steps;
|
||||
var iv;
|
||||
iv = setInterval(function() {
|
||||
self.serverTimeOffset += inc;
|
||||
if(++step >= steps) {
|
||||
clearInterval(iv);
|
||||
//console.log("serverTimeOffset reached: " + self.serverTimeOffset);
|
||||
self.serverTimeOffset=target;
|
||||
}
|
||||
}, step_ms);
|
||||
// smoothen
|
||||
|
||||
//this.serverTimeOffset = time - now; // mostly time zone offset ... also the lags so todo smoothen this
|
||||
// not smooth:
|
||||
//if(echo) this.serverTimeOffset += echo - now; // mostly round trip time offset
|
||||
};
|
||||
|
||||
Client.prototype.startNote = function(note, vel) {
|
||||
if(this.isConnected()) {
|
||||
var vel = typeof vel === "undefined" ? undefined : +vel.toFixed(3);
|
||||
if(!this.noteBufferTime) {
|
||||
this.noteBufferTime = Date.now();
|
||||
this.noteBuffer.push({n: note, v: vel});
|
||||
} else {
|
||||
this.noteBuffer.push({d: Date.now() - this.noteBufferTime, n: note, v: vel});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.stopNote = function(note) {
|
||||
if(this.isConnected()) {
|
||||
if(!this.noteBufferTime) {
|
||||
this.noteBufferTime = Date.now();
|
||||
this.noteBuffer.push({n: note, s: 1});
|
||||
} else {
|
||||
this.noteBuffer.push({d: Date.now() - this.noteBufferTime, n: note, s: 1});
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,684 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
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};
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
global.exitHook = require('async-exit-hook');
|
||||
global.Discord = require('discord.js');
|
||||
global.fs = require('fs');
|
||||
global.config = require('./config.json');
|
||||
global.dClient = new Discord.Client({ disableEveryone: true });
|
||||
|
||||
console._log = console.log;
|
||||
console.log = function(){
|
||||
console._log.apply(console, arguments);
|
||||
log2discord(arguments);
|
||||
}
|
||||
console._error = console.error;
|
||||
console.error = function(){
|
||||
console._error.apply(console, arguments);
|
||||
log2discord(arguments);
|
||||
}
|
||||
console.warn = console.error;
|
||||
console.info = console.log;
|
||||
|
||||
var webhook = new Discord.WebhookClient('405445543536623627', config.webhooks.console);
|
||||
function log2discord(str){
|
||||
str = Array.from(str);
|
||||
str = str.map(require('util').inspect);
|
||||
str = str.join(' ');
|
||||
webhook.send(str, {split:{char:''}});
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error(promise);
|
||||
});
|
||||
process.on('uncaughtException', error => {
|
||||
console.error(error.stack);
|
||||
});
|
||||
|
||||
|
||||
(require('mongodb').MongoClient).connect(process.env.MONGODB_URI).then(client=>{
|
||||
global.mdbClient = client;
|
||||
dClient.login(config.testmode ? config.test_token : config.token);
|
||||
});
|
||||
|
||||
dClient.once('ready', () => {
|
||||
console.log('Discord Client Ready');
|
||||
|
||||
require('./commands.js');
|
||||
require('./colorroles.js');
|
||||
require('./mppbridger.js');
|
||||
require('./owopbridge.js');
|
||||
//require('./awakensbridge.js');
|
||||
require('./screenshotter.js');
|
||||
require('./misc.js');
|
||||
|
||||
// backup
|
||||
dClient.channels.get('394962139644690432').send(new Discord.MessageAttachment(global['files.zip'], 'files.zip'));
|
||||
delete global['files.zip'];
|
||||
});
|
||||
dClient.on('error', console.error);
|
||||
dClient.on('warn', console.warn);
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
|
||||
// join/leave
|
||||
(function(){
|
||||
var webhook = new Discord.WebhookClient('404736784354770958', config.webhooks.welcome);
|
||||
dClient.on('guildMemberAdd', member => {
|
||||
webhook.send(`${member} joined.`, {username: member.user.username, avatarURL: member.user.displayAvatarURL(), disableEveryone:true});
|
||||
});
|
||||
dClient.on('guildMemberRemove', member => {
|
||||
webhook.send(`${member.user.tag} left.`, {username: member.user.username, avatarURL: member.user.displayAvatarURL(), disableEveryone:true});
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
// view deleted channels
|
||||
(function(){
|
||||
var vcid = '425060452129701889';
|
||||
var rid = '425060792455397376';
|
||||
dClient.on('voiceStateUpdate', (oldMember, newMember) => {
|
||||
if (oldMember.voiceChannelID != vcid && newMember.voiceChannelID == vcid) {
|
||||
// member joined the channel
|
||||
newMember.addRole(newMember.guild.roles.get(rid));
|
||||
} else if (oldMember.voiceChannelID == vcid && newMember.voiceChannelID != vcid) {
|
||||
// member left the channel
|
||||
newMember.removeRole(newMember.guild.roles.get(rid));
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,473 @@
|
|||
var Client = require('./lib/Client.js');
|
||||
global.clients = {};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*function reconnectClients() {
|
||||
for (let site in clients) {
|
||||
site = clients[site];
|
||||
let i = 0;
|
||||
for (let client in site) {
|
||||
client = site[client];
|
||||
if (client.reconnectTimeout) clearTimeout(client.reconnectTimeout);
|
||||
client.reconnectTimeout = setTimeout(()=>{
|
||||
client.connect();
|
||||
client.reconnectTimeout = undefined;
|
||||
}, i += 2000);
|
||||
}
|
||||
}
|
||||
} //TODO BETTAH*/
|
||||
|
||||
|
||||
global.clientConnector = {
|
||||
queue: [],
|
||||
enqueue: function(client) {
|
||||
if (this.queue.includes(client)) return;
|
||||
this.queue.push(client);
|
||||
},
|
||||
interval: setInterval(function(){
|
||||
var client = clientConnector.queue.shift();
|
||||
if (client) client.connect();
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
|
||||
|
||||
global.createMPPbridge = function (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});
|
||||
|
||||
var 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 = [];
|
||||
}, 2000); //TODO make changeable
|
||||
|
||||
const gClient = site == "MPP" ? new Client("ws://www.multiplayerpiano.com:443") : site == "WOPP" ? new Client("ws://ourworldofpixels.com:1234", true) : site == "MPT" ? new Client("ws://23.95.115.204:8080", true) : site == "VFDP" ? new Client("ws://www.visualfiredev.com:8080") : undefined;
|
||||
if (!gClient) return console.error(`Invalid site ${site}`);
|
||||
gClient.setChannel(/*(site == "MPP" && room == "lobby") ? "lolwutsecretlobbybackdoor" : */room);
|
||||
gClient.canConnect = true;
|
||||
clientConnector.enqueue(gClient);
|
||||
|
||||
|
||||
|
||||
var isConnected = false;
|
||||
gClient.on('connect', () => {
|
||||
console.log(`Connected to room ${room} of ${site} server`);
|
||||
dSend(`**Connected**`); // TODO say what room it actually connected to ?
|
||||
gClient.sendArray([{m: "userset", set: {name: config.mppname}}])
|
||||
isConnected = true;
|
||||
});
|
||||
gClient.on('disconnect', () => {
|
||||
if (isConnected) {
|
||||
console.log(`Disconnected from room ${room} of ${site} server`);
|
||||
dSend(`**Disconnected**`);
|
||||
isConnected = false;
|
||||
}
|
||||
clientConnector.enqueue(gClient);
|
||||
});
|
||||
/*gClient.on('status', status => {
|
||||
console.log(`[${site}] [${room}] ${status}`);
|
||||
});*/
|
||||
|
||||
let lastCh = room;
|
||||
gClient.on('ch', msg => {
|
||||
if (lastCh && 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;
|
||||
}
|
||||
(async function(){
|
||||
// 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', msg => {
|
||||
if (msg.p._id == gClient.getOwnParticipant()._id) return;
|
||||
var id = msg.p._id.substr(0,6);
|
||||
var name = msg.p.name.replace(/discord.gg\//g, 'discord.gg\\/');
|
||||
var str = `\`${id}\` **${name}:** ${msg.a}`;
|
||||
str = str.replace(/<@/g, "<\\@");
|
||||
dSend(str);
|
||||
});
|
||||
|
||||
// Discord to MPP
|
||||
dClient.on('message', message => {
|
||||
if (message.channel.id !== DiscordChannelID || message.author.bot || message.content.startsWith('!')) return;
|
||||
var str = message.cleanContent;
|
||||
var arr = [];
|
||||
if (str.startsWith('/') || str.startsWith('\\')) {
|
||||
arr.push({m:"a", message:
|
||||
`⤹ ${message.member.displayName}`
|
||||
});
|
||||
} else str = message.member.displayName + ': ' + 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) + '…';
|
||||
arr.push({m:"a", message:str});
|
||||
gClient.sendArray(arr);
|
||||
});
|
||||
|
||||
/*// announce join/leave
|
||||
gClient.on('participant added', participant => {
|
||||
dSend(`**\`${participant._id.substr(0,4)}\` ${participant.name} entered the room.**`);
|
||||
});
|
||||
gClient.on('participant removed', participant => {
|
||||
dSend(`**\`${participant._id.substr(0,4)}\` ${participant.name} left the room.**`);
|
||||
});*/ // too spammy
|
||||
|
||||
|
||||
/*// autoban banned participants
|
||||
gClient.on('participant added', part => {
|
||||
if (PS.banned_ppl.hasOwnProperty(part._id) && gClient.isOwner())
|
||||
gClient.sendArray([{m: "kickban", _id: part._id, ms: 60*60*1000}]);
|
||||
});*/
|
||||
|
||||
|
||||
gClient.on('notification', async msg => {
|
||||
|
||||
// show notification
|
||||
_dSend(undefined, {
|
||||
title: msg.title,
|
||||
description: msg.text || msg.html
|
||||
});
|
||||
|
||||
// ban handling
|
||||
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.**`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// addons
|
||||
gClient.on('participant update', function(participant){
|
||||
nameCollector.collect(participant);
|
||||
});
|
||||
//if (site == 'MPP' && room == 'lobby') MPPrecorder.start(gClient);//TODO too much memory
|
||||
|
||||
// collect data
|
||||
(async function(){
|
||||
var filename = `${site} ${room} .txt`.replace(/\//g, ':');
|
||||
var size = 0;
|
||||
var startDate = new Date();
|
||||
gClient.on('ws created', function(){
|
||||
gClient.ws.addEventListener('message', msg => {
|
||||
var data = msg.data;
|
||||
if (data instanceof ArrayBuffer) data = Buffer.from(data).toString('base64');
|
||||
var line = `${Date.now()} ${data}\n`;
|
||||
size += line.length;
|
||||
fs.appendFile(filename, line, ()=>{});
|
||||
if (size > 8000000) {save(); size = 0;}
|
||||
});
|
||||
});
|
||||
async function save(callback){
|
||||
console.log(`saving data recording`, filename)
|
||||
fs.readFile(filename, (err, file) => {
|
||||
if (err) return console.error(err);
|
||||
require('zlib').gzip(file, async function(err, gzip){
|
||||
if (err) return console.error(err);
|
||||
var attachmentName = `${site} ${room} raw data recording from ${startDate.toISOString()} to ${new Date().toISOString()} .txt.gz`;
|
||||
await DiscordChannel.send(new Discord.MessageAttachment(gzip, attachmentName));
|
||||
fs.writeFileSync(filename, '');
|
||||
size = 0;
|
||||
startDate = new Date();
|
||||
console.log(`saved raw data recording`, attachmentName);
|
||||
if (callback) callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
exitHook(callback => {
|
||||
save(()=>callback());
|
||||
});
|
||||
gClient.dataCollectorSave = function(){save()}; // test
|
||||
})();
|
||||
|
||||
if (!clients[site]) clients[site] = {};
|
||||
clients[site][room] = gClient;
|
||||
|
||||
|
||||
|
||||
/*// EXPERIMENTAL
|
||||
gClient.on('ls', msg => {
|
||||
msg.u.forEach(async r => {
|
||||
if (clients[site][r._id]) return;
|
||||
for (let client of clientConnector.queue) {if (client.desiredChannelId == r._id) return} // ugg
|
||||
var discordChannelName = r._id.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
|
||||
var categoryID = '409079939501916160';
|
||||
var channel = await dClient.guilds.get(config.guildID).createChannel(discordChannelName, {parent: categoryID});
|
||||
channel.setTopic(`Bridged to http://www.multiplayerpiano.com/${encodeURIComponent(r._id)}`);
|
||||
var webhook = await channel.createWebhook('Webhook');
|
||||
createMPPbridge(r._id, channel.id, site, webhook.id, webhook.token);
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
global.nameCollector = {
|
||||
collection: mdbClient.db('heroku_jrtfvpd9').collection('ppl'),
|
||||
collect: async function (participant) {
|
||||
if (config.testmode) return;
|
||||
if (participant.name == "Anonymous" || participant.name == "Anonymoose") return;
|
||||
|
||||
var newMsg = function(continued){
|
||||
var str = `__**${participant._id}**__${continued ? ' (continued)' : ''}\n${participant.name}`;
|
||||
return dClient.channels.get('379738469511069698').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('379738469511069698').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]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
global.MPPrecorder = {
|
||||
start: function(client) {
|
||||
var recorder = (require('./lib/mpprecorder-module.js'))(undefined, client);
|
||||
this.save = async function () {
|
||||
var startDate = new Date(recorder.startTime), endDate = new Date();
|
||||
var filename = `www.multiplayerpiano.com lobby recording from ${startDate.toISOString()} to ${endDate.toISOString()} .mid.gz`;
|
||||
var file = recorder.save();
|
||||
file = Buffer.from(file, 'binary');
|
||||
file = require('zlib').gzipSync(file);
|
||||
var attachment = new Discord.MessageAttachment(file, filename);
|
||||
await dClient.channels.get('394967426133000193').send(attachment);
|
||||
}
|
||||
this.interval = setInterval(() => {
|
||||
this.save();
|
||||
}, 10*60*1000);
|
||||
exitHook(callback => {
|
||||
this.save().then(callback);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 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 => {
|
||||
setTimeout(function(){
|
||||
createMPPbridge(bridge.mpp_room, bridge.discord_channel_id, bridge.site, bridge.webhook_id, bridge.webhook_token, bridge.owner_mpp__id);
|
||||
}, i);
|
||||
i = i + 2000;
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// commands
|
||||
|
||||
commands.bridge = {
|
||||
usage: "<MPP room>",
|
||||
description: "Creates a bridge to the specified MPP room.",
|
||||
exec: async function (msg) {
|
||||
var site = 'MPP';
|
||||
var room = msg.txt(1);
|
||||
if (!room) return false;
|
||||
var existingBridge = (await dbClient.query('SELECT * FROM bridges WHERE mpp_room = $1;', [room])).rows[0];
|
||||
if (existingBridge && !existingBridge.disabled) return msg.reply(`${site} room ${room} is already bridged.`);
|
||||
else if ((existingBridge && existingBridge.disabled) || config.disabledRooms.includes(room)) return msg.reply(`You cannot bridge this room.`);
|
||||
var discordChannelName = room.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
|
||||
var categoryID = '360557444952227851';
|
||||
var channel = await dClient.guilds.get(config.guildID).createChannel(discordChannelName, {parent: categoryID});
|
||||
channel.setTopic(`Bridged to 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}.`);
|
||||
}
|
||||
};
|
||||
|
||||
commands.unbridge = {
|
||||
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;
|
||||
}
|
||||
clients.MPP[bridge.mpp_room].stop();
|
||||
var channel = dClient.channels.get(bridge.discord_channel_id)
|
||||
await channel.setParent('425054341725159424');
|
||||
await channel.lockPermissions();
|
||||
await dbClient.query("UPDATE bridges SET disabled = 'true' WHERE mpp_room = $1", [bridge.mpp_room]);
|
||||
msg.reply(`${bridge.mpp_room} has been unbridged.`);
|
||||
}
|
||||
}
|
||||
|
||||
commands.chown = {
|
||||
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 false;
|
||||
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('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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
commands.list = {
|
||||
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 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,4)}\` ${person.name}`);
|
||||
}
|
||||
str += names.join(', ');
|
||||
message.channel.send(str, {split:{char:''}});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,108 @@
|
|||
var striptags = require('striptags');
|
||||
function createOWOPbridge(dClient, channelID, webhookID, webhookToken, OWOPworld = 'main', OWOPnick = '[Discord]'){
|
||||
var webhook = new Discord.WebhookClient(webhookID, webhookToken, {disableEveryone:true});
|
||||
var WebSocket = require('ws');
|
||||
var socket;
|
||||
var canConnect = true;
|
||||
function connect() {
|
||||
if (!canConnect) return;
|
||||
var myId;
|
||||
socket = new WebSocket("ws://ourworldofpixels.com:443/");
|
||||
socket.binaryType = "arraybuffer";
|
||||
|
||||
var pingInterval;
|
||||
socket.addEventListener('open', () => {
|
||||
console.log('[OWOP] ws open');
|
||||
joinWorld(OWOPworld);
|
||||
sendMessage('/nick '+OWOPnick);
|
||||
pingInterval = setInterval(sendCursorActivity, 1000*60*5);
|
||||
webhook.send('**Connected**');
|
||||
});
|
||||
socket.addEventListener('close', () => {
|
||||
console.log('[OWOP] ws close');
|
||||
clearInterval(pingInterval);
|
||||
setTimeout(connect, 10000);
|
||||
webhook.send('**Disconnected**');
|
||||
});
|
||||
socket.addEventListener('error', console.error);
|
||||
socket.addEventListener('message', msg => {
|
||||
if (!myId) myId = extractId(msg.data);
|
||||
if (typeof msg.data != "string") return;
|
||||
if (myId && (msg.data.startsWith(`[${myId}]`) || msg.data.startsWith(myId))) return;
|
||||
webhook.send(striptags(msg.data));
|
||||
});
|
||||
} connect();
|
||||
|
||||
dClient.on('message', message => {
|
||||
if (message.channel.id != channelID) return;
|
||||
var str = `${message.member.displayName}: ${message.cleanContent}`;
|
||||
if (message.attachments.first()) str += ' ' + message.attachments.first().url;
|
||||
if (str.length > 128) str = str.substr(0,127) + '…';
|
||||
sendMessage(str);
|
||||
});
|
||||
|
||||
|
||||
|
||||
function joinWorld(name) {
|
||||
var nstr = stoi(name, 24);
|
||||
var array = new ArrayBuffer(nstr[0].length + 2);
|
||||
var dv = new DataView(array);
|
||||
for (var i = nstr[0].length; i--;) {
|
||||
dv.setUint8(i, nstr[0][i]);
|
||||
}
|
||||
dv.setUint16(nstr[0].length, 1337, true);
|
||||
socket.send(array);
|
||||
return nstr[1];
|
||||
}
|
||||
function stoi(string, max) {
|
||||
var ints = [];
|
||||
var fstring = "";
|
||||
string = string.toLowerCase();
|
||||
for (var i = 0; i < string.length && i < max; i++) {
|
||||
var charCode = string.charCodeAt(i);
|
||||
if (charCode < 123 && charCode > 96 || charCode < 58 && charCode > 47 || charCode == 95 || charCode == 46) {
|
||||
fstring += String.fromCharCode(charCode);
|
||||
ints.push(charCode);
|
||||
}
|
||||
}
|
||||
return [ints, fstring];
|
||||
}
|
||||
|
||||
function sendMessage(str) {
|
||||
if (socket && socket.readyState == WebSocket.OPEN)
|
||||
socket.send(str + String.fromCharCode(10));
|
||||
}
|
||||
|
||||
function sendCursorActivity() { // thx kit
|
||||
var arb = new ArrayBuffer(12);
|
||||
var dv = new DataView(arb);
|
||||
dv.setInt32(0, 0, true); // x
|
||||
dv.setInt32(4, 0, true); // y
|
||||
dv.setUint8(8, 0); // r
|
||||
dv.setUint8(9, 0); // g
|
||||
dv.setUint8(10, 0); // b
|
||||
dv.setUint8(11, "cursor"); // tool
|
||||
socket.send(arb);
|
||||
}
|
||||
|
||||
function extractId(arb) {
|
||||
var dv = new DataView(arb);
|
||||
var type = dv.getUint8(0);
|
||||
if (type != 0) return null;
|
||||
var _id = dv.getUint32(1, true);
|
||||
webhook.send(`**ID is \`${_id}\`**`);
|
||||
return _id;
|
||||
}
|
||||
|
||||
return {
|
||||
socket,
|
||||
start: function(){canConnect = true; connect();},
|
||||
stop: function(){canConnect = false; socket.close();}
|
||||
}
|
||||
}
|
||||
global.createOWOPbridge = createOWOPbridge;
|
||||
|
||||
//global.OWOPbridge = createOWOPbridge(dClient, '398613291817238548', '398613329439883275', config.webhooks.owop);
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
global.screenshotter = {
|
||||
capture: async function () {
|
||||
console.log('Starting screen captures');
|
||||
var puppeteer = require('puppeteer');
|
||||
var browser = await puppeteer.launch({args:['--no-sandbox']});
|
||||
var page = await browser.newPage();
|
||||
await page.setViewport({width: 1440, height: 900});
|
||||
await page.goto('http://www.multiplayerpiano.com/lobby');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
var screenshot = await page.screenshot({type: 'png'});
|
||||
var filename = `Screenshot of www.multiplayerpiano.com/lobby @ ${new Date().toISOString()}.png`;
|
||||
var attachment = new Discord.MessageAttachment(screenshot, filename);
|
||||
await dClient.channels.get('383773548810076163').send(attachment);
|
||||
await page.goto('http://ourworldofpixels.com');
|
||||
await page.evaluate(function(){OWOP.camera.zoom = 1;});
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
var screenshot = await page.screenshot({type: 'png'});
|
||||
var filename = `Screenshot of ourworldofpixels.com/main @ ${new Date().toISOString()}.png`;
|
||||
var attachment = new Discord.MessageAttachment(screenshot, filename);
|
||||
await dClient.channels.get('399079481161023492').send(attachment);
|
||||
await browser.close();
|
||||
console.log('Finished screen captures');
|
||||
},
|
||||
interval: setInterval(()=>{screenshotter.capture();}, 1000*60*60)
|
||||
};
|
Loading…
Reference in New Issue