Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@ const Notifier = require('./notifications/Notifier.js');
* @property {string} codeBlock - String for denoting multi-line code blocks
*/


function checkPrivateRooms(self, shardId) {
self.logger.debug(`Checking private rooms... Shard ${shardId}`);
self.settings.getPrivateRooms()
.then((privateRooms) => {
self.logger.debug(`Private rooms... ${privateRooms.length}`);
privateRooms.forEach((room) => {
if (room && room.voiceChannel && room.textChannel) {
const now = new Date();
if (((now.getTime() + (now.getTimezoneOffset() * 60000)) - room.createdAt
> self.channelTimeout)
&& room.voiceChannel.members.size === 0) {
if (room.textChannel.deletable) {
self.logger.debug(`Deleting text channel... ${room.textChannel.id}`);
room.textChannel.delete()
.then(() => {
if (room.voiceChannel.deletable) {
self.logger.debug(`Deleting text channel... ${room.voiceChannel.id}`);
return room.voiceChannel.delete();
}
return new Promise();
})
.then(() => {
if (room.textChannel.deletable && room.voiceChannel.deletable) {
self.settings
.deletePrivateRoom(room.guild, room.textChannel, room.voiceChannel);
}
});
}
}
}
});
});
}

/**
* Class describing Genesis bot
*/
Expand Down Expand Up @@ -66,6 +101,8 @@ class Genesis {
*/
this.token = discordToken;

this.channelTimeout = 300000;

/**
* The logger object
* @type {Logger}
Expand Down Expand Up @@ -207,6 +244,9 @@ class Genesis {
this.client.user.setGame(`@${this.client.user.username} help (${this.shardId + 1}/${this.shardCount})`);
this.settings.ensureData(this.client);
this.readyToExecute = true;

const self = this;
setInterval(checkPrivateRooms, self.channelTimeout, self, self.shardId);
}

/**
Expand Down
212 changes: 212 additions & 0 deletions src/commands/Rooms/Create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
'use strict';

const Command = require('../../Command.js');

const useable = ['room', 'raid', 'team'];

/**
* Gets the list of users from the mentions in the call
* @param {Message} message Channel message
* @returns {Array.<User>} Array of users to send message
*/
function getUsersForCall(message) {
const users = [];
if (message.mentions.roles) {
message.mentions.roles.forEach(role =>
role.members.forEach(member =>
users.push(member.user)));
}
if (message.mentions.users) {
message.mentions.users.forEach((user) => {
if (users.indexOf(user) === -1) {
users.push(user);
}
});
}
users.push(message.author);
return users;
}
/**
* Create temporary voice/text channels (can be expanded in the future)
*/
class Create extends Command {
/**
* Constructs a callable command
* @param {Genesis} bot The bot object
*/
constructor(bot) {
super(bot, 'rooms.create', 'create', 'Create a temporary room.');
this.regex = new RegExp(`^${this.call}\\s?(room|raid|team)?(\\w|-)?`, 'i');

this.usages = [
{ description: 'Display instructions for creating temporary rooms', parameters: [] },
{
description: 'Create temporary text and voice channels for the calling user.',
parameters: ['room | raid | team'],
},
{
description: 'Create temporary text and voice channels for the calling user and any mentioned users/roles.',
parameters: ['room | raid | team', 'users and/or role'],
},
];

this.allowDM = false;
}

/**
* Run the command
* @param {Message} message Message with a command to handle, reply to,
* or perform an action based on parameters.
*/
run(message) {
const type = message.strippedContent.match(this.regex)[1];
const optName = message.strippedContent.match(this.regex)[2];
this.bot.settings.getChannelSetting(message.channel, 'createPrivateChannel')
.then((createPrivateChannelAllowed) => {
if (createPrivateChannelAllowed && type) {
const roomType = type.trim();
if (roomType === 'room' || roomType === 'raid' || roomType === 'team') {
const users = getUsersForCall(message);
const name = optName || `${type}-${message.member.displayName}`.toLowerCase();
if (users.length < 11 && !message.guild.channels.find('name', name)) {
message.guild.createChannel(name.replace(/[^\w|-]/g, ' '), 'text')
.then(textChannel => [message.guild.createChannel(name, 'voice'), textChannel])
.then((params) => {
const textChannel = params[1];
// set up listener to delete channels if inactive for more than 5 minutes
return params[0].then((voiceChannel) => {
// set up overwrites
this.setOverwrites(textChannel, voiceChannel, users, message.guild.id);
// add channel to listenedChannels
this.bot.settings.addPrivateRoom(message.guild, textChannel, voiceChannel)
.then(() => {})
.catch(this.logger.error);
// send users invite link to new rooms
this.sendInvites(voiceChannel, users, message.author);
// set room limits
this.setLimits(voiceChannel, roomType);
this.messageManager.embed(message, {
title: 'Channels created',
fields: [{
name: '_ _',
value: `Voice Channel: ${voiceChannel.name}\n` +
`Text Channel: ${textChannel.name}`,
}],
}, false, false);
});
}).catch((error) => {
this.logger.error(error);
if (error.response) {
this.logger.debug(`${error.message}: ${error.status}.\n` +
`Stack trace: ${error.stack}\n` +
`Response: ${error.response.body}`);
}
});
} else {
let msg = '';
if (users.length > 10) {
// notify caller that there's too many users if role is more than 10 people.
msg = 'you are trying to send an invite to too many people, please keep the total number under 10';
} else {
msg = 'that room already exists.';
}
this.messageManager.reply(message, msg, true, true);
}
}
} else {
this.messageManager.reply(message, '```haskell\n' +
'Sorry, you need to specify what you want to create. Right now these are available to create:' +
`\n* ${useable.join('\n* ')}\n\`\`\``
, true, false);
}
});
}

/**
* Send channel invites to users who were tagged in message
* @param {VoiceChannel} voiceChannel Voice channel to create invites for
* @param {Array.<User>} users Array of users to send invites to
* @param {User} author Calling user who sends message
*/
sendInvites(voiceChannel, users, author) {
if (voiceChannel.permissionsFor(this.bot.client.user).has('CREATE_INSTANT_INVITE')) {
users.forEach((user) => {
voiceChannel.createInvite({ maxUses: 1 }).then((invite) => {
this.messageManager.sendDirectMessageToUser(user, `Invite for ${voiceChannel.name} from ${author}: ${invite}`, false);
});
});
}
}

/**
* Set up overwrites for the text channel and voice channel
* for the list of users as the team, barring everyone else from joining
* @param {TextChannel} textChannel Text channel to set permissions for
* @param {VoiceChannel} voiceChannel Voice channel to set permissions for
* @param {Array.<User>} users Array of users for whom to allow into channels
* @param {string} everyoneId Snowflake id for the everyone role
*/
setOverwrites(textChannel, voiceChannel, users, everyoneId) {
// create overwrites
const overwritePromises = [];
// create text channel perms
overwritePromises.push(textChannel.overwritePermissions(everyoneId, {
READ_MESSAGES: false,
}));
// create voice channel perms
overwritePromises.push(voiceChannel.overwritePermissions(everyoneId, {
CONNECT: false,
}));

// allow bot to manage channels
overwritePromises.push(textChannel.overwritePermissions(this.bot.client.user.id, {
READ_MESSAGES: true,
SEND_MESSAGES: true,
}));
overwritePromises.push(voiceChannel.overwritePermissions(this.bot.client.user.id, {
CREATE_INSTANT_INVITE: true,
CONNECT: true,
SPEAK: true,
MUTE_MEMBERS: true,
DEAFEN_MEMBERS: true,
MOVE_MEMBERS: true,
USE_VAD: true,
MANAGE_ROLES: true,
MANAGE_CHANNELS: true,
}));

// set up overwrites per-user
users.forEach((user) => {
overwritePromises.push(textChannel.overwritePermissions(user.id, {
READ_MESSAGES: true,
SEND_MESSAGES: true,
}));
overwritePromises.push(voiceChannel.overwritePermissions(user.id, {
CONNECT: true,
SPEAK: true,
USE_VAD: true,
}));
});

overwritePromises.forEach(promise => promise.catch(this.logger.error));
}

setLimits(voiceChannel, type) {
let limit = 99;
switch (type) {
case 'team':
limit = 4;
break;
case 'raid':
limit = 8;
break;
case 'room':
default:
break;
}
voiceChannel.setUserLimit(limit)
.then(vc => this.logger.debug(`User limit set to ${limit} for ${vc.name}`));
}
}

module.exports = Create;
44 changes: 44 additions & 0 deletions src/commands/Rooms/RoomCreation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const Command = require('../../Command.js');

class RespondToSettings extends Command {
constructor(bot) {
super(bot, 'settings.allowprivateroom', 'allow private room', 'Set whether or not to allow the bot to create private rooms.');
this.usages = [
{ description: 'Change if the bot to delete commands and/or responses after responding in this channel', parameters: ['deleting enabled'] },
];
this.regex = new RegExp('^allow\\s?private\\s?rooms?\\s?(.+)?$', 'i');
this.requiresAuth = true;
this.allowDM = false;
}

run(message) {
let enable = message.strippedContent.match(this.regex)[1];
if (!enable) {
const embed = {
title: 'Usage',
type: 'rich',
color: 0x0000ff,
fields: [
{
name: `${this.bot.prefix}${this.call} <yes|no>`,
value: '_ _',
},
],
};
this.messageManager.embed(message, embed, true, true);
} else {
enable = enable.trim();
let enableResponse = false;
if (enable === 'enable' || enable === 'yes' || enable === '1' || enable === 'true' || enable === 1) {
enableResponse = true;
}
this.bot.settings.setGuildSetting(message.guild, 'createPrivateChannel', enableResponse)
.then(() => this.messageManager.notifySettingsChange(message, true, true))
.catch(this.logger.error);
}
}
}

module.exports = RespondToSettings;
29 changes: 28 additions & 1 deletion src/settings/Database.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Database {
platform: 'pc',
language: 'en-us',
delete_after_respond: true,
createPrivateChannel: false,
};
}

