Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support new moderation api stuff #1403

Merged
merged 43 commits into from
Apr 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
998a485
start audit logs
devsnek Apr 23, 2017
22d3540
make better var types so gawdl3y doesn't shit on this
devsnek Apr 23, 2017
cdb6241
add constructor stuff
devsnek Apr 23, 2017
cd397e1
make more changes
devsnek Apr 23, 2017
7e47d54
add entry creation
devsnek Apr 23, 2017
1fd6514
add methods
devsnek Apr 23, 2017
75617d8
make it all work hopefully
devsnek Apr 23, 2017
536a029
aaa
devsnek Apr 23, 2017
c6d5200
aaaa
devsnek Apr 23, 2017
4f2db4a
i wish i could test this locally
devsnek Apr 23, 2017
d53d80e
fix users, guild when i feel like it
devsnek Apr 23, 2017
837f065
make guild prop non-enumerable
devsnek Apr 23, 2017
edfdafd
make better types
devsnek Apr 23, 2017
1af75c2
change nouns
devsnek Apr 23, 2017
30c35e1
e
devsnek Apr 23, 2017
d4d6774
Update GuildAuditLogs.js
devsnek Apr 23, 2017
4f03c41
Update GuildAuditLogs.js
devsnek Apr 23, 2017
e9ca17b
Update GuildAuditLogs.js
devsnek Apr 23, 2017
31b3443
eek
devsnek Apr 24, 2017
574ecb6
Update GuildAuditLogs.js
devsnek Apr 24, 2017
7f515c5
Update GuildAuditLogs.js
devsnek Apr 24, 2017
e40246b
friggin trailing spaces
devsnek Apr 24, 2017
83e0f3d
Update GuildAuditLogs.js
devsnek Apr 24, 2017
c73bd8f
docs!
devsnek Apr 24, 2017
447390c
Update GuildAuditLogs.js
devsnek Apr 27, 2017
a416232
reason stuff
devsnek Apr 27, 2017
3b3c42d
Update GuildAuditLogs.js
devsnek Apr 27, 2017
7ecd60b
Update GuildAuditLogs.js
devsnek Apr 27, 2017
5e354cc
support before/after for pagination
devsnek Apr 27, 2017
3cf60e6
Update Guild.js
devsnek Apr 27, 2017
e16814c
Update GuildAuditLogs.js
devsnek Apr 27, 2017
22e800c
mfw using github web editor
devsnek Apr 27, 2017
87980b6
fix build
devsnek Apr 27, 2017
095692b
Update Guild.js
devsnek Apr 27, 2017
c70cae6
amazing cache fuckery shit evil
devsnek Apr 27, 2017
0258420
Merge branch 'auditlogs' of https://github.com/guscaplan/discord.js i…
devsnek Apr 27, 2017
54e6ba7
cool stuff
devsnek Apr 27, 2017
1490436
make building audit logs nicer
devsnek Apr 28, 2017
cdcc0bf
ban endpoint stuff
devsnek Apr 28, 2017
110d406
dox
devsnek Apr 28, 2017
161f32d
Merge branch 'master' into auditlogs
devsnek Apr 28, 2017
3b0809f
Merge branch 'master' into auditlogs
iCrawl Apr 29, 2017
caf9514
<.<
devsnek Apr 29, 2017
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
50 changes: 34 additions & 16 deletions src/client/rest/RESTMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const Channel = require('../../structures/Channel');
const GroupDMChannel = require('../../structures/GroupDMChannel');
const Guild = require('../../structures/Guild');
const VoiceRegion = require('../../structures/VoiceRegion');
const GuildAuditLogs = require('../../structures/GuildAuditLogs');

