Merge branch 'master' into indev-prism

This commit is contained in:
Amish Shah
2017-02-22 20:37:59 +00:00
48 changed files with 738 additions and 420 deletions

View File

@@ -1,26 +0,0 @@
const User = require('./User');
const OAuth2Application = require('./OAuth2Application');
/**
* Represents the client's OAuth2 Application
* @extends {OAuth2Application}
*/
class ClientOAuth2Application extends OAuth2Application {
setup(data) {
super.setup(data);
/**
* The app's flags
* @type {number}
*/
this.flags = data.flags;
/**
* The app's owner
* @type {User}
*/
this.owner = new User(this.client, data.owner);
}
}
module.exports = ClientOAuth2Application;

View File

@@ -5,9 +5,7 @@ const Presence = require('./Presence').Presence;
const GuildMember = require('./GuildMember');
const Constants = require('../util/Constants');
const Collection = require('../util/Collection');
const cloneObject = require('../util/CloneObject');
const arraysEqual = require('../util/ArraysEqual');
const moveElementInArray = require('../util/MoveElementInArray');
const Util = require('../util/Util');
/**
* Represents a guild (or a server) on Discord.
@@ -331,6 +329,24 @@ class Guild {
return this.client.rest.methods.fetchVoiceRegions(this.id);
}
/**
* Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` permission.
* @param {UserResolvable} user User to add to the guild
* @param {Object} options Options for the addition
* @param {string} options.accessToken An OAuth2 access token for the user with the `guilds.join` scope granted to the
* bot's application
* @param {string} [options.nick] Nickname to give the member (requires `MANAGE_NICKNAMES`)
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} [options.roles] Roles to add to the member
* (requires `MANAGE_ROLES`)
* @param {boolean} [options.mute] Whether the member should be muted (requires `MUTE_MEMBERS`)
* @param {boolean} [options.deaf] Whether the member should be deafened (requires `DEAFEN_MEMBERS`)
* @returns {Promise<GuildMember>}
*/
addMember(user, options) {
if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
return this.client.rest.methods.putGuildMember(this, user, options);
}
/**
* Fetch a single guild member from a user.
* @param {UserResolvable} user The user to fetch the member for
@@ -552,8 +568,10 @@ class Guild {
* If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
* be resolved, the user ID will be the result.
* @example
* // ban a user
* guild.ban('123123123123');
* // ban a user by ID (or with a user/guild member object)
* guild.ban('some user ID')
* .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`))
* .catch(console.error);
*/
ban(user, deleteDays = 0) {
return this.client.rest.methods.banGuildMember(this, user, deleteDays);
@@ -564,10 +582,10 @@ class Guild {
* @param {UserResolvable} user The user to unban
* @returns {Promise<User>}
* @example
* // unban a user
* guild.unban('123123123123')
* // unban a user by ID (or with a user/guild member object)
* guild.unban('some user ID')
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
* .catch(reject);
* .catch(console.error);
*/
unban(user) {
return this.client.rest.methods.unbanGuildMember(this, user);
@@ -659,7 +677,7 @@ class Guild {
let updatedRoles = Object.assign([], this.roles.array()
.sort((r1, r2) => r1.position !== r2.position ? r1.position - r2.position : r1.id - r2.id));
moveElementInArray(updatedRoles, role, position, relative);
Util.moveElementInArray(updatedRoles, role, position, relative);
updatedRoles = updatedRoles.map((r, i) => ({ id: r.id, position: i }));
return this.client.rest.methods.setRolePositions(this.id, updatedRoles);
@@ -687,9 +705,10 @@ class Guild {
if (typeof attachment === 'string' && attachment.startsWith('data:')) {
resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles));
} else {
this.client.resolver.resolveBuffer(attachment).then(data =>
resolve(this.client.rest.methods.createEmoji(this, data, name, roles))
);
this.client.resolver.resolveBuffer(attachment).then(data => {
const dataURI = this.client.resolver.resolveBase64(data);
resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles));
});
}
});
}
@@ -748,7 +767,7 @@ class Guild {
this.memberCount === guild.member_count &&
this.large === guild.large &&
this.icon === guild.icon &&
arraysEqual(this.features, guild.features) &&
Util.arraysEqual(this.features, guild.features) &&
this.ownerID === guild.owner_id &&
this.verificationLevel === guild.verification_level &&
this.embedEnabled === guild.embed_enabled;
@@ -814,12 +833,12 @@ class Guild {
}
_updateMember(member, data) {
const oldMember = cloneObject(member);
const oldMember = Util.cloneObject(member);
if (data.roles) member._roles = data.roles;
if (typeof data.nick !== 'undefined') member.nickname = data.nick;
const notSame = member.nickname !== oldMember.nickname || !arraysEqual(member._roles, oldMember._roles);
const notSame = member.nickname !== oldMember.nickname || !Util.arraysEqual(member._roles, oldMember._roles);
if (this.client.ws.status === Constants.Status.READY && notSame) {
/**

View File

@@ -1,9 +1,9 @@
const Attachment = require('./MessageAttachment');
const Embed = require('./MessageEmbed');
const MessageReaction = require('./MessageReaction');
const Util = require('../util/Util');
const Collection = require('../util/Collection');
const Constants = require('../util/Constants');
const escapeMarkdown = require('../util/EscapeMarkdown');
let GuildMember;
/**
@@ -56,7 +56,7 @@ class Message {
/**
* Represents the author of the message as a guild member. Only available if the message comes from a guild
* where the author is still a member.
* @type {GuildMember}
* @type {?GuildMember}
*/
this.member = this.guild ? this.guild.member(this.author) || null : null;
@@ -409,7 +409,7 @@ class Message {
* @returns {Promise<Message>}
*/
editCode(lang, content) {
content = escapeMarkdown(this.client.resolver.resolveString(content), true);
content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true);
return this.edit(`\`\`\`${lang || ''}\n${content}\n\`\`\``);
}

View File

@@ -47,9 +47,51 @@ class OAuth2Application {
/**
* The app's RPC origins
* @type {Array<string>}
* @type {?string[]}
*/
this.rpcOrigins = data.rpc_origins;
/**
* The app's redirect URIs
* @type {string[]}
*/
this.redirectURIs = data.redirect_uris;
/**
* If this app's bot requires a code grant when using the oauth2 flow
* @type {boolean}
*/
this.botRequireCodeGrant = data.bot_require_code_grant;
/**
* If this app's bot is public
* @type {boolean}
*/
this.botPublic = data.bot_public;
/**
* If this app can use rpc
* @type {boolean}
*/
this.rpcApplicationState = data.rpc_application_state;
/**
* Object containing basic info about this app's bot
* @type {Object}
*/
this.bot = data.bot;
/**
* Flags for the app
* @type {number}
*/
this.flags = data.flags;
/**
* oauth2 secret for the app
* @type {boolean}
*/
this.secret = data.secret;
}
/**
@@ -70,6 +112,14 @@ class OAuth2Application {
return new Date(this.createdTimestamp);
}
/**
* Reset the app's secret and bot token
* @returns {OAuth2Application}
*/
reset() {
return this.client.rest.methods.resetApplication(this.id);
}
/**
* When concatenated with a string, this automatically concatenates the app name rather than the app object.
* @returns {string}

View File

@@ -37,7 +37,7 @@ class PartialGuildChannel {
* The type of this guild channel - `text` or `voice`
* @type {string}
*/
this.type = Constants.ChannelTypes.text === data.type ? 'text' : 'voice';
this.type = Constants.ChannelTypes.TEXT === data.type ? 'text' : 'voice';
}
}

View File

@@ -65,6 +65,12 @@ class RichEmbed {
* @type {Object}
*/
this.footer = data.footer;
/**
* File to upload alongside this Embed
* @type {string}
*/
this.file = data.file;
}
/**
@@ -150,6 +156,15 @@ class RichEmbed {
return this;
}
/**
* Convenience function for `<RichEmbed>.addField('\u200B', '\u200B', inline)`.
* @param {boolean} [inline=false] Set the field to display inline
* @returns {RichEmbed} This embed
*/
addBlankField(inline = false) {
return this.addField('\u200B', '\u200B', inline);
}
/**
* Set the thumbnail of this embed
* @param {string} url The URL of the thumbnail
@@ -162,7 +177,7 @@ class RichEmbed {
/**
* Set the image of this embed
* @param {string} url The URL of the thumbnail
* @param {string} url The URL of the image
* @returns {RichEmbed} This embed
*/
setImage(url) {
@@ -182,6 +197,18 @@ class RichEmbed {
this.footer = { text, icon_url: icon };
return this;
}
/**
* Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when
* setting an embed image or author/footer icons. Only one file may be attached.
* @param {FileOptions|string} file Local path or URL to the file to attach, or valid FileOptions for a file to attach
* @returns {RichEmbed} This embed
*/
attachFile(file) {
if (this.file) throw new RangeError('You may not upload more than one file at once.');
this.file = file;
return this;
}
}
module.exports = RichEmbed;

View File

@@ -140,11 +140,7 @@ class Role {
* console.log(role.serialize());
*/
serialize() {
const serializedPermissions = {};
for (const permissionName in Constants.PermissionFlags) {
serializedPermissions[permissionName] = this.hasPermission(permissionName);
}
return serializedPermissions;
return this.client.resolver.serializePermissions(this.permissions);
}
/**
@@ -160,10 +156,8 @@ class Role {
* console.log('This role can\'t ban members');
* }
*/
hasPermission(permission, explicit = false) {
permission = this.client.resolver.resolvePermission(permission);
if (!explicit && (this.permissions & Constants.PermissionFlags.ADMINISTRATOR) > 0) return true;
return (this.permissions & permission) > 0;
hasPermission(permission, explicit) {
return this.client.resolver.hasPermission(this.permissions, permission, explicit);
}
/**

View File

@@ -78,6 +78,8 @@ class TextBasedChannel {
options = {};
}
if (options.embed && options.embed.file) options.file = options.embed.file;
if (options.file) {
if (typeof options.file === 'string') options.file = { attachment: options.file };
if (!options.file.name) {
@@ -223,6 +225,33 @@ class TextBasedChannel {
});
}
/**
* @typedef {Object} MessageSearchOptions
* @property {string} [content] Message content
* @property {string} [maxID] Maximum ID for the filter
* @property {string} [minID] Minimum ID for the filter
* @property {string} [has] One of `link`, `embed`, `file`, `video`, `image`, or `sound`,
* or add `-` to negate (e.g. `-file`)
* @property {ChannelResolvable} [channel] Channel to limit search to (only for guild search endpoint)
* @property {UserResolvable} [author] Author to limit search
* @property {string} [authorType] One of `user`, `bot`, `webhook`, or add `-` to negate (e.g. `-webhook`)
* @property {string} [sortBy='recent'] `recent` or `relevant`
* @property {string} [sortOrder='desc'] `asc` or `desc`
* @property {number} [contextSize=2] How many messages to get around the matched message (0 to 2)
* @property {number} [limit=25] Maximum number of results to get (1 to 25)
* @property {number} [offset=0] Offset the "pages" of results (since you can only see 25 at a time)
* @property {UserResolvable} [mentions] Mentioned user filter
* @property {boolean} [mentionsEveryone] If everyone is mentioned
* @property {string} [linkHostname] Filter links by hostname
* @property {string} [embedProvider] The name of an embed provider
* @property {string} [embedType] one of `image`, `video`, `url`, `rich`
* @property {string} [attachmentFilename] The name of an attachment
* @property {string} [attachmentExtension] The extension of an attachment
* @property {Date} [before] Date to find messages before
* @property {Date} [after] Date to find messages before
* @property {Date} [during] Date to find messages during (range of date to date + 24 hours)
*/
/**
* Performs a search within the channel.
* @param {MessageSearchOptions} [options={}] Options to pass to the search