Expand Down Expand Up @@ -300,7 +301,7 @@ class Database {
guild.channels.array().forEach((channel) => {
promises.push(this.setChannelSetting(channel, setting, val));
});
return null;
return Promise.each(promises, () => {});
}

/**
Expand Down Expand Up @@ -841,6 +842,32 @@ class Database {
return [];
});
}

addPrivateRoom(guild, textChannel, voiceChannel) {
const query = SQL`INSERT INTO private_channels (guild_id, text_id, voice_id) VALUES (${guild.id}, ${textChannel.id}, ${voiceChannel.id})`;
return this.db.query(query);
}

deletePrivateRoom(guild, textChannel, voiceChannel) {
const query = SQL`DELETE FROM private_channels WHERE guild_id = ${guild.id} AND text_id = ${textChannel.id} AND voice_id = ${voiceChannel.id}`;
return this.db.query(query);
}

getPrivateRooms() {
const query = SQL`SELECT guild_id, text_id, voice_id, created_at as crt_sec FROM private_channels WHERE MOD(IFNULL(guild_id, 0) >> 22, ${this.bot.shardCount}) = ${this.bot.shardId}`;
return this.db.query(query)
.then((res) => {
if (res[0]) {
return res[0].map(value => ({
guild: this.bot.client.guilds.get(value.guild_id),
textChannel: this.bot.client.channels.get(value.text_id),
voiceChannel: this.bot.client.channels.get(value.voice_id),
createdAt: value.crt_sec,
}));
}
return [];
});
}
}

module.exports = Database;
Loading