Skip to content

Commit

Permalink
add channel categories (#1727)
Browse files Browse the repository at this point in the history
* add channel categories

* add specific class

* speed

* Update Channel.js

* fix type typo

* Update Channel.js

* rewrite position stuff in prep for category sorting

* fix small issues in generation of permissions

* Update Guild.js

* Update Constants.js

* Update GuildChannel.js

* doc fix

* Update GuildChannel.js

* <.<
  • Loading branch information
devsnek authored and iCrawl committed Sep 9, 2017
1 parent ac4b2b3 commit c46c092
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 57 deletions.
9 changes: 9 additions & 0 deletions src/structures/CategoryChannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const GuildChannel = require('./GuildChannel');

class CategoryChannel extends GuildChannel {
get children() {
return this.guild.channels.filter(c => c.parentID === this.id);
}
}

module.exports = CategoryChannel;
5 changes: 5 additions & 0 deletions src/structures/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Channel extends Base {
* * `group` - a Group DM channel
* * `text` - a guild text channel
* * `voice` - a guild voice channel
* * `category` - a guild category channel
* * `unknown` - a generic channel of unknown type, could be Channel or GuildChannel
* @type {string}
*/
Expand Down Expand Up @@ -69,6 +70,7 @@ class Channel extends Base {
const GroupDMChannel = require('./GroupDMChannel');
const TextChannel = require('./TextChannel');
const VoiceChannel = require('./VoiceChannel');
const CategoryChannel = require('./CategoryChannel');
const GuildChannel = require('./GuildChannel');
const types = Constants.ChannelTypes;
let channel;
Expand All @@ -86,6 +88,9 @@ class Channel extends Base {
case types.VOICE:
channel = new VoiceChannel(guild, data);
break;
case types.CATEGORY:
channel = new CategoryChannel(guild, data);
break;
default:
channel = new GuildChannel(guild, data);
}
Expand Down
35 changes: 10 additions & 25 deletions src/structures/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -891,10 +891,9 @@ class Guild extends Base {
/**
* Creates a new channel in the guild.
* @param {string} name The name of the new channel
* @param {string} type The type of the new channel, either `text` or `voice`
* @param {Object} [options={}] Options
* @param {string} type The type of the new channel, either `text`, `voice`, or `category`
* @param {Object} [options] Options
* @param {Array<PermissionOverwrites|ChannelCreationOverwrites>} [options.overwrites] Permission overwrites
* to apply to the new channel
* @param {string} [options.reason] Reason for creating this channel
* @returns {Promise<TextChannel|VoiceChannel>}
* @example
Expand Down Expand Up @@ -1205,31 +1204,17 @@ class Guild extends Base {

/**
* Fetches a collection of channels in the current guild sorted by position.
* @param {string} type The channel type
* @param {Channel} channel Channel
* @returns {Collection<Snowflake, GuildChannel>}
* @private
*/
_sortedChannels(type) {
return this._sortPositionWithID(this.channels.filter(c => {
if (type === 'voice' && c.type === 'voice') return true;
else if (type !== 'voice' && c.type !== 'voice') return true;
else return type === c.type;
}));
}

/**
* Sorts a collection by object position or ID if the positions are equivalent.
* Intended to be identical to Discord's sorting method.
* @param {Collection} collection The collection to sort
* @returns {Collection}
* @private
*/
_sortPositionWithID(collection) {
return collection.sort((a, b) =>
a.position !== b.position ?
a.position - b.position :
Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber()
);
_sortedChannels(channel) {
const sort = col => col
.sort((a, b) => a.rawPosition - b.rawPosition || Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber());
if (channel.type === Constants.ChannelTypes.CATEGORY) {
return sort(this.channels.filter(c => c.type === Constants.ChannelTypes.CATEGORY));
}
return sort(this.channels.filter(c => c.parent === channel.parent));
}
}

Expand Down
91 changes: 66 additions & 25 deletions src/structures/GuildChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const Channel = require('./Channel');
const Role = require('./Role');
const Invite = require('./Invite');
const PermissionOverwrites = require('./PermissionOverwrites');
const Util = require('../util/Util');
const Permissions = require('../util/Permissions');
const Collection = require('../util/Collection');
const Constants = require('../util/Constants');
Expand Down Expand Up @@ -32,10 +33,16 @@ class GuildChannel extends Channel {
this.name = data.name;

/**
* The position of the channel in the list
* The raw position of the channel from discord
* @type {number}
*/
this.position = data.position;
this.rawPosition = data.position;

/**
* The ID of the category parent of this channel
* @type {?Snowflake}
*/
this.parentID = data.parent_id;

/**
* A map of permission overwrites in this channel for roles and users
Expand All @@ -49,13 +56,21 @@ class GuildChannel extends Channel {
}
}

/**
* The category parent of this channel
* @type {?GuildChannel}
*/
get parent() {
return this.guild.channels.get(this.parentID);
}

/**
* The position of the channel
* @type {number}
* @readonly
*/
get calculatedPosition() {
const sorted = this.guild._sortedChannels(this.type);
get position() {
const sorted = this.guild._sortedChannels(this);
return sorted.array().indexOf(sorted.get(this.id));
}

Expand All @@ -70,34 +85,32 @@ class GuildChannel extends Channel {
if (!member) return null;
if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL);

let permissions = 0;

const roles = member.roles;
for (const role of roles.values()) permissions |= role.permissions;

const overwrites = this.overwritesFor(member, true, roles);
let resolved = this.guild.roles.get(this.guild.id).permissions;

const overwrites = this.overwritesFor(member, true);
if (overwrites.everyone) {
permissions &= ~overwrites.everyone._denied;
permissions |= overwrites.everyone._allowed;
resolved &= ~overwrites.everyone._denied;
resolved |= overwrites.everyone._allowed;
}

let allow = 0;
let allows = 0;
let denies = 0;
for (const overwrite of overwrites.roles) {
permissions &= ~overwrite._denied;
allow |= overwrite._allowed;
allows |= overwrite._allowed;
denies |= overwrite._denied;
}
permissions |= allow;
resolved &= ~denies;
resolved |= allows;

if (overwrites.member) {
permissions &= ~overwrites.member._denied;
permissions |= overwrites.member._allowed;
resolved &= ~overwrites.member._denied;
resolved |= overwrites.member._allowed;
}

const admin = Boolean(permissions & Permissions.FLAGS.ADMINISTRATOR);
if (admin) permissions = Permissions.ALL;
const admin = Boolean(resolved & Permissions.FLAGS.ADMINISTRATOR);
if (admin) resolved = Permissions.ALL;

return new Permissions(permissions);
return new Permissions(resolved);
}

overwritesFor(member, verified = false, roles = null) {
Expand Down Expand Up @@ -236,10 +249,12 @@ class GuildChannel extends Channel {
return this.client.api.channels(this.id).patch({
data: {
name: (data.name || this.name).trim(),
topic: data.topic || this.topic,
topic: data.topic,
position: data.position || this.position,
bitrate: data.bitrate || (this.bitrate ? this.bitrate * 1000 : undefined),
user_limit: data.userLimit || this.userLimit,
user_limit: data.userLimit != null ? data.userLimit : this.userLimit, // eslint-disable-line eqeqeq
parent_id: data.parentID,
lock_permissions: data.lockPermissions,
},
reason,
}).then(newData => {
Expand Down Expand Up @@ -275,8 +290,34 @@ class GuildChannel extends Channel {
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
* .catch(console.error);
*/
setPosition(position, relative) {
return this.guild.setChannelPosition(this, position, relative).then(() => this);
setPosition(position, { relative, reason }) {
position = Number(position);
if (isNaN(position)) return Promise.reject(new TypeError('INVALID_TYPE', 'position', 'number'));
let updatedChannels = this.guild._sortedChannels(this).array();
Util.moveElementInArray(updatedChannels, this, position, relative);
updatedChannels = updatedChannels.map((r, i) => ({ id: r.id, position: i }));
return this.client.api.guilds(this.id).channels.patch({ data: updatedChannels, reason })
.then(() => {
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.id,
channels: updatedChannels,
});
return this;
});
}

/**
* Set the category parent of this channel.
* @param {GuildChannel|Snowflake} channel Parent channel
* @param {boolean} [options.lockPermissions] Lock the permissions to what the parent's permissions are
* @param {string} [options.reason] Reason for modifying the parent of this channel
* @returns {Promise<GuildChannel>}
*/
setParent(channel, { lockPermissions = true, reason } = {}) {
return this.edit({
parentID: channel.id ? channel.id : channel,
lockPermissions,
}, reason);
}

/**
Expand Down
15 changes: 8 additions & 7 deletions src/util/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,6 @@ exports.VoiceStatus = {
DISCONNECTED: 4,
};

exports.ChannelTypes = {
TEXT: 0,
DM: 1,
VOICE: 2,
GROUP: 3,
};

exports.OPCodes = {
DISPATCH: 0,
HEARTBEAT: 1,
Expand Down Expand Up @@ -574,6 +567,14 @@ exports.UserFlags = {
HYPESQUAD: 1 << 2,
};

exports.ChannelTypes = {
TEXT: 0,
DM: 1,
VOICE: 2,
GROUP: 3,
CATEGORY: 4,
};

exports.ClientApplicationAssetTypes = {
SMALL: 1,
BIG: 2,
Expand Down

0 comments on commit c46c092

Please sign in to comment.