Internal API Request Rewrite (#1490)

* start rewrite

* converted guilds

* more changes

* convert GuildMember

* convert User and remove friend methods which kill people

* convert more stuff

* even more stuff

* make things nicer

* speed and fixes and stuff

* almost finished

* fix

* Update Client.js

* uwu

* Update RESTMethods.js

* message editing

* fix router

* fix issue with references

* message delete reason

* move message sending

* fix dm

* message splitting

* NO MORE REST METHODS

* Update Client.js

* Update WebhookClient.js

* remove all those endpoints from the constants

* Update ClientUser.js

* Update ClientUser.js

* fixes

* Update ClientUser.js

* complaiancy

* all sort of fixes

* merge master (#1)

* Fix Permissions now that member is deprecated (#1491)

* removing more deprecation leftovers (#1492)

* Fix MessageCollectors

* Fix awaitMessages (#1493)

* Fix MessageCollector#cleanup

* Fix MessageCollector#postCheck

* Add max option back for safety

* Update Invite.js (#1496)

* guild setPosition missing docs (#1498)

* missing docs

* update return docs

* indent

* switched .invites for the apirouter and invite.js

* make multiple options an object

* Update ClientUser.js

* fix nicks

* Update WebhookClient.js
This commit is contained in:
Gus Caplan
2017-05-21 00:04:19 -05:00
committed by Crawl
parent 02f03c439f
commit 0baa59b679
33 changed files with 849 additions and 1303 deletions

View File

@@ -12,6 +12,11 @@ const WebSocketManager = require('./websocket/WebSocketManager');
const ActionsManager = require('./actions/ActionsManager');
const Collection = require('../util/Collection');
const Presence = require('../structures/Presence').Presence;
const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook');
const User = require('../structures/User');
const Invite = require('../structures/Invite');
const OAuth2Application = require('../structures/OAuth2Application');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const VoiceBroadcast = require('./voice/VoiceBroadcast');
@@ -44,6 +49,13 @@ class Client extends EventEmitter {
*/
this.rest = new RESTManager(this);
/**
* API shortcut
* @type {Object}
* @private
*/
this.api = this.rest.api;
/**
* The data manager of the client
* @type {ClientDataManager}
@@ -274,7 +286,11 @@ class Client extends EventEmitter {
* client.login('my token');
*/
login(token) {
return this.rest.methods.login(token);
return new Promise((resolve, reject) => {
if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN);
token = token.replace(/^Bot\s*/i, '');
this.manager.connectToWebSocket(token, resolve, reject);
});
}
/**
@@ -312,7 +328,9 @@ class Client extends EventEmitter {
*/
fetchUser(id, cache = true) {
if (this.users.has(id)) return Promise.resolve(this.users.get(id));
return this.rest.methods.getUser(id, cache);
return this.api.users(id).get().then(data =>
cache ? this.dataManager.newUser(data) : new User(this, data)
);
}
/**
@@ -322,7 +340,8 @@ class Client extends EventEmitter {
*/
fetchInvite(invite) {
const code = this.resolver.resolveInviteCode(invite);
return this.rest.methods.getInvite(code);
return this.api.invites(code).get({ query: { with_counts: true } })
.then(data => new Invite(this, data));
}
/**
@@ -332,7 +351,7 @@ class Client extends EventEmitter {
* @returns {Promise<Webhook>}
*/
fetchWebhook(id, token) {
return this.rest.methods.getWebhook(id, token);
return this.api.webhooks(id, token).get().then(data => new Webhook(this.client, data));
}
/**
@@ -340,7 +359,11 @@ class Client extends EventEmitter {
* @returns {Collection<string, VoiceRegion>}
*/
fetchVoiceRegions() {
return this.rest.methods.fetchVoiceRegions();
return this.rest.api.voice.regions.get().then(res => {
const regions = new Collection();
for (const region of res) regions.set(region.id, new VoiceRegion(region));
return regions;
});
}
/**
@@ -385,7 +408,8 @@ class Client extends EventEmitter {
* @returns {Promise<OAuth2Application>}
*/
fetchApplication(id = '@me') {
return this.rest.methods.getApplication(id);
return this.rest.api.oauth2.applications(id).get()
.then(app => new OAuth2Application(this.client, app));
}
/**

View File

@@ -38,7 +38,7 @@ class ClientManager {
this.client.emit(Constants.Events.DEBUG, `Authenticated using token ${token}`);
this.client.token = token;
const timeout = this.client.setTimeout(() => reject(new Error(Constants.Errors.TOOK_TOO_LONG)), 1000 * 300);
this.client.rest.methods.getGateway().then(res => {
this.client.api.gateway.get().then(res => {
const protocolVersion = Constants.DefaultOptions.ws.version;
const gateway = `${res.url}/?v=${protocolVersion}&encoding=${WebSocketConnection.ENCODING}`;
this.client.emit(Constants.Events.DEBUG, `Using gateway ${gateway}`);
@@ -63,7 +63,7 @@ class ClientManager {
this.client.token = null;
return Promise.resolve();
} else {
return this.client.rest.methods.logout().then(() => {
return this.client.api.logout.post().then(() => {
this.client.token = null;
});
}

View File

@@ -34,6 +34,13 @@ class WebhookClient extends Webhook {
*/
this.rest = new RESTManager(this);
/**
* API shortcut
* @type {Object}
* @private
*/
this.api = this.rest.api;
/**
* The data resolver of the client
* @type {ClientDataResolver}

View File

@@ -1,16 +1,15 @@
const querystring = require('querystring');
const snekfetch = require('snekfetch');
const Constants = require('../../util/Constants');
class APIRequest {
constructor(rest, method, path, auth, data, files) {
constructor(rest, method, path, options) {
this.rest = rest;
this.client = rest.client;
this.method = method;
this.path = path.toString();
this.auth = auth;
this.data = data;
this.files = files;
this.route = this.getRoute(this.path);
this.options = options;
}
getRoute(url) {
@@ -34,14 +33,23 @@ class APIRequest {
gen() {
const API = `${this.client.options.http.host}/api/v${this.client.options.http.version}`;
if (this.options.query) {
const queryString = (querystring.stringify(this.options.query).match(/[^=&?]+=[^=&?]+/g) || []).join('&');
this.path += `?${queryString}`;
}
const request = snekfetch[this.method](`${API}${this.path}`);
if (this.auth) request.set('Authorization', this.getAuth());
if (this.options.auth !== false) request.set('Authorization', this.getAuth());
if (this.options.reason) request.set('X-Audit-Log-Reason', this.options.reason);
if (!this.rest.client.browser) request.set('User-Agent', this.rest.userAgentManager.userAgent);
if (this.files) {
for (const file of this.files) if (file && file.file) request.attach(file.name, file.file, file.name);
if (typeof this.data !== 'undefined') request.attach('payload_json', JSON.stringify(this.data));
} else if (this.data) {
request.send(this.data);
if (this.options.files) {
for (const file of this.options.files) if (file && file.file) request.attach(file.name, file.file, file.name);
if (typeof this.options.data !== 'undefined') request.attach('payload_json', JSON.stringify(this.options.data));
} else if (typeof this.options.data !== 'undefined') {
request.send(this.options.data);
}
return request;
}

View File

@@ -0,0 +1,39 @@
const util = require('util');
const methods = ['get', 'post', 'delete', 'patch', 'put'];
// Paramable exists so we don't return a function unless we actually need one #savingmemory
const paramable = [
'channels', 'users', 'guilds', 'members',
'bans', 'emojis', 'pins', 'permissions',
'reactions', 'webhooks', 'messages',
'notes', 'roles', 'applications',
'invites',
];
const reflectors = ['toString', 'valueOf', 'inspect', Symbol.toPrimitive, util.inspect.custom];
module.exports = restManager => {
const handler = {
get(list, name) {
if (reflectors.includes(name)) return () => list.join('/');
if (paramable.includes(name)) {
function toReturn(...args) { // eslint-disable-line no-inner-declarations
list = list.concat(name);
for (const arg of args) {
if (arg !== null && typeof arg !== 'undefined') list = list.concat(arg);
}
return new Proxy(list, handler);
}
const directJoin = () => `${list.join('/')}/${name}`;
for (const r of reflectors) toReturn[r] = directJoin;
for (const method of methods) {
toReturn[method] = options => restManager.request(method, `${list.join('/')}/${name}`, options);
}
return toReturn;
}
if (methods.includes(name)) return options => restManager.request(name, list.join('/'), options);
return new Proxy(list.concat(name), handler);
},
};
return new Proxy([''], handler);
};

View File

@@ -29,8 +29,8 @@ class DiscordAPIError extends Error {
if (obj[k]._errors) {
messages.push(`${newKey}: ${obj[k]._errors.map(e => e.message).join(' ')}`);
} else if (obj[k].code && obj[k].message) {
messages.push(`${obj[k].code}: ${obj[k].message}`);
} else if (obj[k].code || obj[k].message) {
messages.push(`${obj[k].code ? `${obj[k].code}: ` : ''}${obj[k].message}`.trim());
} else {
messages = messages.concat(this.flattenErrors(obj[k], newKey));
}

View File

@@ -1,8 +1,8 @@
const UserAgentManager = require('./UserAgentManager');
const RESTMethods = require('./RESTMethods');
const SequentialRequestHandler = require('./RequestHandlers/Sequential');
const BurstRequestHandler = require('./RequestHandlers/Burst');
const APIRequest = require('./APIRequest');
const mountApi = require('./APIRouter');
const Constants = require('../../util/Constants');
class RESTManager {
@@ -10,9 +10,10 @@ class RESTManager {
this.client = client;
this.handlers = {};
this.userAgentManager = new UserAgentManager(this);
this.methods = new RESTMethods(this);
this.rateLimitedEndpoints = {};
this.globallyRateLimited = false;
this.api = mountApi(this);
}
destroy() {
@@ -42,8 +43,8 @@ class RESTManager {
}
}
makeRequest(method, url, auth, data, file) {
const apiRequest = new APIRequest(this, method, url, auth, data, file);
request(method, url, options = {}) {
const apiRequest = new APIRequest(this, method, url, options);
if (!this.handlers[apiRequest.route]) {
const RequestHandlerType = this.getRequestHandler();
this.handlers[apiRequest.route] = new RequestHandlerType(this, apiRequest.route);

View File

@@ -1,903 +0,0 @@
const querystring = require('querystring');
const long = require('long');
const Permissions = require('../../util/Permissions');
const Constants = require('../../util/Constants');
const Endpoints = Constants.Endpoints;
const Collection = require('../../util/Collection');
const Snowflake = require('../../util/Snowflake');
const Util = require('../../util/Util');
const User = require('../../structures/User');
const GuildMember = require('../../structures/GuildMember');
const Message = require('../../structures/Message');
const Role = require('../../structures/Role');
const Invite = require('../../structures/Invite');
const Webhook = require('../../structures/Webhook');
const UserProfile = require('../../structures/UserProfile');
const OAuth2Application = require('../../structures/OAuth2Application');
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) {
this.rest = restManager;
this.client = restManager.client;
this._ackToken = null;
}
login(token = this.client.token) {
return new Promise((resolve, reject) => {
if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN);
token = token.replace(/^Bot\s*/i, '');
this.client.manager.connectToWebSocket(token, resolve, reject);
});
}
logout() {
return this.rest.makeRequest('post', Endpoints.logout, true, {});
}
getGateway(bot = false) {
return this.rest.makeRequest('get', bot ? Endpoints.gateway.bot : Endpoints.gateway, true);
}
fetchVoiceRegions(guildID) {
let endpoint;
if (guildID) endpoint = Endpoints.Guild(guildID).voiceRegions;
else endpoint = Endpoints.voiceRegions;
return this.rest.makeRequest('get', endpoint, true).then(res => {
const regions = new Collection();
for (const region of res) regions.set(region.id, new VoiceRegion(region));
return regions;
});
}
sendMessage(channel, content, { tts, nonce, embed, disableEveryone, split, code, reply } = {}, files = null) {
return new Promise((resolve, reject) => { // eslint-disable-line complexity
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
// The nonce has to be a uint64 :<
if (typeof nonce !== 'undefined') {
nonce = parseInt(nonce);
if (isNaN(nonce) || nonce < 0) throw new RangeError('Message nonce must fit in an unsigned 64-bit integer.');
}
if (content) {
if (split && typeof split !== 'object') split = {};
// Wrap everything in a code block
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true);
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
if (split) {
split.prepend = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n`;
split.append = '\n```';
}
}
// Add zero-width spaces to @everyone/@here
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
}
// Add the reply prefix
if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
const id = this.client.resolver.resolveUserID(reply);
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
content = `${mention}${content ? `, ${content}` : ''}`;
if (split) split.prepend = `${mention}, ${split.prepend || ''}`;
}
// Split the content
if (split) content = Util.splitMessage(content, split);
} else if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
const id = this.client.resolver.resolveUserID(reply);
content = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
}
const send = chan => {
if (content instanceof Array) {
const messages = [];
(function sendChunk(list, index) {
const options = index === list.length ? { tts, embed } : { tts };
chan.send(list[index], options, index === list.length ? files : null).then(message => {
messages.push(message);
if (index >= list.length - 1) return resolve(messages);
return sendChunk(list, ++index);
});
}(content, 0));
} else {
this.rest.makeRequest('post', Endpoints.Channel(chan).messages, true, {
content, tts, nonce, embed,
}, files).then(data => resolve(this.client.actions.MessageCreate.handle(data).message), reject);
}
};
if (channel instanceof User || channel instanceof GuildMember) this.createDM(channel).then(send, reject);
else send(channel);
});
}
updateMessage(message, content, { embed, code, reply } = {}) {
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
// Wrap everything in a code block
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true);
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
}
// Add the reply prefix
if (reply && message.channel.type !== 'dm') {
const id = this.client.resolver.resolveUserID(reply);
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
content = `${mention}${content ? `, ${content}` : ''}`;
}
return this.rest.makeRequest('patch', Endpoints.Message(message), true, {
content, embed,
}).then(data => this.client.actions.MessageUpdate.handle(data).updated);
}
deleteMessage(message) {
return this.rest.makeRequest('delete', Endpoints.Message(message), true)
.then(() =>
this.client.actions.MessageDelete.handle({
id: message.id,
channel_id: message.channel.id,
}).message
);
}
ackMessage(message) {
return this.rest.makeRequest('post', Endpoints.Message(message).ack, true, { token: this._ackToken }).then(res => {
if (res.token) this._ackToken = res.token;
return message;
});
}
ackTextChannel(channel) {
return this.rest.makeRequest('post', Endpoints.Channel(channel).Message(channel.lastMessageID).ack, true, {
token: this._ackToken,
}).then(res => {
if (res.token) this._ackToken = res.token;
return channel;
});
}
ackGuild(guild) {
return this.rest.makeRequest('post', Endpoints.Guild(guild).ack, true).then(() => guild);
}
bulkDeleteMessages(channel, messages, filterOld) {
if (filterOld) {
messages = messages.filter(id =>
Date.now() - Snowflake.deconstruct(id).date.getTime() < 1209600000
);
}
return this.rest.makeRequest('post', Endpoints.Channel(channel).messages.bulkDelete, true, {
messages,
}).then(() =>
this.client.actions.MessageDeleteBulk.handle({
channel_id: channel.id,
ids: messages,
}).messages
);
}
search(target, options) {
if (typeof options === 'string') options = { content: options };
if (options.before) {
if (!(options.before instanceof Date)) options.before = new Date(options.before);
options.maxID = long.fromNumber(options.before.getTime() - 14200704e5).shiftLeft(22).toString();
}
if (options.after) {
if (!(options.after instanceof Date)) options.after = new Date(options.after);
options.minID = long.fromNumber(options.after.getTime() - 14200704e5).shiftLeft(22).toString();
}
if (options.during) {
if (!(options.during instanceof Date)) options.during = new Date(options.during);
const t = options.during.getTime() - 14200704e5;
options.minID = long.fromNumber(t).shiftLeft(22).toString();
options.maxID = long.fromNumber(t + 86400000).shiftLeft(22).toString();
}
if (options.channel) options.channel = this.client.resolver.resolveChannelID(options.channel);
if (options.author) options.author = this.client.resolver.resolveUserID(options.author);
if (options.mentions) options.mentions = this.client.resolver.resolveUserID(options.options.mentions);
options = {
content: options.content,
max_id: options.maxID,
min_id: options.minID,
has: options.has,
channel_id: options.channel,
author_id: options.author,
author_type: options.authorType,
context_size: options.contextSize,
sort_by: options.sortBy,
sort_order: options.sortOrder,
limit: options.limit,
offset: options.offset,
mentions: options.mentions,
mentions_everyone: options.mentionsEveryone,
link_hostname: options.linkHostname,
embed_provider: options.embedProvider,
embed_type: options.embedType,
attachment_filename: options.attachmentFilename,
attachment_extension: options.attachmentExtension,
};
for (const key in options) if (options[key] === undefined) delete options[key];
const queryString = (querystring.stringify(options).match(/[^=&?]+=[^=&?]+/g) || []).join('&');
let endpoint;
if (target instanceof Channel) {
endpoint = Endpoints.Channel(target).search;
} else if (target instanceof Guild) {
endpoint = Endpoints.Guild(target).search;
} else {
throw new TypeError('Target must be a TextChannel, DMChannel, GroupDMChannel, or Guild.');
}
return this.rest.makeRequest('get', `${endpoint}?${queryString}`, true).then(body => {
const messages = body.messages.map(x =>
x.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client))
);
return {
totalResults: body.total_results,
messages,
};
});
}
createChannel(guild, channelName, channelType, overwrites) {
if (overwrites instanceof Collection) overwrites = overwrites.array();
return this.rest.makeRequest('post', Endpoints.Guild(guild).channels, true, {
name: channelName,
type: channelType,
permission_overwrites: overwrites,
}).then(data => this.client.actions.ChannelCreate.handle(data).channel);
}
createDM(recipient) {
const dmChannel = this.getExistingDM(recipient);
if (dmChannel) return Promise.resolve(dmChannel);
return this.rest.makeRequest('post', Endpoints.User(this.client.user).channels, true, {
recipient_id: recipient.id,
}).then(data => this.client.actions.ChannelCreate.handle(data).channel);
}
createGroupDM(options) {
const data = this.client.user.bot ?
{ access_tokens: options.accessTokens, nicks: options.nicks } :
{ recipients: options.recipients };
return this.rest.makeRequest('post', Endpoints.User('@me').channels, true, data)
.then(res => new GroupDMChannel(this.client, res));
}
addUserToGroupDM(channel, options) {
const data = this.client.user.bot ?
{ nick: options.nick, access_token: options.accessToken } :
{ recipient: options.id };
return this.rest.makeRequest('put', Endpoints.Channel(channel).Recipient(options.id), true, data)
.then(() => channel);
}
getExistingDM(recipient) {
return this.client.channels.find(channel =>
channel.recipient && channel.recipient.id === recipient.id
);
}
deleteChannel(channel) {
if (channel instanceof User || channel instanceof GuildMember) channel = this.getExistingDM(channel);
if (!channel) return Promise.reject(new Error('No channel to delete.'));
return this.rest.makeRequest('delete', Endpoints.Channel(channel), true).then(data => {
data.id = channel.id;
return this.client.actions.ChannelDelete.handle(data).channel;
});
}
updateChannel(channel, _data) {
const data = {};
data.name = (_data.name || channel.name).trim();
data.topic = _data.topic || channel.topic;
data.position = _data.position || channel.position;
data.bitrate = _data.bitrate || channel.bitrate;
data.user_limit = _data.userLimit || channel.userLimit;
return this.rest.makeRequest('patch', Endpoints.Channel(channel), true, data).then(newData =>
this.client.actions.ChannelUpdate.handle(newData).updated
);
}
leaveGuild(guild) {
if (guild.ownerID === this.client.user.id) return Promise.reject(new Error('Guild is owned by the client.'));
return this.rest.makeRequest('delete', Endpoints.User('@me').Guild(guild.id), true).then(() =>
this.client.actions.GuildDelete.handle({ id: guild.id }).guild
);
}
createGuild(options) {
options.icon = this.client.resolver.resolveBase64(options.icon) || null;
options.region = options.region || 'us-central';
return new Promise((resolve, reject) => {
this.rest.makeRequest('post', Endpoints.guilds, true, options).then(data => {
if (this.client.guilds.has(data.id)) return resolve(this.client.guilds.get(data.id));
const handleGuild = guild => {
if (guild.id === data.id) {
this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild);
this.client.clearTimeout(timeout);
resolve(guild);
}
};
this.client.on(Constants.Events.GUILD_CREATE, handleGuild);
const timeout = this.client.setTimeout(() => {
this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild);
reject(new Error('Took too long to receive guild data.'));
}, 10000);
return undefined;
}, reject);
});
}
// Untested but probably will work
deleteGuild(guild) {
return this.rest.makeRequest('delete', Endpoints.Guild(guild), true).then(() =>
this.client.actions.GuildDelete.handle({ id: guild.id }).guild
);
}
getUser(userID, cache) {
return this.rest.makeRequest('get', Endpoints.User(userID), true).then(data => {
if (cache) return this.client.actions.UserGet.handle(data).user;
else return new User(this.client, data);
});
}
updateCurrentUser(_data, password) {
const user = this.client.user;
const data = {};
data.username = _data.username || user.username;
data.avatar = this.client.resolver.resolveBase64(_data.avatar) || user.avatar;
if (!user.bot) {
data.email = _data.email || user.email;
data.password = password;
if (_data.new_password) data.new_password = _data.newPassword;
}
return this.rest.makeRequest('patch', Endpoints.User('@me'), true, data).then(newData =>
this.client.actions.UserUpdate.handle(newData).updated
);
}
updateGuild(guild, _data) {
const data = {};
if (_data.name) data.name = _data.name;
if (_data.region) data.region = _data.region;
if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel);
if (_data.afkChannel) data.afk_channel_id = this.client.resolver.resolveChannel(_data.afkChannel).id;
if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout);
if (_data.icon) data.icon = this.client.resolver.resolveBase64(_data.icon);
if (_data.owner) data.owner_id = this.client.resolver.resolveUser(_data.owner).id;
if (_data.splash) data.splash = this.client.resolver.resolveBase64(_data.splash);
return this.rest.makeRequest('patch', Endpoints.Guild(guild), true, data).then(newData =>
this.client.actions.GuildUpdate.handle(newData).updated
);
}
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,
}).member
);
}
createGuildRole(guild, data) {
if (data.color) data.color = this.client.resolver.resolveColor(data.color);
if (data.permissions) data.permissions = Permissions.resolve(data.permissions);
return this.rest.makeRequest('post', Endpoints.Guild(guild).roles, true, data).then(role =>
this.client.actions.GuildRoleCreate.handle({
guild_id: guild.id,
role,
}).role
);
}
deleteGuildRole(role) {
return this.rest.makeRequest('delete', Endpoints.Guild(role.guild).Role(role.id), true).then(() =>
this.client.actions.GuildRoleDelete.handle({
guild_id: role.guild.id,
role_id: role.id,
}).role
);
}
setChannelOverwrite(channel, payload) {
return this.rest.makeRequest('put', `${Endpoints.Channel(channel).permissions}/${payload.id}`, true, payload);
}
deletePermissionOverwrites(overwrite) {
return this.rest.makeRequest(
'delete', `${Endpoints.Channel(overwrite.channel).permissions}/${overwrite.id}`, true
).then(() => overwrite);
}
getChannelMessages(channel, payload = {}) {
const params = [];
if (payload.limit) params.push(`limit=${payload.limit}`);
if (payload.around) params.push(`around=${payload.around}`);
else if (payload.before) params.push(`before=${payload.before}`);
else if (payload.after) params.push(`after=${payload.after}`);
let endpoint = Endpoints.Channel(channel).messages;
if (params.length > 0) endpoint += `?${params.join('&')}`;
return this.rest.makeRequest('get', endpoint, true);
}
getChannelMessage(channel, messageID) {
const msg = channel.messages.get(messageID);
if (msg) return Promise.resolve(msg);
return this.rest.makeRequest('get', Endpoints.Channel(channel).Message(messageID), true);
}
putGuildMember(guild, user, options) {
options.access_token = options.accessToken;
if (options.roles) {
const roles = options.roles;
if (roles instanceof Collection || (roles instanceof Array && roles[0] instanceof Role)) {
options.roles = roles.map(role => role.id);
}
}
return this.rest.makeRequest('put', Endpoints.Guild(guild).Member(user.id), true, options)
.then(data => this.client.actions.GuildMemberGet.handle(guild, data).member);
}
getGuildMember(guild, user, cache) {
return this.rest.makeRequest('get', Endpoints.Guild(guild).Member(user.id), true).then(data => {
if (cache) return this.client.actions.GuildMemberGet.handle(guild, data).member;
else return new GuildMember(guild, data);
});
}
updateGuildMember(member, data) {
if (data.channel) {
data.channel_id = this.client.resolver.resolveChannel(data.channel).id;
data.channel = null;
}
if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role);
let endpoint = Endpoints.Member(member);
// Fix your endpoints, discord ;-;
if (member.id === this.client.user.id) {
const keys = Object.keys(data);
if (keys.length === 1 && keys[0] === 'nick') {
endpoint = Endpoints.Member(member).nickname;
}
}
return this.rest.makeRequest('patch', endpoint, true, data).then(newData =>
member.guild._updateMember(member, newData).mem
);
}
addMemberRole(member, role) {
return new Promise((resolve, reject) => {
if (member._roles.includes(role.id)) return resolve(member);
const listener = (oldMember, newMember) => {
if (!oldMember._roles.includes(role.id) && newMember._roles.includes(role.id)) {
this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener);
resolve(newMember);
}
};
this.client.on(Constants.Events.GUILD_MEMBER_UPDATE, listener);
const timeout = this.client.setTimeout(() =>
this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener), 10e3);
return this.rest.makeRequest('put', Endpoints.Member(member).Role(role.id), true).catch(err => {
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
this.client.clearTimeout(timeout);
reject(err);
});
});
}
removeMemberRole(member, role) {
return new Promise((resolve, reject) => {
if (!member._roles.includes(role.id)) return resolve(member);
const listener = (oldMember, newMember) => {
if (oldMember._roles.includes(role.id) && !newMember._roles.includes(role.id)) {
this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener);
resolve(newMember);
}
};
this.client.on(Constants.Events.GUILD_MEMBER_UPDATE, listener);
const timeout = this.client.setTimeout(() =>
this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener), 10e3);
return this.rest.makeRequest('delete', Endpoints.Member(member).Role(role.id), true).catch(err => {
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
this.client.clearTimeout(timeout);
reject(err);
});
});
}
sendTyping(channelID) {
return this.rest.makeRequest('post', Endpoints.Channel(channelID).typing, true);
}
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.'));
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) {
member = this.client.resolver.resolveGuildMember(guild, user);
return member || user;
}
return id;
});
}
unbanGuildMember(guild, member) {
return new Promise((resolve, reject) => {
const id = this.client.resolver.resolveUserID(member);
if (!id) throw new Error('Couldn\'t resolve the user ID to unban.');
const listener = (eGuild, eUser) => {
if (eGuild.id === guild.id && eUser.id === id) {
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
this.client.clearTimeout(timeout);
resolve(eUser);
}
};
this.client.on(Constants.Events.GUILD_BAN_REMOVE, listener);
const timeout = this.client.setTimeout(() => {
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
reject(new Error('Took too long to receive the ban remove event.'));
}, 10000);
this.rest.makeRequest('delete', `${Endpoints.Guild(guild).bans}/${id}`, true).catch(err => {
this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
this.client.clearTimeout(timeout);
reject(err);
});
});
}
getGuildBans(guild) {
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) {
const data = {};
data.name = _data.name || role.name;
data.position = typeof _data.position !== 'undefined' ? _data.position : role.position;
data.color = this.client.resolver.resolveColor(_data.color || role.color);
data.hoist = typeof _data.hoist !== 'undefined' ? _data.hoist : role.hoist;
data.mentionable = typeof _data.mentionable !== 'undefined' ? _data.mentionable : role.mentionable;
if (_data.permissions) data.permissions = Permissions.resolve(_data.permissions);
else data.permissions = role.permissions;
return this.rest.makeRequest('patch', Endpoints.Guild(role.guild).Role(role.id), true, data).then(_role =>
this.client.actions.GuildRoleUpdate.handle({
role: _role,
guild_id: role.guild.id,
}).updated
);
}
pinMessage(message) {
return this.rest.makeRequest('put', Endpoints.Channel(message.channel).Pin(message.id), true)
.then(() => message);
}
unpinMessage(message) {
return this.rest.makeRequest('delete', Endpoints.Channel(message.channel).Pin(message.id), true)
.then(() => message);
}
getChannelPinnedMessages(channel) {
return this.rest.makeRequest('get', Endpoints.Channel(channel).pins, true);
}
createChannelInvite(channel, options) {
const payload = {};
payload.temporary = options.temporary;
payload.max_age = options.maxAge;
payload.max_uses = options.maxUses;
return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload)
.then(invite => new Invite(this.client, invite));
}
deleteInvite(invite) {
return this.rest.makeRequest('delete', Endpoints.Invite(invite.code), true).then(() => invite);
}
getInvite(code) {
return this.rest.makeRequest('get', Endpoints.Invite(code), true).then(invite =>
new Invite(this.client, invite)
);
}
getGuildInvites(guild) {
return this.rest.makeRequest('get', Endpoints.Guild(guild).invites, true).then(inviteItems => {
const invites = new Collection();
for (const inviteItem of inviteItems) {
const invite = new Invite(this.client, inviteItem);
invites.set(invite.code, invite);
}
return invites;
});
}
pruneGuildMembers(guild, days, dry) {
return this.rest.makeRequest(dry ? 'get' : 'post', `${Endpoints.Guild(guild).prune}?days=${days}`, true)
.then(data => data.pruned);
}
createEmoji(guild, image, name, roles) {
const data = { image, name };
if (roles) data.roles = roles.map(r => r.id ? r.id : r);
return this.rest.makeRequest('post', Endpoints.Guild(guild).emojis, true, data)
.then(emoji => this.client.actions.GuildEmojiCreate.handle(guild, emoji).emoji);
}
updateEmoji(emoji, _data) {
const data = {};
if (_data.name) data.name = _data.name;
if (_data.roles) data.roles = _data.roles.map(r => r.id ? r.id : r);
return this.rest.makeRequest('patch', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, data)
.then(newEmoji => this.client.actions.GuildEmojiUpdate.handle(emoji, newEmoji).emoji);
}
deleteEmoji(emoji) {
return this.rest.makeRequest('delete', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true)
.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)
);
}
getGuildWebhooks(guild) {
return this.rest.makeRequest('get', Endpoints.Guild(guild).webhooks, true).then(data => {
const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks;
});
}
getChannelWebhooks(channel) {
return this.rest.makeRequest('get', Endpoints.Channel(channel).webhooks, true).then(data => {
const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks;
});
}
createWebhook(channel, name, avatar) {
return this.rest.makeRequest('post', Endpoints.Channel(channel).webhooks, true, { name, avatar })
.then(data => new Webhook(this.client, data));
}
editWebhook(webhook, name, avatar) {
return this.rest.makeRequest('patch', Endpoints.Webhook(webhook.id, webhook.token), false, {
name,
avatar,
}).then(data => {
webhook.name = data.name;
webhook.avatar = data.avatar;
return webhook;
});
}
deleteWebhook(webhook) {
return this.rest.makeRequest('delete', Endpoints.Webhook(webhook.id, webhook.token), false);
}
sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds, username } = {}, file = null) {
username = username || webhook.name;
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
if (content) {
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
}
}
return this.rest.makeRequest('post', `${Endpoints.Webhook(webhook.id, webhook.token)}?wait=true`, false, {
username,
avatar_url: avatarURL,
content,
tts,
embeds,
}, file);
}
sendSlackWebhookMessage(webhook, body) {
return this.rest.makeRequest(
'post', `${Endpoints.Webhook(webhook.id, webhook.token)}/slack?wait=true`, false, body
);
}
fetchUserProfile(user) {
return this.rest.makeRequest('get', Endpoints.User(user).profile, true).then(data =>
new UserProfile(user, data)
);
}
fetchMentions(options) {
if (options.guild instanceof Guild) options.guild = options.guild.id;
Util.mergeDefault({ limit: 25, roles: true, everyone: true, guild: null }, options);
return this.rest.makeRequest(
'get', Endpoints.User('@me').Mentions(options.limit, options.roles, options.everyone, options.guild), true
).then(data => data.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client)));
}
addFriend(user) {
return this.rest.makeRequest('post', Endpoints.User('@me'), true, {
username: user.username,
discriminator: user.discriminator,
}).then(() => user);
}
removeFriend(user) {
return this.rest.makeRequest('delete', Endpoints.User('@me').Relationship(user.id), true)
.then(() => user);
}
blockUser(user) {
return this.rest.makeRequest('put', Endpoints.User('@me').Relationship(user.id), true, { type: 2 })
.then(() => user);
}
unblockUser(user) {
return this.rest.makeRequest('delete', Endpoints.User('@me').Relationship(user.id), true)
.then(() => user);
}
updateChannelPositions(guildID, channels) {
const data = new Array(channels.length);
for (let i = 0; i < channels.length; i++) {
data[i] = {
id: this.client.resolver.resolveChannelID(channels[i].channel),
position: channels[i].position,
};
}
return this.rest.makeRequest('patch', Endpoints.Guild(guildID).channels, true, data).then(() =>
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: guildID,
channels,
}).guild
);
}
setRolePositions(guildID, roles) {
return this.rest.makeRequest('patch', Endpoints.Guild(guildID).roles, true, roles).then(() =>
this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: guildID,
roles,
}).guild
);
}
setChannelPositions(guildID, channels) {
return this.rest.makeRequest('patch', Endpoints.Guild(guildID).channels, true, channels).then(() =>
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: guildID,
channels,
}).guild
);
}
addMessageReaction(message, emoji) {
return this.rest.makeRequest(
'put', Endpoints.Message(message).Reaction(emoji).User('@me'), true
).then(() =>
message._addReaction(Util.parseEmoji(emoji), message.client.user)
);
}
removeMessageReaction(message, emoji, userID) {
const endpoint = Endpoints.Message(message).Reaction(emoji).User(userID === this.client.user.id ? '@me' : userID);
return this.rest.makeRequest('delete', endpoint, true).then(() =>
this.client.actions.MessageReactionRemove.handle({
user_id: userID,
message_id: message.id,
emoji: Util.parseEmoji(emoji),
channel_id: message.channel.id,
}).reaction
);
}
removeMessageReactions(message) {
return this.rest.makeRequest('delete', Endpoints.Message(message).reactions, true)
.then(() => message);
}
getMessageReactionUsers(message, emoji, limit = 100) {
return this.rest.makeRequest('get', Endpoints.Message(message).Reaction(emoji, limit), true);
}
getApplication(id) {
return this.rest.makeRequest('get', Endpoints.OAUTH2.Application(id), true).then(app =>
new OAuth2Application(this.client, app)
);
}
resetApplication(id) {
return this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).reset, true)
.then(app => new OAuth2Application(this.client, app));
}
setNote(user, note) {
return this.rest.makeRequest('put', Endpoints.User(user).note, true, { note }).then(() => user);
}
acceptInvite(code) {
if (code.id) code = code.id;
return new Promise((resolve, reject) =>
this.rest.makeRequest('post', Endpoints.Invite(code), true).then(res => {
const handler = guild => {
if (guild.id === res.id) {
resolve(guild);
this.client.removeListener(Constants.Events.GUILD_CREATE, handler);
}
};
this.client.on(Constants.Events.GUILD_CREATE, handler);
this.client.setTimeout(() => {
this.client.removeListener(Constants.Events.GUILD_CREATE, handler);
reject(new Error('Accepting invite timed out'));
}, 120e3);
})
);
}
patchUserSettings(data) {
return this.rest.makeRequest('patch', Constants.Endpoints.User('@me').settings, true, data);
}
}
module.exports = RESTMethods;

