ok delete junk folder
This commit is contained in:
parent
ff135e5c5b
commit
959d8481a5
|
@ -1,203 +0,0 @@
|
|||
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;
|
||||
}
|
|
@ -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,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.resolve(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,184 +0,0 @@
|
|||
global.colorRoles = new Object();
|
||||
|
||||
colorRoles.findColorRole = function (member) { // get color role of member
|
||||
return member.roles.cache.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.resolve(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.resolve(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.resolve(config.guildID);
|
||||
for (let role of guild.roles.cache) {
|
||||
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("🆗");
|
||||
}
|
||||
}
|
|
@ -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,107 +0,0 @@
|
|||
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);
|
||||
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
(async function() {
|
||||
global.RocketChat = require("@rocket.chat/sdk");
|
||||
var driver = RocketChat.driver;
|
||||
var api = RocketChat.api;
|
||||
|
||||
driver.useLog({
|
||||
debug: ()=>{},
|
||||
info: ()=>{},
|
||||
warn: x => onError(x, 'Rocket.chat Warning'),
|
||||
error: x => onError(x, 'Rocket.chat Error')
|
||||
});
|
||||
|
||||
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.resolve(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,
|
||||
disableMentions: 'all',
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
Loading…
Reference in New Issue