This commit is contained in:
Amish Shah
2017-04-29 21:09:56 +01:00
6 changed files with 271 additions and 27 deletions

View File

@@ -19,6 +19,7 @@ const Channel = require('../../structures/Channel');
const GroupDMChannel = require('../../structures/GroupDMChannel'); const GroupDMChannel = require('../../structures/GroupDMChannel');
const Guild = require('../../structures/Guild'); const Guild = require('../../structures/Guild');
const VoiceRegion = require('../../structures/VoiceRegion'); const VoiceRegion = require('../../structures/VoiceRegion');
const GuildAuditLogs = require('../../structures/GuildAuditLogs');
class RESTMethods { class RESTMethods {
constructor(restManager) { constructor(restManager) {
@@ -386,8 +387,9 @@ class RESTMethods {
); );
} }
kickGuildMember(guild, member) { kickGuildMember(guild, member, reason) {
return this.rest.makeRequest('delete', Endpoints.Guild(guild).Member(member), true).then(() => const url = `${Endpoints.Guild(guild).Member(member)}?reason=${reason}`;
return this.rest.makeRequest('delete', url, true).then(() =>
this.client.actions.GuildMemberRemove.handle({ this.client.actions.GuildMemberRemove.handle({
guild_id: guild.id, guild_id: guild.id,
user: member.user, user: member.user,
@@ -530,14 +532,12 @@ class RESTMethods {
return this.rest.makeRequest('post', Endpoints.Channel(channelID).typing, true); return this.rest.makeRequest('post', Endpoints.Channel(channelID).typing, true);
} }
banGuildMember(guild, member, deleteDays = 0) { banGuildMember(guild, member, options) {
const id = this.client.resolver.resolveUserID(member); const id = this.client.resolver.resolveUserID(member);
if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.')); if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.'));
return this.rest.makeRequest(
'put', `${Endpoints.Guild(guild).bans}/${id}?delete-message-days=${deleteDays}`, true, { const url = `${Endpoints.Guild(guild).bans}/${id}?${querystring.stringify(options)}`;
'delete-message-days': deleteDays, return this.rest.makeRequest('put', url, true).then(() => {
}
).then(() => {
if (member instanceof GuildMember) return member; if (member instanceof GuildMember) return member;
const user = this.client.resolver.resolveUser(id); const user = this.client.resolver.resolveUser(id);
if (user) { if (user) {
@@ -576,14 +576,15 @@ class RESTMethods {
} }
getGuildBans(guild) { getGuildBans(guild) {
return this.rest.makeRequest('get', Endpoints.Guild(guild).bans, true).then(banItems => { return this.rest.makeRequest('get', Endpoints.Guild(guild).bans, true).then(bans =>
const bannedUsers = new Collection(); bans.reduce((collection, ban) => {
for (const banItem of banItems) { collection.set(ban.user.id, {
const user = this.client.dataManager.newUser(banItem.user); reason: ban.reason,
bannedUsers.set(user.id, user); user: this.client.dataManager.newUser(ban.user),
} });
return bannedUsers; return collection;
}); }, new Collection())
);
} }
updateGuildRole(role, _data) { updateGuildRole(role, _data) {
@@ -674,6 +675,23 @@ class RESTMethods {
.then(() => this.client.actions.GuildEmojiDelete.handle(emoji).data); .then(() => this.client.actions.GuildEmojiDelete.handle(emoji).data);
} }
getGuildAuditLogs(guild, options = {}) {
if (options.before && options.before instanceof GuildAuditLogs.Entry) options.before = options.before.id;
if (options.after && options.after instanceof GuildAuditLogs.Entry) options.after = options.after.id;
if (typeof options.type === 'string') options.type = GuildAuditLogs.Actions[options.type];
const queryString = (querystring.stringify({
before: options.before,
after: options.after,
limit: options.limit,
user_id: this.client.resolver.resolveUserID(options.user),
action_type: options.type,
}).match(/[^=&?]+=[^=&?]+/g) || []).join('&');
return this.rest.makeRequest('get', `${Endpoints.Guild(guild).auditLogs}?${queryString}`, true)
.then(data => GuildAuditLogs.build(guild, data));
}
getWebhook(id, token) { getWebhook(id, token) {
return this.rest.makeRequest('get', Endpoints.Webhook(id, token), !token).then(data => return this.rest.makeRequest('get', Endpoints.Webhook(id, token), !token).then(data =>
new Webhook(this.client, data) new Webhook(this.client, data)

View File

@@ -347,7 +347,13 @@ class Guild {
* @returns {Promise<Collection<Snowflake, User>>} * @returns {Promise<Collection<Snowflake, User>>}
*/ */
fetchBans() { fetchBans() {
return this.client.rest.methods.getGuildBans(this); return this.client.rest.methods.getGuildBans(this)
// This entire re-mapping can be removed in the next major release
.then(bans => {
const users = new Collection();
for (const ban of bans.values()) users.set(ban.user.id, ban.user);
return users;
});
} }
/** /**
@@ -374,6 +380,20 @@ class Guild {
return this.client.rest.methods.fetchVoiceRegions(this.id); return this.client.rest.methods.fetchVoiceRegions(this.id);
} }
/**
* Fetch audit logs for this guild
* @param {Object} [options={}] Options for fetching audit logs
* @param {Snowflake|GuildAuditLogsEntry} [options.before] Limit to entries from before specified entry
* @param {Snowflake|GuildAuditLogsEntry} [options.after] Limit to entries from after specified entry
* @param {number} [options.limit] Limit number of entries
* @param {UserResolvable} [options.user] Only show entries involving this user
* @param {string|number} [options.type] Only show entries involving this action type
* @returns {Promise<GuildAuditLogs>}
*/
fetchAuditLogs(options) {
return this.client.rest.methods.getGuildAuditLogs(this, options);
}
/** /**
* Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` permission. * Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` permission.
* @param {UserResolvable} user User to add to the guild * @param {UserResolvable} user User to add to the guild
@@ -607,8 +627,9 @@ class Guild {
/** /**
* Bans a user from the guild. * Bans a user from the guild.
* @param {UserResolvable} user The user to ban * @param {UserResolvable} user The user to ban
* @param {number} [deleteDays=0] The amount of days worth of messages from this user that should * @param {Object} [options] Ban options.
* also be deleted. Between `0` and `7`. * @param {number} [options.days=0] Number of days of messages to delete
* @param {string} [options.reason] Reason for banning
* @returns {Promise<GuildMember|User|string>} Result object will be resolved as specifically as possible. * @returns {Promise<GuildMember|User|string>} Result object will be resolved as specifically as possible.
* If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot * 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. * be resolved, the user ID will be the result.
@@ -618,8 +639,13 @@ class Guild {
* .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`)) * .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`))
* .catch(console.error); * .catch(console.error);
*/ */
ban(user, deleteDays = 0) { ban(user, options = {}) {
return this.client.rest.methods.banGuildMember(this, user, deleteDays); if (typeof options === 'number') {
options = { reason: null, days: options };
} else if (typeof options === 'string') {
options = { reason: options, days: 0 };
}
return this.client.rest.methods.banGuildMember(this, user, options);
} }
/** /**

View File

@@ -0,0 +1,196 @@
const Targets = {
GUILD: 'GUILD',
CHANNEL: 'CHANNEL',
USER: 'USER',
ROLE: 'ROLE',
INVITE: 'INVITE',
WEBHOOK: 'WEBHOOK',
EMOJI: 'EMOJI',
};
const Actions = {
GUILD_UPDATE: 1,
CHANNEL_CREATE: 10,
CHANNEL_UPDATE: 11,
CHANNEL_DELETE: 12,
CHANNEL_OVERWRITE_CREATE: 13,
CHANNEL_OVERWRITE_UPDATE: 14,
CHANNEL_OVERWRITE_DELETE: 15,
MEMBER_KICK: 20,
MEMBER_PRUNE: 21,
MEMBER_BAN_ADD: 22,
MEMBER_BAN_REMOVE: 23,
MEMBER_UPDATE: 24,
MEMBER_ROLE_UPDATE: 25,
ROLE_CREATE: 30,
ROLE_UPDATE: 31,
ROLE_DELETE: 32,
INVITE_CREATE: 40,
INVITE_UPDATE: 41,
INVITE_DELETE: 42,
WEBHOOK_CREATE: 50,
WEBHOOK_UPDATE: 51,
WEBHOOK_DELETE: 52,
EMOJI_CREATE: 60,
EMOJI_UPDATE: 61,
EMOJI_DELETE: 62,
};
class GuildAuditLogs {
constructor(guild, data) {
if (data.users) for (const user of data.users) guild.client.dataManager.newUser(user);
/**
* Entries for this Guild's audit logs
* @type {GuildAuditLogsEntry[]}
*/
this.entries = [];
for (const entry of data.audit_log_entries) this.entries.push(new GuildAuditLogsEntry(guild, entry));
}
/**
* Handles possible promises for entry targets
* @returns {GuildAuditLogs}
*/
static build(...args) {
return new Promise(resolve => {
const logs = new GuildAuditLogs(...args);
Promise.all(logs.entries.map(e => e.target)).then(() => resolve(logs));
});
}
/**
* Find target type from entry action
* @param {number} target Action target
* @returns {?string}
*/
static targetType(target) {
if (target < 10) return Targets.GUILD;
if (target < 20) return Targets.CHANNEL;
if (target < 30) return Targets.USER;
if (target < 40) return Targets.ROLE;
if (target < 50) return Targets.INVITE;
if (target < 60) return Targets.WEBHOOK;
if (target < 70) return Targets.EMOJI;
return null;
}
/**
* Find action type from entry action
* @param {string} action Action target
* @returns {string}
*/
static actionType(action) {
if ([
Actions.CHANNEL_CREATE,
Actions.CHANNEL_OVERWRITE_CREATE,
Actions.MEMBER_BAN_REMOVE,
Actions.ROLE_CREATE,
Actions.INVITE_CREATE,
Actions.WEBHOOK_CREATE,
Actions.EMOJI_CREATE,
].includes(action)) return 'CREATE';
if ([
Actions.CHANNEL_DELETE,
Actions.CHANNEL_OVERWRITE_DELETE,
Actions.MEMBER_KICK,
Actions.MEMBER_PRUNE,
Actions.MEMBER_BAN_ADD,
Actions.ROLE_DELETE,
Actions.INVITE_DELETE,
Actions.WEBHOOK_DELETE,
Actions.EMOJI_DELETE,
].includes(action)) return 'DELETE';
if ([
Actions.GUILD_UPDATE,
Actions.CHANNEL_UPDATE,
Actions.CHANNEL_OVERWRITE_UPDATE,
Actions.MEMBER_UPDATE,
Actions.ROLE_UPDATE,
Actions.INVITE_UPDATE,
Actions.WEBHOOK_UPDATE,
Actions.EMOJI_UPDATE,
].includes(action)) return 'UPDATE';
return 'ALL';
}
}
class GuildAuditLogsEntry {
constructor(guild, data) {
const targetType = GuildAuditLogs.targetType(data.action_type);
/**
* Target type of this entry
* @type {string}
*/
this.targetType = targetType;
/**
* Action type of this entry
* @type {string}
*/
this.actionType = GuildAuditLogs.actionType(data.action_type);
/**
* Specific action type of this entry
* @type {string}
*/
this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type);
/**
* Reason of this entry
* @type {?string}
*/
this.reason = data.reason || null;
/**
* User that executed this entry
* @type {User}
*/
this.executor = guild.client.users.get(data.user_id);
/**
* Specific property changes
* @type {Object[]}
*/
this.changes = data.changes ? data.changes.map(c => ({ name: c.key, old: c.old_value, new: c.new_value })) : null;
/**
* ID of this entry
* @type {Snowflake}
*/
this.id = data.id;
if (['USER', 'GUILD'].includes(targetType)) {
/**
* Target of this entry
* @type {?Guild|User|Role|Emoji|Promise<Invite>|Promise<Webhook>}
*/
this.target = guild.client[`${targetType.toLowerCase()}s`].get(data.target_id);
} else if (targetType === 'WEBHOOK') {
this.target = guild.fetchWebhooks()
.then(hooks => {
this.target = hooks.find(h => h.id === data.target_id);
return this.target;
});
} else if (targetType === 'INVITE') {
const change = this.changes.find(c => c.name === 'code');
this.target = guild.fetchInvites()
.then(invites => {
this.target = invites.find(i => i.code === (change.new || change.old));
return this.target;
});
} else {
this.target = guild[`${targetType.toLowerCase()}s`].get(data.target_id);
}
}
}
GuildAuditLogs.Actions = Actions;
GuildAuditLogs.Targets = Targets;
GuildAuditLogs.Entry = GuildAuditLogsEntry;
module.exports = GuildAuditLogs;

View File

@@ -473,23 +473,25 @@ class GuildMember {
/** /**
* Kick this member from the guild * Kick this member from the guild
* @param {string} [reason] Reason for kicking user
* @returns {Promise<GuildMember>} * @returns {Promise<GuildMember>}
*/ */
kick() { kick(reason) {
return this.client.rest.methods.kickGuildMember(this.guild, this); return this.client.rest.methods.kickGuildMember(this.guild, this, reason);
} }
/** /**
* Ban this guild member * Ban this guild member
* @param {number} [deleteDays=0] The amount of days worth of messages from this member that should * @param {Object} [options] Ban options.
* also be deleted. Between `0` and `7`. * @param {number} [options.days=0] Number of days of messages to delete
* @param {string} [options.reason] Reason for banning
* @returns {Promise<GuildMember>} * @returns {Promise<GuildMember>}
* @example * @example
* // ban a guild member * // ban a guild member
* guildMember.ban(7); * guildMember.ban(7);
*/ */
ban(deleteDays = 0) { ban(options) {
return this.client.rest.methods.banGuildMember(this.guild, this, deleteDays); return this.guild.ban(this, options);
} }
/** /**

View File

@@ -132,6 +132,7 @@ const Endpoints = exports.Endpoints = {
webhooks: `${base}/webhooks`, webhooks: `${base}/webhooks`,
ack: `${base}/ack`, ack: `${base}/ack`,
settings: `${base}/settings`, settings: `${base}/settings`,
auditLogs: `${base}/audit-logs`,
Emoji: emojiID => Endpoints.CDN(root).Emoji(emojiID), Emoji: emojiID => Endpoints.CDN(root).Emoji(emojiID),
Icon: (root, hash) => Endpoints.CDN(root).Icon(guildID, hash), Icon: (root, hash) => Endpoints.CDN(root).Icon(guildID, hash),
Splash: (root, hash) => Endpoints.CDN(root).Splash(guildID, hash), Splash: (root, hash) => Endpoints.CDN(root).Splash(guildID, hash),

View File

@@ -212,6 +212,7 @@ Permissions.FLAGS = {
MANAGE_CHANNELS: 1 << 4, MANAGE_CHANNELS: 1 << 4,
MANAGE_GUILD: 1 << 5, MANAGE_GUILD: 1 << 5,
ADD_REACTIONS: 1 << 6, ADD_REACTIONS: 1 << 6,
VIEW_AUDIT_LOG: 1 << 7,
READ_MESSAGES: 1 << 10, READ_MESSAGES: 1 << 10,
SEND_MESSAGES: 1 << 11, SEND_MESSAGES: 1 << 11,