View File

@@ -53,7 +53,7 @@ class Channel {
}
/**
* Deletes the channel.
* Deletes this channel.
* @returns {Promise<Channel>}
* @example
* // Delete the channel
@@ -62,7 +62,7 @@ class Channel {
* .catch(console.error); // Log error
*/
delete() {
return this.client.rest.methods.deleteChannel(this);
return this.client.api.channels(this.id).delete().then(() => this);
}
}

View File

@@ -2,6 +2,10 @@ const User = require('./User');
const Collection = require('../util/Collection');
const ClientUserSettings = require('./ClientUserSettings');
const Constants = require('../util/Constants');
const Util = require('../util/Util');
const Guild = require('./Guild');
const Message = require('./Message');
const GroupDMChannel = require('./GroupDMChannel');
/**
* Represents the logged in client's Discord user.
@@ -75,8 +79,18 @@ class ClientUser extends User {
if (data.user_settings) this.settings = new ClientUserSettings(this, data.user_settings);
}
edit(data) {
return this.client.rest.methods.updateCurrentUser(data);
edit(data, password) {
const _data = {};
_data.username = data.username || this.username;
_data.avatar = this.client.resolver.resolveBase64(data.avatar) || this.avatar;
if (!this.bot) {
_data.email = data.email || this.email;
_data.password = password;
if (data.new_password) _data.new_password = data.newPassword;
}
return this.client.api.users('@me').patch({ data })
.then(newData => this.client.actions.UserUpdate.handle(newData).updated);
}
/**
@@ -93,7 +107,7 @@ class ClientUser extends User {
* .catch(console.error);
*/
setUsername(username, password) {
return this.client.rest.methods.updateCurrentUser({ username }, password);
return this.edit({ username }, password);
}
/**
@@ -109,7 +123,7 @@ class ClientUser extends User {
* .catch(console.error);
*/
setEmail(email, password) {
return this.client.rest.methods.updateCurrentUser({ email }, password);
return this.edit({ email }, password);
}
/**
@@ -125,7 +139,7 @@ class ClientUser extends User {
* .catch(console.error);
*/
setPassword(newPassword, oldPassword) {
return this.client.rest.methods.updateCurrentUser({ password: newPassword }, oldPassword);
return this.edit({ password: newPassword }, oldPassword);
}
/**
@@ -140,11 +154,10 @@ class ClientUser extends User {
*/
setAvatar(avatar) {
if (typeof avatar === 'string' && avatar.startsWith('data:')) {
return this.client.rest.methods.updateCurrentUser({ avatar });
return this.edit({ avatar });
} else {
return this.client.resolver.resolveBuffer(avatar).then(data =>
this.client.rest.methods.updateCurrentUser({ avatar: data })
);
return this.client.resolver.resolveBuffer(avatar)
.then(data => this.edit({ avatar: this.client.resolver.resolveBase64(data) || null }));
}
}
@@ -266,58 +279,42 @@ class ClientUser extends User {
* @returns {Promise<Message[]>}
*/
fetchMentions(options = {}) {
return this.client.rest.methods.fetchMentions(options);
}
if (options.guild instanceof Guild) options.guild = options.guild.id;
Util.mergeDefault({ limit: 25, roles: true, everyone: true, guild: null }, options);
/**
* Send a friend request.
* <warn>This is only available when using a user account.</warn>
* @param {UserResolvable} user The user to send the friend request to
* @returns {Promise<User>} The user the friend request was sent to
*/
addFriend(user) {
user = this.client.resolver.resolveUser(user);
return this.client.rest.methods.addFriend(user);
}
/**
* Remove a friend.
* <warn>This is only available when using a user account.</warn>
* @param {UserResolvable} user The user to remove from your friends
* @returns {Promise<User>} The user that was removed
*/
removeFriend(user) {
user = this.client.resolver.resolveUser(user);
return this.client.rest.methods.removeFriend(user);
return this.client.api.users('@me').mentions.get({ query: options })
.then(data => data.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client)));
}
/**
* Creates a guild.
* <warn>This is only available when using a user account.</warn>
* @param {string} name The name of the guild
* @param {string} region The region for the server
* @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild
* @param {Object} [options] Options for the creating
* @param {string} [options.region] The region for the server, defaults to the closest one available
* @param {BufferResolvable|Base64Resolvable} [options.icon=null] The icon for the guild
* @returns {Promise<Guild>} The guild that was created
*/
createGuild(name, region, icon = null) {
if (!icon) return this.client.rest.methods.createGuild({ name, icon, region });
if (typeof icon === 'string' && icon.startsWith('data:')) {
return this.client.rest.methods.createGuild({ name, icon, region });
createGuild(name, { region, icon = null } = {}) {
if (!icon || (typeof icon === 'string' && icon.startsWith('data:'))) {
return this.client.api.guilds.post({ data: { name, region, icon } })
.then(data => this.client.dataManager.newGuild(data));
} else {
return this.client.resolver.resolveBuffer(icon).then(data =>
this.client.rest.methods.createGuild({ name, icon: data, region })
);
return this.client.resolver.resolveBuffer(icon)
.then(data => this.createGuild(name, region, this.client.resolver.resolveBase64(data) || null));
}
}
/**
* An object containing either a user or access token, and an optional nickname.
* @typedef {Object} GroupDMRecipientOptions
* @property {UserResolvable|Snowflake} [user] User to add to the Group DM
* @property {UserResolvable} [user] User to add to the Group DM
* (only available if a user is creating the DM)
* @property {string} [accessToken] Access token to use to add a user to the Group DM
* (only available if a bot is creating the DM)
* @property {string} [nick] Permanent nickname (only available if a bot is creating the DM)
* @property {string} [id] If no user resolveable is provided and you want to assign nicknames
* you must provide user ids instead
*/
/**
@@ -326,21 +323,15 @@ class ClientUser extends User {
* @returns {Promise<GroupDMChannel>}
*/
createGroupDM(recipients) {
return this.client.rest.methods.createGroupDM({
recipients: recipients.map(u => this.client.resolver.resolveUserID(u.user)),
accessTokens: recipients.map(u => u.accessToken),
nicks: recipients.map(u => u.nick),
});
}
/**
* Accepts an invite to join a guild.
* <warn>This is only available when using a user account.</warn>
* @param {Invite|string} invite Invite or code to accept
* @returns {Promise<Guild>} Joined guild
*/
acceptInvite(invite) {
return this.client.rest.methods.acceptInvite(invite);
const data = this.bot ? {
access_tokens: recipients.map(u => u.accessToken),
nicks: recipients.reduce((o, r) => {
if (r.nick) o[r.user ? r.user.id : r.id] = r.nick;
return o;
}, {}),
} : { recipients: recipients.map(u => this.client.resolver.resolveUserID(u)) };
return this.client.api.users('@me').channels.post({ data })
.then(res => new GroupDMChannel(this.client, res));
}
}

View File

@@ -33,7 +33,7 @@ class ClientUserSettings {
* @returns {Promise<Object>}
*/
update(name, value) {
return this.user.client.rest.methods.patchUserSettings({ [name]: value });
return this.user.client.api.users('@me').settings.patch({ data: { [name]: value } });
}
/**

View File

@@ -112,6 +112,7 @@ class Emoji {
/**
* Edits the emoji.
* @param {EmojiEditData} data The new data for the emoji
* @param {string} [reason] Reason for editing this emoji
* @returns {Promise<Emoji>}
* @example
* // Edit a emoji
@@ -119,8 +120,13 @@ class Emoji {
* .then(e => console.log(`Edited emoji ${e}`))
* .catch(console.error);
*/
edit(data) {
return this.client.rest.methods.updateEmoji(this, data);
edit(data, reason) {
return this.client.api.guilds(this.guild.id).emojis(this.id)
.patch({ data: {
name: data.name,
roles: data.roles ? data.roles.map(r => r.id ? r.id : r) : [],
}, reason })
.then(() => this);
}
/**

View File

@@ -126,16 +126,17 @@ class GroupDMChannel extends Channel {
/**
* Add a user to the DM
* @param {UserResolvable|string} accessTokenOrID Access token or user resolvable
* @param {UserResolvable|string} accessTokenOrUser Access token or user resolvable
* @param {string} [nick] Permanent nickname to give the user (only available if a bot is creating the DM)
* @returns {Promise<GroupDMChannel>}
*/
addUser(accessTokenOrID, nick) {
return this.client.rest.methods.addUserToGroupDM(this, {
nick,
id: this.client.resolver.resolveUserID(accessTokenOrID),
accessToken: accessTokenOrID,
});
addUser(accessTokenOrUser, nick) {
const id = this.client.resolver.resolveUserID(accessTokenOrUser);
const data = this.client.user.bot ?
{ nick, access_token: accessTokenOrUser } :
{ recipient: id };
return this.client.api.channels(this.id).recipients(id).put({ data })
.then(() => this);
}
/**

View File

@@ -2,12 +2,18 @@ const Long = require('long');
const User = require('./User');
const Role = require('./Role');
const Emoji = require('./Emoji');
const Invite = require('./Invite');
const GuildAuditLogs = require('./GuildAuditLogs');
const Webhook = require('./Webhook');
const Presence = require('./Presence').Presence;
const GuildMember = require('./GuildMember');
const VoiceRegion = require('./VoiceRegion');
const Constants = require('../util/Constants');
const Collection = require('../util/Collection');
const Util = require('../util/Util');
const Snowflake = require('../util/Snowflake');
const Permissions = require('../util/Permissions');
const Shared = require('./shared');
/**
* Represents a guild (or a server) on Discord.
@@ -264,7 +270,7 @@ class Guild {
size = format;
format = 'default';
}
return Constants.Endpoints.Guild(this).Icon(this.client.options.http.cdn, this.icon, format, size);
return Constants.Endpoints.CDN(this.client.options.http.cdn).Icon(this.id, this.icon, format, size);
}
/**
@@ -283,7 +289,7 @@ class Guild {
*/
get splashURL() {
if (!this.splash) return null;
return Constants.Endpoints.Guild(this).Splash(this.client.options.http.cdn, this.splash);
return Constants.Endpoints.CDN(this.client.options.http.cdn).Splash(this.id, this.splash);
}
/**
@@ -370,13 +376,15 @@ class Guild {
* @returns {Promise<Collection<Snowflake, User>>}
*/
fetchBans() {
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;
});
return this.client.api.guilds(this.id).bans.get().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())
);
}
/**
@@ -384,7 +392,15 @@ class Guild {
* @returns {Promise<Collection<string, Invite>>}
*/
fetchInvites() {
return this.client.rest.methods.getGuildInvites(this);
return this.client.api.guilds(this.id).invites.get()
.then(inviteItems => {
const invites = new Collection();
for (const inviteItem of inviteItems) {
const invite = new Invite(this.client, inviteItem);
invites.set(invite.code, invite);
}
return invites;
});
}
/**
@@ -392,7 +408,11 @@ class Guild {
* @returns {Collection<Snowflake, Webhook>}
*/
fetchWebhooks() {
return this.client.rest.methods.getGuildWebhooks(this);
return this.client.api.guilds(this.id).webhooks.get().then(data => {
const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks;
});
}
/**
@@ -400,7 +420,11 @@ class Guild {
* @returns {Collection<string, VoiceRegion>}
*/
fetchVoiceRegions() {
return this.client.rest.methods.fetchVoiceRegions(this.id);
return this.client.api.guilds(this.id).regions.get().then(res => {
const regions = new Collection();
for (const region of res) regions.set(region.id, new VoiceRegion(region));
return regions;
});
}
/**
@@ -413,8 +437,19 @@ class Guild {
* @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);
fetchAuditLogs(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];
return this.client.api.guilds(this.id)['audit-logs'].get({ query: {
before: options.before,
after: options.after,
limit: options.limit,
user_id: this.client.resolver.resolveUserID(options.user),
action_type: options.type,
} })
.then(data => GuildAuditLogs.build(this, data));
}
/**
@@ -432,7 +467,15 @@ class Guild {
*/
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);
options.access_token = options.accessToken;
if (options.roles) {
const roles = options.roles;
if (roles instanceof Collection || (roles instanceof Array && roles[0] instanceof Role)) {
options.roles = roles.map(role => role.id);
}
}
return this.client.api.guilds(this.id).members(user.id).put({ data: options })
.then(data => this.client.actions.GuildMemberGet.handle(this, data).member);
}
/**
@@ -445,7 +488,11 @@ class Guild {
user = this.client.resolver.resolveUser(user);
if (!user) return Promise.reject(new Error('User is not cached. Use Client.fetchUser first.'));
if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
return this.client.rest.methods.getGuildMember(this, user, cache);
return this.client.api.guilds(this.id).members(user.id).get()
.then(data => {
if (cache) return this.client.actions.GuildMemberGet.handle(this, data).member;
else return new GuildMember(this, data);
});
}
/**
@@ -502,7 +549,7 @@ class Guild {
* }).catch(console.error);
*/
search(options = {}) {
return this.client.rest.methods.search(this, options);
return Shared.search(this, options);
}
/**
@@ -521,6 +568,7 @@ class Guild {
/**
* Updates the guild with new information - e.g. a new name.
* @param {GuildEditData} data The data to update the guild with
* @param {string} [reason] Reason for editing this guild
* @returns {Promise<Guild>}
* @example
* // Set the guild name and region
@@ -531,8 +579,18 @@ class Guild {
* .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`))
* .catch(console.error);
*/
edit(data) {
return this.client.rest.methods.updateGuild(this, data);
edit(data, reason) {
const _data = {};
if (data.name) _data.name = data.name;
if (data.region) _data.region = data.region;
if (data.verificationLevel) _data.verification_level = Number(data.verificationLevel);
if (data.afkChannel) _data.afk_channel_id = this.client.resolver.resolveChannel(data.afkChannel).id;
if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout);
if (data.icon) _data.icon = this.client.resolver.resolveBase64(data.icon);
if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id;
if (data.splash) _data.splash = this.client.resolver.resolveBase64(data.splash);
return this.client.api.guilds(this.id).patch({ data: _data, reason })
.then(newData => this.client.actions.GuildUpdate.handle(newData).updated);
}
/**
@@ -667,7 +725,12 @@ class Guild {
* @returns {Promise<Guild>}
*/
acknowledge() {
return this.client.rest.methods.ackGuild(this);
return this.client.api.guilds(this.id).ack
.post({ data: { token: this.client.rest._ackToken } })
.then(res => {
if (res.token) this.client.rest._ackToken = res.token;
return this;
});
}
/**
@@ -697,19 +760,26 @@ class Guild {
* .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`))
* .catch(console.error);
*/
ban(user, options = {}) {
if (typeof options === 'number') {
options = { reason: null, 'delete-message-days': options };
} else if (typeof options === 'string') {
options = { reason: options, 'delete-message-days': 0 };
}
ban(user, options = { days: 0 }) {
if (options.days) options['delete-message-days'] = options.days;
return this.client.rest.methods.banGuildMember(this, user, options);
const id = this.client.resolver.resolveUserID(user);
if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.'));
return this.client.api.guilds(this.id).bans(id).put({ query: options })
.then(() => {
if (user instanceof GuildMember) return user;
const _user = this.client.resolver.resolveUser(id);
if (_user) {
const member = this.client.resolver.resolveGuildMember(this, _user);
return member || _user;
}
return id;
});
}
/**
* Unbans a user from the guild.
* @param {UserResolvable} user The user to unban
* @param {string} [reason] Reason for unbanning user
* @returns {Promise<User>}
* @example
* // Unban a user by ID (or with a user/guild member object)
@@ -717,29 +787,35 @@ class Guild {
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
* .catch(console.error);
*/
unban(user) {
return this.client.rest.methods.unbanGuildMember(this, user);
unban(user, reason) {
const id = this.client.resolver.resolveUserID(user);
if (!id) throw new Error('Couldn\'t resolve the user ID to unban.');
return this.client.api.guilds(this.id).bans(id).delete({ reason })
.then(() => user);
}
/**
* Prunes members from the guild based on how long they have been inactive.
* @param {number} days Number of days of inactivity required to kick
* @param {boolean} [dry=false] If true, will return number of users that will be kicked, without actually doing it
* @param {number} [options.days=7] Number of days of inactivity required to kick
* @param {boolean} [options.dry=false] Get number of users that will be kicked, without actually kicking them
* @param {string} [options.reason] Reason for this prune
* @returns {Promise<number>} The number of members that were/will be kicked
* @example
* // See how many members will be pruned
* guild.pruneMembers(12, true)
* guild.pruneMembers({ dry: true })
* .then(pruned => console.log(`This will prune ${pruned} people!`))
* .catch(console.error);
* @example
* // Actually prune the members
* guild.pruneMembers(12)
* guild.pruneMembers({ days: 1, reason: 'too many people!' })
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
* .catch(console.error);
*/
pruneMembers(days, dry = false) {
pruneMembers({ days = 7, dry = false, reason } = {}) {
if (typeof days !== 'number') throw new TypeError('Days must be a number.');
return this.client.rest.methods.pruneGuildMembers(this, days, dry);
return this.client.api.guilds(this.id).prune[dry ? 'get' : 'post']({ query: { days }, reason })
.then(data => data.pruned);
}
/**
@@ -754,7 +830,9 @@ class Guild {
* 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 {Array<PermissionOverwrites|Object>} overwrites Permission overwrites to apply to the new channel
* @param {Object} options Options
* @param {Array<PermissionOverwrites|Object>} [options.overwrites] Permission overwrites to apply to the new channel
* @param {string} [options.reason] Reason for creating this channel
* @returns {Promise<TextChannel|VoiceChannel>}
* @example
* // Create a new text channel
@@ -762,8 +840,14 @@ class Guild {
* .then(channel => console.log(`Created new channel ${channel}`))
* .catch(console.error);
*/
createChannel(name, type, overwrites) {
return this.client.rest.methods.createChannel(this, name, type, overwrites);
createChannel(name, type, { overwrites, reason } = {}) {
if (overwrites instanceof Collection) overwrites = overwrites.array();
return this.client.api.guilds(this.id).channels.post({
data: {
name, type, permission_overwrites: overwrites,
},
reason,
}).then(data => this.client.actions.ChannelCreate.handle(data).channel);
}
/**
@@ -783,12 +867,30 @@ class Guild {
* .catch(console.error);
*/
setChannelPositions(channelPositions) {
return this.client.rest.methods.updateChannelPositions(this.id, channelPositions);
const data = new Array(channelPositions.length);
for (let i = 0; i < channelPositions.length; i++) {
data[i] = {
id: this.client.resolver.resolveChannelID(channelPositions[i].channel),
position: channelPositions[i].position,
};
}
return this.client.api.guilds(this.id).channels.patch({ data: {
guild_id: this.id,
channels: channelPositions,
} }).then(() =>
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.id,
channels: channelPositions,
}).guild
);
}
/**
* Creates a new role in the guild with given information
* @param {RoleData} [data] The data to update the role with
* @param {Object} [options] Options
* @param {RoleData} [options.data] The data to update the role with
* @param {string} [options.reason] Reason for creating this role
* @returns {Promise<Role>}
* @example
* // Create a new role
@@ -796,16 +898,27 @@ class Guild {
* .then(role => console.log(`Created role ${role}`))
* .catch(console.error);
* @example
* // Create a new role with data
* // Create a new role with data and a reason
* guild.createRole({
* name: 'Super Cool People',
* color: 'BLUE',
* data: {
* name: 'Super Cool People',
* color: 'BLUE',
* },
* reason: 'we needed a role for Super Cool People',
* })
* .then(role => console.log(`Created role ${role}`))
* .catch(console.error)
*/
createRole(data = {}) {
return this.client.rest.methods.createGuildRole(this, data);
createRole({ data = {}, reason } = {}) {
if (data.color) data.color = this.client.resolver.resolveColor(data.color);
if (data.permissions) data.permissions = Permissions.resolve(data.permissions);
return this.client.api.guilds(this.id).roles.post({ data, reason }).then(role =>
this.client.actions.GuildRoleCreate.handle({
guild_id: this.id,
role,
}).role
);
}
/**
@@ -826,16 +939,18 @@ class Guild {
* .catch(console.error);
*/
createEmoji(attachment, name, roles) {
return new Promise(resolve => {
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 => {
const dataURI = this.client.resolver.resolveBase64(data);
resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles));
});
}
});
if (typeof attahment === 'string' && attachment.startsWith('data:')) {
const data = { image: attachment, name };
if (roles) data.roles = roles.map(r => r.id ? r.id : r);
return this.client.api.guilds(this.id).emojis.post({ data })
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji);
} else {
return this.client.resolver.resolveBuffer(attachment)
.then(data => {
const dataURI = this.client.resolver.resolveBase64(data);
return this.createEmoji(dataURI, name, roles);
});
}
}
/**
@@ -845,7 +960,8 @@ class Guild {
*/
deleteEmoji(emoji) {
if (!(emoji instanceof Emoji)) emoji = this.emojis.get(emoji);
return this.client.rest.methods.deleteEmoji(emoji);
return this.client.api.guilds(this.id).emojis(this.id).delete()
.then(() => this.client.actions.GuildEmojiDelete.handle(emoji).data);
}
/**
@@ -858,7 +974,9 @@ class Guild {
* .catch(console.error);
*/
leave() {
return this.client.rest.methods.leaveGuild(this);
if (this.ownerID === this.client.user.id) return Promise.reject(new Error('Guild is owned by the client.'));
return this.rest.api.users('@me').guilds(this.id).delete()
.then(() => this.client.actions.GuildDelete.handle({ id: this.id }).guild);
}
/**
@@ -871,7 +989,8 @@ class Guild {
* .catch(console.error);
*/
delete() {
return this.client.rest.methods.deleteGuild(this);
return this.client.api.guilds(this.id).delete()
.then(() => this.client.actions.GuildDelete.handle({ id: this.id }).guild);
}
/**
@@ -1028,7 +1147,13 @@ class Guild {
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);
return this.client.api.guilds(this.id).roles.patch({ data: updatedRoles })
.then(() =>
this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.id,
roles: updatedRoles,
}).guild
);
}
/**
@@ -1052,7 +1177,13 @@ class Guild {
Util.moveElementInArray(updatedChannels, channel, position, relative);
updatedChannels = updatedChannels.map((r, i) => ({ id: r.id, position: i }));
return this.client.rest.methods.setChannelPositions(this.id, updatedChannels);
return this.client.api.guilds(this.id).channels.patch({ data: updatedChannels })
.then(() =>
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.id,
roles: updatedChannels,
}).guild
);
}
/**

View File

@@ -10,6 +10,7 @@ const Targets = {
WEBHOOK: 'WEBHOOK',
EMOJI: 'EMOJI',
MESSAGE: 'MESSAGE',
UNKNOWN: 'UNKNOWN',
};
const Actions = {
@@ -83,7 +84,7 @@ class GuildAuditLogs {
if (target < 60) return Targets.WEBHOOK;
if (target < 70) return Targets.EMOJI;
if (target < 80) return Targets.MESSAGE;
return null;
return Targets.UNKNOWN;
}
@@ -219,11 +220,14 @@ class GuildAuditLogsEntry {
}
}
if ([Targets.USER, Targets.GUILD].includes(targetType)) {
if (targetType === Targets.UNKNOWN) {
/**
* The target of this entry
* @type {?Guild|User|Role|Emoji|Invite|Webhook}
* @type {Snowflake|Guild|User|Role|Emoji|Invite|Webhook}
*/
this.target = data.target_id;
} else if ([Targets.USER, Targets.GUILD].includes(targetType)) {
this.target = guild.client[`${targetType.toLowerCase()}s`].get(data.target_id);
} else if (targetType === Targets.WEBHOOK) {
this.target = guild.fetchWebhooks()

View File

@@ -1,5 +1,6 @@
const Channel = require('./Channel');
const Role = require('./Role');
const Invite = require('./Invite');
const PermissionOverwrites = require('./PermissionOverwrites');
const Permissions = require('../util/Permissions');
const Collection = require('../util/Collection');
@@ -138,7 +139,8 @@ class GuildChannel extends Channel {
* Overwrites the permissions for a user or role in this channel.
* @param {RoleResolvable|UserResolvable} userOrRole The user or role to update
* @param {PermissionOverwriteOptions} options The configuration for the update
* @returns {Promise}
* @param {string} [reason] Reason for creating/editing this overwrite
* @returns {Promise<GuildChannel>}
* @example
* // Overwrite permissions for a message author
* message.channel.overwritePermissions(message.author, {
@@ -147,7 +149,7 @@ class GuildChannel extends Channel {
* .then(() => console.log('Done!'))
* .catch(console.error);
*/
overwritePermissions(userOrRole, options) {
overwritePermissions(userOrRole, options, reason) {
const payload = {
allow: 0,
deny: 0,
@@ -186,7 +188,9 @@ class GuildChannel extends Channel {
}
}
return this.client.rest.methods.setChannelOverwrite(this, payload);
return this.client.api.channels(this.id).permissions(payload.id)
.put({ data: payload, reason })
.then(() => this);
}
/**
@@ -202,6 +206,7 @@ class GuildChannel extends Channel {
/**
* Edits the channel.
* @param {ChannelData} data The new data for the channel
* @param {string} [reason] Reason for editing this channel
* @returns {Promise<GuildChannel>}
* @example
* // Edit a channel
@@ -209,8 +214,17 @@ class GuildChannel extends Channel {
* .then(c => console.log(`Edited channel ${c}`))
* .catch(console.error);
*/
edit(data) {
return this.client.rest.methods.updateChannel(this, data);
edit(data, reason) {
return this.client.api.channels(this.id).patch({
data: {
name: (data.name || this.name).trim(),
topic: data.topic || this.topic,
position: data.position || this.position,
bitrate: data.bitrate || this.bitrate,
user_limit: data.userLimit || this.userLimit,
},
reason,
}).then(newData => this.client.actions.ChannelUpdate.handle(newData).updated);
}
/**
@@ -253,7 +267,7 @@ class GuildChannel extends Channel {
* .catch(console.error);
*/
setTopic(topic) {
return this.client.rest.methods.updateChannel(this, { topic });
return this.edit({ topic });
}
/**
@@ -269,10 +283,14 @@ class GuildChannel extends Channel {
* kicked after 24 hours if they have not yet received a role
* @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever)
* @param {number} [options.maxUses=0] Maximum number of uses
* @param {string} [reason] Reason for creating this
* @returns {Promise<Invite>}
*/
createInvite(options = {}) {
return this.client.rest.methods.createChannelInvite(this, options);
createInvite({ temporary = false, maxAge = 86400, maxUses = 0 }, reason) {
return this.client.api.channels(this.id).invites.post({ data: {
temporary, max_age: maxAge, max_uses: maxUses,
}, reason })
.then(invite => new Invite(this.client, invite));
}
/**
@@ -322,6 +340,20 @@ class GuildChannel extends Channel {
this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS);
}
/**
* Deletes this channel.
* @param {string} [reason] Reason for deleting this channel
* @returns {Promise<GuildChannel>}
* @example
* // Delete the channel
* channel.delete('making room for new channels')
* .then() // Success
* .catch(console.error); // Log error
*/
delete(reason) {
return this.client.api.channels(this.id).delete({ reason }).then(() => this);
}
/**
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
* @returns {string}

View File

@@ -332,10 +332,24 @@ class GuildMember {
/**
* Edit a guild member.
* @param {GuildMemberEditData} data The data to edit the member with
* @param {string} [reason] Reason for editing this user
* @returns {Promise<GuildMember>}
*/
edit(data) {
return this.client.rest.methods.updateGuildMember(this, data);
edit(data, reason) {
if (data.channel) {
data.channel_id = this.client.resolver.resolveChannel(data.channel).id;
data.channel = null;
}
if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role);
let endpoint = this.client.api.guilds(this.guild.id);
if (this.user.id === this.client.user.id) {
const keys = Object.keys(data);
if (keys.length === 1 && keys[0] === 'nick') endpoint = endpoint.members('@me').nick;
else endpoint = endpoint.members(this.id);
} else {
endpoint = endpoint.members(this.id);
}
return endpoint.patch({ data, reason }).then(newData => this.guild._updateMember(this, newData).mem);
}
/**
@@ -382,7 +396,10 @@ class GuildMember {
addRole(role) {
if (!(role instanceof Role)) role = this.guild.roles.get(role);
if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.'));
return this.client.rest.methods.addMemberRole(this, role);
if (this._roles.includes(role.id)) return Promise.resolve(this);
return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id)
.put()
.then(() => this);
}
/**
@@ -394,9 +411,9 @@ class GuildMember {
let allRoles;
if (roles instanceof Collection) {
allRoles = this._roles.slice();
for (const role of roles.values()) allRoles.push(role.id);
for (const role of roles.values()) allRoles.push(role.id ? role.id : role);
} else {
allRoles = this._roles.concat(roles);
allRoles = this._roles.concat(roles.map(r => r.id ? r.id : r));
}
return this.edit({ roles: allRoles });
}
@@ -409,7 +426,9 @@ class GuildMember {
removeRole(role) {
if (!(role instanceof Role)) role = this.guild.roles.get(role);
if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.'));
return this.client.rest.methods.removeMemberRole(this, role);
return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id)
.delete()
.then(() => this);
}
/**
@@ -464,7 +483,13 @@ class GuildMember {
* @returns {Promise<GuildMember>}
*/
kick(reason) {
return this.client.rest.methods.kickGuildMember(this.guild, this, reason);
return this.client.api.guilds(this.guild.id).members(this.user.id).delete({ reason })
.then(() =>
this.client.actions.GuildMemberRemove.handle({
guild_id: this.guild.id,
user: this.user,
}).member
);
}
/**

View File

@@ -136,15 +136,16 @@ class Invite {
* @readonly
*/
get url() {
return Constants.Endpoints.inviteLink(this.code);
return Constants.Endpoints.invite(this.code);
}
/**
* Deletes this invite.
* @param {string} [reason] Reason for deleting this invite
* @returns {Promise<Invite>}
*/
delete() {
return this.client.rest.methods.deleteInvite(this);
delete(reason) {
return this.client.api.invites(this.code).delete({ reason }).then(() => this);
}
/**

View File

@@ -380,7 +380,27 @@ class Message {
} else if (!options) {
options = {};
}
return this.client.rest.methods.updateMessage(this, content, options);
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
const { embed, code, reply } = options;
// Wrap everything in a code block
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true);
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
}
// Add the reply prefix
if (reply && this.channel.type !== 'dm') {
const id = this.client.resolver.resolveUserID(reply);
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
content = `${mention}${content ? `, ${content}` : ''}`;
}
return this.client.api.channels(this.channel.id).messages(this.id)
.patch({ data: { content, embed } })
.then(data => this.client.actions.MessageUpdate.handle(data).updated);
}
/**
@@ -388,7 +408,8 @@ class Message {
* @returns {Promise<Message>}
*/
pin() {
return this.client.rest.methods.pinMessage(this);
return this.client.api.channels(this.channel.id).pins(this.id).put()
.then(() => this);
}
/**
@@ -396,7 +417,8 @@ class Message {
* @returns {Promise<Message>}
*/
unpin() {
return this.client.rest.methods.unpinMessage(this);
return this.client.api.channels(this.channel.id).pins(this.id).delete()
.then(() => this);
}
/**
@@ -408,7 +430,9 @@ class Message {
emoji = this.client.resolver.resolveEmojiIdentifier(emoji);
if (!emoji) throw new TypeError('Emoji must be a string or Emoji/ReactionEmoji');
return this.client.rest.methods.addMessageReaction(this, emoji);
return this.client.api.channels(this.channel.id).messages(this.id).reactions(emoji)['@me']
.put()
.then(() => this._addReaction(Util.parseEmoji(emoji), this.client.user));
}
/**
@@ -416,12 +440,15 @@ class Message {
* @returns {Promise<Message>}
*/
clearReactions() {
return this.client.rest.methods.removeMessageReactions(this);
return this.client.api.channels(this.channel.id).messages(this.id).reactions.delete()
.then(() => this);
}
/**
* Deletes the message.
* @param {number} [timeout=0] How long to wait to delete the message in milliseconds
* @param {Object} [options] Options
* @param {number} [options.timeout=0] How long to wait to delete the message in milliseconds
* @param {string} [options.reason] Reason for deleting this message, if it does not belong to the client user
* @returns {Promise<Message>}
* @example
* // Delete a message
@@ -429,13 +456,19 @@ class Message {
* .then(msg => console.log(`Deleted message from ${msg.author}`))
* .catch(console.error);
*/
delete(timeout = 0) {
delete({ timeout = 0, reason } = {}) {
if (timeout <= 0) {
return this.client.rest.methods.deleteMessage(this);
return this.client.api.channels(this.channel.id).messages(this.id)
.delete({ reason })
.then(() =>
this.client.actions.MessageDelete.handle({
id: this.id,
channel_id: this.channel.id,
}).message);
} else {
return new Promise(resolve => {
this.client.setTimeout(() => {
resolve(this.delete());
resolve(this.delete({ reason }));
}, timeout);
});
}
@@ -468,7 +501,12 @@ class Message {
* @returns {Promise<Message>}
*/
acknowledge() {
return this.client.rest.methods.ackMessage(this);
return this.client.api.channels(this.channel.id).messages(this.id).ack
.post({ data: { token: this.client.rest._ackToken } })
.then(res => {
if (res.token) this.client.rest._ackToken = res.token;
return this;
});
}
/**

View File

@@ -61,12 +61,19 @@ class MessageReaction {
* @returns {Promise<MessageReaction>}
*/
remove(user = this.message.client.user) {
const message = this.message;
const userID = this.message.client.resolver.resolveUserID(user);
if (!userID) return Promise.reject(new Error('Couldn\'t resolve the user ID to remove from the reaction.'));
return message.client.rest.methods.removeMessageReaction(
message, this.emoji.identifier, userID
);
return this.message.client.api.channels(this.message.channel.id).messages(this.message.id)
.reactions(this.emoji.identifier)[userID === this.message.client.user.id ? '@me' : userID]
.delete()
.then(() =>
this.message.client.actions.MessageReactionRemove.handle({
user_id: userID,
message_id: this.message.id,
emoji: this.emoji,
channel_id: this.message.channel.id,
}).reaction
);
}
/**
@@ -76,17 +83,18 @@ class MessageReaction {
*/
fetchUsers(limit = 100) {
const message = this.message;
return message.client.rest.methods.getMessageReactionUsers(
message, this.emoji.identifier, limit
).then(users => {
this.users = new Collection();
for (const rawUser of users) {
const user = this.message.client.dataManager.newUser(rawUser);
this.users.set(user.id, user);
}
this.count = this.users.size;
return this.users;
});
return message.client.api.channels(message.channel.id).messages(message.id)
.reactions(this.emoji.identifier)
.get({ query: { limit } })
.then(users => {
this.users = new Collection();
for (const rawUser of users) {
const user = message.client.dataManager.newUser(rawUser);
this.users.set(user.id, user);
}
this.count = this.users.size;
return this.users;
});
}
}

View File

@@ -137,7 +137,8 @@ class OAuth2Application {
* @returns {OAuth2Application}
*/
reset() {
return this.client.rest.methods.resetApplication(this.id);
return this.rest.api.oauth2.applications(this.id).reset.post()
.then(app => new OAuth2Application(this.client, app));
}
/**

View File

@@ -33,10 +33,13 @@ class PermissionOverwrites {
/**
* Delete this Permission Overwrite.
* @param {string} [reason] Reason for deleting this overwrite
* @returns {Promise<PermissionOverwrites>}
*/
delete() {
return this.channel.client.rest.methods.deletePermissionOverwrites(this);
delete(reason) {
return this.channel.client.api.channels(this.channel.id).permissions(this.id)
.delete({ reason })
.then(() => this);
}
}

View File

@@ -190,6 +190,7 @@ class Role {
/**
* Edits the role.
* @param {RoleData} data The new data for the role
* @param {string} [reason] Reason for editing this role
* @returns {Promise<Role>}
* @example
* // Edit a role
@@ -197,8 +198,20 @@ class Role {
* .then(r => console.log(`Edited role ${r}`))
* .catch(console.error);
*/
edit(data) {
return this.client.rest.methods.updateGuildRole(this, data);
edit(data, reason) {
if (data.permissions) data.permissions = Permissions.resolve(data.permissions);
else data.permissions = this.permissions;
return this.client.api.guilds(this.guild.id).roles(this.id).patch({
data: {
name: data.name || this.name,
position: typeof data.position !== 'undefined' ? data.position : this.position,
color: this.client.resolver.resolveColor(data.color || this.color),
hoist: typeof data.hoist !== 'undefined' ? data.hoist : this.hoist,
mentionable: typeof data.mentionable !== 'undefined' ? data.mentionable : this.mentionable,
},
reason,
})
.then(role => this.client.actions.GuildRoleUpdate.handle({ role, guild_id: this.guild.id }).updated);
}
/**
@@ -288,6 +301,7 @@ class Role {
/**
* Deletes the role.
* @param {string} [reason] Reason for deleting this role
* @returns {Promise<Role>}
* @example
* // Delete a role
@@ -295,8 +309,11 @@ class Role {
* .then(r => console.log(`Deleted role ${r}`))
* .catch(console.error);
*/
delete() {
return this.client.rest.methods.deleteGuildRole(this);
delete(reason) {
return this.client.api.guilds(this.guild.id).roles(this.id).delete({ reason })
.then(() =>
this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: this.id }).role
);
}
/**

View File

@@ -1,4 +1,5 @@
const GuildChannel = require('./GuildChannel');
const Webhook = require('./Webhook');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Collection = require('../util/Collection');
@@ -56,7 +57,11 @@ class TextChannel extends GuildChannel {
* @returns {Promise<Collection<Snowflake, Webhook>>}
*/
fetchWebhooks() {
return this.client.rest.methods.getChannelWebhooks(this);
return this.client.api.channels(this.id).webhooks.get().then(data => {
const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks;
});
}
/**
@@ -70,15 +75,14 @@ class TextChannel extends GuildChannel {
* .catch(console.error)
*/
createWebhook(name, avatar) {
return new Promise(resolve => {
if (typeof avatar === 'string' && avatar.startsWith('data:')) {
resolve(this.client.rest.methods.createWebhook(this, name, avatar));
} else {
this.client.resolver.resolveBuffer(avatar).then(data =>
resolve(this.client.rest.methods.createWebhook(this, name, data))
);
}
});
if (typeof avatar === 'string' && avatar.startsWith('data:')) {
return this.client.api.channels(this.id).webhooks.post({ data: {
name, avatar,
} }).then(data => new Webhook(this.client, data));
} else {
return this.client.resolver.resolveBuffer(avatar).then(data =>
this.createWebhook(name, this.client.resolver.resolveBase64(data) || null));
}
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel

View File

@@ -1,6 +1,7 @@
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Constants = require('../util/Constants');
const Presence = require('./Presence').Presence;
const UserProfile = require('./UserProfile');
const Snowflake = require('../util/Snowflake');
/**
@@ -115,7 +116,7 @@ class User {
size = format;
format = 'default';
}
return Constants.Endpoints.User(this).Avatar(this.client.options.http.cdn, this.avatar, format, size);
return Constants.Endpoints.CDN(this.client.options.http.cdn).Avatar(this.id, this.avatar, format, size);
}
/**
@@ -199,7 +200,11 @@ class User {
* @returns {Promise<DMChannel>}
*/
createDM() {
return this.client.rest.methods.createDM(this);
if (this.dmChannel) return Promise.resolve(this.dmChannel);
return this.client.api.users(this.client.user.id).channels.post({ data: {
recipient_id: this.id,
} })
.then(data => this.client.actions.ChannelCreate.handle(data).channel);
}
/**
@@ -207,43 +212,10 @@ class User {
* @returns {Promise<DMChannel>}
*/
deleteDM() {
return this.client.rest.methods.deleteChannel(this);
}
/**
* Sends a friend request to the user.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<User>}
*/
addFriend() {
return this.client.rest.methods.addFriend(this);
}
/**
* Removes the user from your friends.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<User>}
*/
removeFriend() {
return this.client.rest.methods.removeFriend(this);
}
/**
* Blocks the user.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<User>}
*/
block() {
return this.client.rest.methods.blockUser(this);
}
/**
* Unblocks the user.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<User>}
*/
unblock() {
return this.client.rest.methods.unblockUser(this);
if (!this.dmChannel) return Promise.reject(new Error('No DM Channel exists!'));
return this.client.api.channels(this.dmChannel.id).delete().then(data =>
this.client.actions.ChannelDelete.handle(data).channel
);
}
/**
@@ -252,7 +224,7 @@ class User {
* @returns {Promise<UserProfile>}
*/
fetchProfile() {
return this.client.rest.methods.fetchUserProfile(this);
return this.client.api.users(this.id).profile.get().then(data => new UserProfile(data));
}
/**
@@ -262,7 +234,8 @@ class User {
* @returns {Promise<User>}
*/
setNote(note) {
return this.client.rest.methods.setNote(this, note);
return this.client.api.users('@me').notes(this.id).put({ data: { note } })
.then(() => this);
}
/**

View File

@@ -91,7 +91,7 @@ class Webhook {
* Send a message with this webhook.
* @param {StringResolvable} [content] The content to send
* @param {WebhookMessageOptions} [options={}] The options to provide
* @returns {Promise<Message|Message[]>}
* @returns {Promise<Message|Object>}
* @example
* // Send a message
* webhook.send('hello!')
@@ -106,6 +106,23 @@ class Webhook {
options = {};
}
if (!options.username) options.username = this.name;
if (options.avatarURL) {
options.avatar_url = options.avatarURL;
options.avatarURL = null;
}
if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
if (content) {
if (options.disableEveryone ||
(typeof options.disableEveryone === 'undefined' && this.client.options.disableEveryone)
) {
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
}
}
options.content = content;
if (options.file) {
if (options.files) options.files.push(options.file);
else options.files = [options.file];
@@ -132,16 +149,29 @@ class Webhook {
file.file = buffer;
return file;
})
)).then(files => this.client.rest.methods.sendWebhookMessage(this, content, options, files));
)).then(files => this.client.api.webhooks(this.id, this.token).post({
data: options,
query: { wait: true },
files,
auth: false,
}));
}
return this.client.rest.methods.sendWebhookMessage(this, content, options);
return this.client.api.webhooks(this.id, this.token).post({
data: options,
query: { wait: true },
auth: false,
}).then(data => {
if (!this.client.channels) return data;
const Message = require('./Message');
return new Message(this.client.channels.get(data.channel_id, data, this.client));
});
}
/**
* Send a raw slack message with this webhook.
* @param {Object} body The raw body to send
* @returns {Promise}
* @returns {Promise<Message|Object>}
* @example
* // Send a slack message
* webhook.sendSlackMessage({
@@ -156,34 +186,49 @@ class Webhook {
* }).catch(console.error);
*/
sendSlackMessage(body) {
return this.client.rest.methods.sendSlackWebhookMessage(this, body);
return this.client.api.webhooks(this.id, this.token).slack.post({
query: { wait: true },
auth: false,
data: body,
}).then(data => {
if (!this.client.channels) return data;
const Message = require('./Message');
return new Message(this.client.channels.get(data.channel_id, data, this.client));
});
}
/**
* Edit the webhook.
* @param {string} name The new name for the webhook
* @param {BufferResolvable} avatar The new avatar for the webhook
* @param {Object} options Options
* @param {string} [options.name] New name for this webhook
* @param {BufferResolvable} [options.avatar] New avatar for this webhook
* @param {string} [reason] Reason for editing this webhook
* @returns {Promise<Webhook>}
*/
edit(name = this.name, avatar) {
if (avatar) {
edit({ name = this.name, avatar }, reason) {
if (avatar && (typeof avatar === 'string' && !avatar.startsWith('data:'))) {
return this.client.resolver.resolveBuffer(avatar).then(file => {
const dataURI = this.client.resolver.resolveBase64(file);
return this.client.rest.methods.editWebhook(this, name, dataURI);
return this.edit({ name, avatar: dataURI }, reason);
});
}
return this.client.rest.methods.editWebhook(this, name).then(data => {
this.setup(data);
return this.client.api.webhooks(this.id, this.token).patch({
data: { name, avatar },
reason,
}).then(data => {
this.name = data.name;
this.avatar = data.avatar;
return this;
});
}
/**
* Delete the webhook.
* @param {string} [reason] Reason for deleting this webhook
* @returns {Promise}
*/
delete() {
return this.client.rest.methods.deleteWebhook(this);
delete(reason) {
return this.client.api.webhooks(this.id, this.token).delete({ reason });
}
}

View File

@@ -1,7 +1,8 @@
const path = require('path');
const Message = require('../Message');
const MessageCollector = require('../MessageCollector');
const Shared = require('../shared');
const Collection = require('../../util/Collection');
const Snowflake = require('../../util/Snowflake');
/**
* Interface for classes that have text-channel-like features.
@@ -78,6 +79,8 @@ class TextBasedChannel {
options = {};
}
if (!options.content) options.content = content;
if (options.embed && options.embed.file) options.file = options.embed.file;
if (options.file) {
@@ -106,10 +109,13 @@ class TextBasedChannel {
file.file = buffer;
return file;
})
)).then(files => this.client.rest.methods.sendMessage(this, content, options, files));
)).then(files => {
options.files = files;
return Shared.sendMessage(this, options);
});
}
return this.client.rest.methods.sendMessage(this, content, options);
return Shared.sendMessage(this, options);
}
/**
@@ -125,14 +131,17 @@ class TextBasedChannel {
* .catch(console.error);
*/
fetchMessage(messageID) {
const Message = require('../Message');
if (!this.client.user.bot) {
return this.fetchMessages({ limit: 1, around: messageID }).then(messages => {
return this.fetchMessages({ limit: 1, around: messageID })
.then(messages => {
const msg = messages.get(messageID);
if (!msg) throw new Error('Message not found.');
return msg;
});
}
return this.client.rest.methods.getChannelMessage(this, messageID).then(data => {
return this.client.api.channels(this.id).messages(messageID).get()
.then(data => {
const msg = data instanceof Message ? data : new Message(this, data, this.client);
this._cacheMessage(msg);
return msg;
@@ -160,7 +169,9 @@ class TextBasedChannel {
* .catch(console.error);
*/
fetchMessages(options = {}) {
return this.client.rest.methods.getChannelMessages(this, options).then(data => {
const Message = require('../Message');
return this.client.api.channels(this.id).messages.get({ query: options })
.then(data => {
const messages = new Collection();
for (const message of data) {
const msg = new Message(this, message, this.client);
@@ -176,7 +187,8 @@ class TextBasedChannel {
* @returns {Promise<Collection<Snowflake, Message>>}
*/
fetchPinnedMessages() {
return this.client.rest.methods.getChannelPinnedMessages(this).then(data => {
const Message = require('../Message');
return this.client.api.channels(this.id).pins.get().then(data => {
const messages = new Collection();
for (const message of data) {
const msg = new Message(this, message, this.client);
@@ -231,7 +243,7 @@ class TextBasedChannel {
* }).catch(console.error);
*/
search(options = {}) {
return this.client.rest.methods.search(this, options);
return Shared.search(this, options);
}
/**
@@ -244,13 +256,14 @@ class TextBasedChannel {
startTyping(count) {
if (typeof count !== 'undefined' && count < 1) throw new RangeError('Count must be at least 1.');
if (!this.client.user._typing.has(this.id)) {
const endpoint = this.client.api.channels(this.id).typing;
this.client.user._typing.set(this.id, {
count: count || 1,
interval: this.client.setInterval(() => {
this.client.rest.methods.sendTyping(this.id);
endpoint.post();
}, 9000),
});
this.client.rest.methods.sendTyping(this.id);
endpoint.post();
} else {
const entry = this.client.user._typing.get(this.id);
entry.count = count || entry.count + 1;
@@ -360,8 +373,20 @@ class TextBasedChannel {
bulkDelete(messages, filterOld = false) {
if (!isNaN(messages)) return this.fetchMessages({ limit: messages }).then(msgs => this.bulkDelete(msgs, filterOld));
if (messages instanceof Array || messages instanceof Collection) {
const messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id);
return this.client.rest.methods.bulkDeleteMessages(this, messageIDs, filterOld);
let messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id);
if (filterOld) {
messageIDs = messageIDs.filter(id =>
Date.now() - Snowflake.deconstruct(id).date.getTime() < 1209600000
);
}
return this.rest.api.channels(this.id).messages['bulk-delete']
.post({ data: { messages: messageIDs } })
.then(() =>
this.client.actions.MessageDeleteBulk.handle({
channel_id: this.id,
ids: messageIDs,
}).messages
);
}
throw new TypeError('The messages must be an Array, Collection, or number.');
}
@@ -373,7 +398,12 @@ class TextBasedChannel {
*/
acknowledge() {
if (!this.lastMessageID) return Promise.resolve(this);
return this.client.rest.methods.ackTextChannel(this);
return this.client.api.channels(this.id).messages(this.lastMessageID)
.post({ data: { token: this.client.rest._ackToken } })
.then(res => {
if (res.token) this.client.rest._ackToken = res.token;
return this;
});
}
_cacheMessage(message) {

View File

@@ -0,0 +1,63 @@
const long = require('long');
module.exports = function search(target, options) {
if (typeof options === 'string') options = { content: options };
if (options.before) {
if (!(options.before instanceof Date)) options.before = new Date(options.before);
options.maxID = long.fromNumber(options.before.getTime() - 14200704e5).shiftLeft(22).toString();
}
if (options.after) {
if (!(options.after instanceof Date)) options.after = new Date(options.after);
options.minID = long.fromNumber(options.after.getTime() - 14200704e5).shiftLeft(22).toString();
}
if (options.during) {
if (!(options.during instanceof Date)) options.during = new Date(options.during);
const t = options.during.getTime() - 14200704e5;
options.minID = long.fromNumber(t).shiftLeft(22).toString();
options.maxID = long.fromNumber(t + 86400000).shiftLeft(22).toString();
}
if (options.channel) options.channel = target.client.resolver.resolveChannelID(options.channel);
if (options.author) options.author = target.client.resolver.resolveUserID(options.author);
if (options.mentions) options.mentions = target.client.resolver.resolveUserID(options.options.mentions);
options = {
content: options.content,
max_id: options.maxID,
min_id: options.minID,
has: options.has,
channel_id: options.channel,
author_id: options.author,
author_type: options.authorType,
context_size: options.contextSize,
sort_by: options.sortBy,
sort_order: options.sortOrder,
limit: options.limit,
offset: options.offset,
mentions: options.mentions,
mentions_everyone: options.mentionsEveryone,
link_hostname: options.linkHostname,
embed_provider: options.embedProvider,
embed_type: options.embedType,
attachment_filename: options.attachmentFilename,
attachment_extension: options.attachmentExtension,
};
// Lazy load these because some of them use util
const Channel = require('../Channel');
const Guild = require('../Guild');
const Message = require('../Message');
if (!(target instanceof Channel || target instanceof Guild)) {
throw new TypeError('Target must be a TextChannel, DMChannel, GroupDMChannel, or Guild.');
}
let endpoint = target.client.api[target instanceof Channel ? 'channels' : 'guilds'](target.id).messages().search;
return endpoint.get({ query: options }).then(body => {
const messages = body.messages.map(x =>
x.map(m => new Message(target.client.channels.get(m.channel_id), m, target.client))
);
return {
totalResults: body.total_results,
messages,
};
});
};

View File

@@ -0,0 +1,60 @@
const Util = require('../../util/Util');
module.exports = function sendMessage(channel, options) {
const User = require('../User');
if (channel instanceof User) return channel.createDM().then(dm => dm.send(options));
const GuildMember = require('../GuildMember');
let { content, nonce, reply, code, disableEveryone, tts, embed, files, split } = options;
if (typeof nonce !== 'undefined') {
nonce = parseInt(nonce);
if (isNaN(nonce) || nonce < 0) throw new RangeError('Message nonce must fit in an unsigned 64-bit integer.');
}
if (content) {
if (split && typeof split !== 'object') split = {};
// Wrap everything in a code block
if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
content = Util.escapeMarkdown(channel.client.resolver.resolveString(content), true);
content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
if (split) {
split.prepend = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n`;
split.append = '\n```';
}
}
// Add zero-width spaces to @everyone/@here
if (disableEveryone || (typeof disableEveryone === 'undefined' && channel.client.options.disableEveryone)) {
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
}
if (split) content = Util.splitMessage(content, split);
}
// Add the reply prefix
if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
const id = channel.client.resolver.resolveUserID(reply);
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
if (split) split.prepend = `${mention}, ${split.prepend || ''}`;
content = `${mention}${typeof content !== 'undefined' ? `, ${content}` : ''}`;
}
if (content instanceof Array) {
return new Promise((resolve, reject) => {
const messages = [];
(function sendChunk() {
const opt = content.length ? { tts } : { tts, embed, files };
channel.send(content.shift(), opt).then(message => {
messages.push(message);
if (content.length === 0) return resolve(messages);
return sendChunk();
}).catch(reject);
}());
});
}
return channel.client.api.channels(channel.id).messages.post({
data: { content, tts, nonce, embed },
files,
}).then(data => channel.client.actions.MessageCreate.handle(data).message);
};

View File

@@ -0,0 +1,4 @@
module.exports = {
search: require('./Search'),
sendMessage: require('./SendMessage'),
};

View File

@@ -106,101 +106,7 @@ const AllowedImageSizes = [
2048,
];
const Endpoints = exports.Endpoints = {
User: userID => {
if (userID.id) userID = userID.id;
const base = `/users/${userID}`;
return {
toString: () => base,
channels: `${base}/channels`,
profile: `${base}/profile`,
relationships: `${base}/relationships`,
settings: `${base}/settings`,
Relationship: uID => `${base}/relationships/${uID}`,
Guild: guildID => `${base}/guilds/${guildID}`,
Note: id => `${base}/notes/${id}`,
Mentions: (limit, roles, everyone, guildID) =>
`${base}/mentions?limit=${limit}&roles=${roles}&everyone=${everyone}${guildID ? `&guild_id=${guildID}` : ''}`,
Avatar: (root, hash, format, size) => {
if (userID === '1') return hash;
return Endpoints.CDN(root).Avatar(userID, hash, format, size);
},
};
},
guilds: '/guilds',
Guild: guildID => {
if (guildID.id) guildID = guildID.id;
const base = `/guilds/${guildID}`;
return {
toString: () => base,
prune: `${base}/prune`,
embed: `${base}/embed`,
bans: `${base}/bans`,
integrations: `${base}/integrations`,
members: `${base}/members`,
channels: `${base}/channels`,
invites: `${base}/invites`,
roles: `${base}/roles`,
emojis: `${base}/emojis`,
search: `${base}/messages/search`,
voiceRegions: `${base}/regions`,
webhooks: `${base}/webhooks`,
ack: `${base}/ack`,
settings: `${base}/settings`,
auditLogs: `${base}/audit-logs`,
Emoji: emojiID => `${base}/emojis/${emojiID}`,
Icon: (root, hash, format, size) => Endpoints.CDN(root).Icon(guildID, hash, format, size),
Splash: (root, hash) => Endpoints.CDN(root).Splash(guildID, hash),
Role: roleID => `${base}/roles/${roleID}`,
Member: memberID => {
if (memberID.id) memberID = memberID.id;
const mbase = `${base}/members/${memberID}`;
return {
toString: () => mbase,
Role: roleID => `${mbase}/roles/${roleID}`,
nickname: `${base}/members/@me/nick`,
};
},
};
},
channels: '/channels',
Channel: channelID => {
if (channelID.id) channelID = channelID.id;
const base = `/channels/${channelID}`;
return {
toString: () => base,
messages: {
toString: () => `${base}/messages`,
bulkDelete: `${base}/messages/bulk-delete`,
},
invites: `${base}/invites`,
typing: `${base}/typing`,
permissions: `${base}/permissions`,
webhooks: `${base}/webhooks`,
search: `${base}/messages/search`,
pins: `${base}/pins`,
Pin: messageID => `${base}/pins/${messageID}`,
Recipient: recipientID => `${base}/recipients/${recipientID}`,
Message: messageID => {
if (messageID.id) messageID = messageID.id;
const mbase = `${base}/messages/${messageID}`;
return {
toString: () => mbase,
reactions: `${mbase}/reactions`,
ack: `${mbase}/ack`,
Reaction: (emoji, limit) => {
const rbase = `${mbase}/reactions/${emoji}${limit ? `?limit=${limit}` : ''}`;
return {
toString: () => rbase,
User: userID => `${rbase}/${userID}`,
};
},
};
},
};
},
Message: m => exports.Endpoints.Channel(m.channel).Message(m),
Member: m => exports.Endpoints.Guild(m.guild).Member(m),
exports.Endpoints = {
CDN(root) {
return {
Emoji: emojiID => `${root}/emojis/${emojiID}.png`,
@@ -227,26 +133,7 @@ const Endpoints = exports.Endpoints = {
Splash: (guildID, hash) => `${root}/splashes/${guildID}/${hash}.jpg`,
};
},
OAUTH2: {
Application: appID => {
const base = `/oauth2/applications/${appID}`;
return {
toString: () => base,
reset: `${base}/reset`,
};
},
App: appID => `/oauth2/authorize?client_id=${appID}`,
},
login: '/auth/login',
logout: '/auth/logout',
voiceRegions: '/voice/regions',
gateway: {
toString: () => '/gateway',
bot: '/gateway/bot',
},
Invite: inviteID => `/invite/${inviteID}?with_counts=true`,
inviteLink: id => `https://discord.gg/${id}`,
Webhook: (webhookID, token) => `/webhooks/${webhookID}${token ? `/${token}` : ''}`,
invite: code => `https://discord.gg/${code}`,
};

View File

@@ -30,7 +30,7 @@ class Util {
}
messages[msg] += (messages[msg].length > 0 && messages[msg] !== prepend ? char : '') + splitText[i];
}
return messages;
return messages.filter(m => m);
}
/**

46
test/tester1000.js Normal file
View File

@@ -0,0 +1,46 @@
const Discord = require('../src');
const { token, prefix, owner } = require('./auth.js');
// eslint-disable-next-line no-console
const log = (...args) => console.log(process.uptime().toFixed(3), ...args);
const client = new Discord.Client();
client.on('debug', log);
client.on('ready', () => {
log('READY', client.user.tag, client.user.id);
});
const commands = {
eval: message => {
if (message.author.id !== owner) return;
let res;
try {
res = eval(message.content);
if (typeof res !== 'string') res = require('util').inspect(res);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err.stack);
res = err.message;
}
message.channel.send(res, { code: 'js' });
},
};
client.on('message', message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
message.content = message.content.replace(prefix, '').trim().split(' ');
const command = message.content.shift();
message.content = message.content.join(' ');
// eslint-disable-next-line no-console
console.log('COMMAND', command, message.content);
if (command in commands) commands[command](message);
});
client.login(token);
// eslint-disable-next-line no-console
process.on('unhandledRejection', console.error);