class RESTMethods {
constructor(restManager) {
Expand Down Expand Up @@ -386,8 +387,9 @@ class RESTMethods {
);
}

kickGuildMember(guild, member) {
return this.rest.makeRequest('delete', Endpoints.Guild(guild).Member(member), true).then(() =>
kickGuildMember(guild, member, reason) {
const url = `${Endpoints.Guild(guild).Member(member)}?reason=${reason}`;
return this.rest.makeRequest('delete', url, true).then(() =>
this.client.actions.GuildMemberRemove.handle({
guild_id: guild.id,
user: member.user,
Expand Down Expand Up @@ -530,14 +532,12 @@ class RESTMethods {
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);
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, {
'delete-message-days': deleteDays,
}
).then(() => {

const url = `${Endpoints.Guild(guild).bans}/${id}?${querystring.stringify(options)}`;
return this.rest.makeRequest('put', url, true).then(() => {
if (member instanceof GuildMember) return member;
const user = this.client.resolver.resolveUser(id);
if (user) {
Expand Down Expand Up @@ -576,14 +576,15 @@ class RESTMethods {
}

getGuildBans(guild) {
return this.rest.makeRequest('get', Endpoints.Guild(guild).bans, true).then(banItems => {
const bannedUsers = new Collection();
for (const banItem of banItems) {
const user = this.client.dataManager.newUser(banItem.user);
bannedUsers.set(user.id, user);
}
return bannedUsers;
});
return this.rest.makeRequest('get', Endpoints.Guild(guild).bans, true).then(bans =>
bans.reduce((collection, ban) => {
collection.set(ban.user.id, {
reason: ban.reason,
user: this.client.dataManager.newUser(ban.user),
});
return collection;
}, new Collection())
);
}

updateGuildRole(role, _data) {
Expand Down Expand Up @@ -674,6 +675,23 @@ class RESTMethods {
.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) {
return this.rest.makeRequest('get', Endpoints.Webhook(id, token), !token).then(data =>
new Webhook(this.client, data)
Expand Down
36 changes: 31 additions & 5 deletions src/structures/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,13 @@ class Guild {
* @returns {Promise<Collection<Snowflake, User>>}
*/
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;
});
}

/**
Expand All @@ -374,6 +380,20 @@ class Guild {
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.
* @param {UserResolvable} user User to add to the guild
Expand Down Expand Up @@ -607,8 +627,9 @@ class Guild {
/**
* Bans a user from the guild.
* @param {UserResolvable} user The user to ban
* @param {number} [deleteDays=0] The amount of days worth of messages from this user that should
* also be deleted. Between `0` and `7`.
* @param {Object} [options] Ban options.
* @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.
* 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.
Expand All @@ -618,8 +639,13 @@ class Guild {
* .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);
ban(user, options = {}) {
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);
}

/**
Expand Down
196 changes: 196 additions & 0 deletions src/structures/GuildAuditLogs.js
Original file line number Diff line number Diff line change
@@ -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;
14 changes: 8 additions & 6 deletions src/structures/GuildMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,23 +472,25 @@ class GuildMember {

/**
* Kick this member from the guild
* @param {string} [reason] Reason for kicking user
* @returns {Promise<GuildMember>}
*/
kick() {
return this.client.rest.methods.kickGuildMember(this.guild, this);
kick(reason) {
return this.client.rest.methods.kickGuildMember(this.guild, this, reason);
}

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

/**
Expand Down
1 change: 1 addition & 0 deletions src/util/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const Endpoints = exports.Endpoints = {
webhooks: `${base}/webhooks`,
ack: `${base}/ack`,
settings: `${base}/settings`,
auditLogs: `${base}/audit-logs`,
Emoji: emojiID => Endpoints.CDN(root).Emoji(emojiID),
Icon: (root, hash) => Endpoints.CDN(root).Icon(guildID, hash),
Splash: (root, hash) => Endpoints.CDN(root).Splash(guildID, hash),
Expand Down
1 change: 1 addition & 0 deletions src/util/Permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ Permissions.FLAGS = {
MANAGE_CHANNELS: 1 << 4,
MANAGE_GUILD: 1 << 5,
ADD_REACTIONS: 1 << 6,
VIEW_AUDIT_LOG: 1 << 7,

READ_MESSAGES: 1 << 10,
SEND_MESSAGES: 1 << 11,
Expand Down