Centralise message sending logic in one method, remove sendTTSMessage, add client shortcut in RESTMethods (#1031)

* start wip rewrite of sending/editing messages

* pass the build, modify the edit method to fit the new system

* simplify the applyToClass method

* change handling of file options

* add back message splitting

* i couldn't help myself

* add some smart message options

* clean up, add sexy options

* fix indentation

* fix up splitting

* add \b

* add back old methods for hydar happiness

* clean up more

* move code handling to the rest method

* clean up this.rest.client

* Update TextBasedChannel.js

* add docs back for the bad methods

* fix reply in group dms

* srsly gawdl3y

* make code better

* fix changes for gawdl3y

* fix checking

* remove getter

* make code handling more robust

* k

* fix up sendEmbed docs

* stupid

* fix up more docs because aaaaa

* no more pls
This commit is contained in:
Gus Caplan
2016-12-28 22:58:30 -06:00
committed by Schuyler Cebulskie
parent a014b59790
commit ed8fcf651a
6 changed files with 201 additions and 198 deletions

View File

@@ -2,6 +2,7 @@ const Constants = require('../../util/Constants');
const Collection = require('../../util/Collection'); const Collection = require('../../util/Collection');
const splitMessage = require('../../util/SplitMessage'); const splitMessage = require('../../util/SplitMessage');
const parseEmoji = require('../../util/ParseEmoji'); const parseEmoji = require('../../util/ParseEmoji');
const escapeMarkdown = require('../../util/EscapeMarkdown');
const User = require('../../structures/User'); const User = require('../../structures/User');
const GuildMember = require('../../structures/GuildMember'); const GuildMember = require('../../structures/GuildMember');
@@ -15,13 +16,14 @@ const ClientOAuth2Application = require('../../structures/ClientOAuth2Applicatio
class RESTMethods { class RESTMethods {
constructor(restManager) { constructor(restManager) {
this.rest = restManager; this.rest = restManager;
this.client = restManager.client;
} }
login(token = this.rest.client.token) { login(token = this.client.token) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN); if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN);
token = token.replace(/^Bot\s*/i, ''); token = token.replace(/^Bot\s*/i, '');
this.rest.client.manager.connectToWebSocket(token, resolve, reject); this.client.manager.connectToWebSocket(token, resolve, reject);
}); });
} }
@@ -31,8 +33,8 @@ class RESTMethods {
getGateway() { getGateway() {
return this.rest.makeRequest('get', Constants.Endpoints.gateway, true).then(res => { return this.rest.makeRequest('get', Constants.Endpoints.gateway, true).then(res => {
this.rest.client.ws.gateway = `${res.url}/?v=${Constants.PROTOCOL_VERSION}`; this.client.ws.gateway = `${res.url}/?v=${Constants.PROTOCOL_VERSION}`;
return this.rest.client.ws.gateway; return this.client.ws.gateway;
}); });
} }
@@ -40,63 +42,64 @@ class RESTMethods {
return this.rest.makeRequest('get', Constants.Endpoints.botGateway, true); return this.rest.makeRequest('get', Constants.Endpoints.botGateway, true);
} }
sendMessage(channel, content, { tts, nonce, embed, disableEveryone, split } = {}, file = null) { sendMessage(channel, content, { tts, nonce, embed, disableEveryone, split, code } = {}, file = null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
if (content) { if (content) {
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) { if (code) {
content = escapeMarkdown(this.client.resolver.resolveString(content), true);
content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``;
}
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
content = content.replace(/@(everyone|here)/g, '@\u200b$1'); content = content.replace(/@(everyone|here)/g, '@\u200b$1');
} }
if (split) content = splitMessage(content, typeof split === 'object' ? split : {}); if (split) content = splitMessage(content, typeof split === 'object' ? split : {});
} }
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 ? file : null).then((message) => {
messages.push(message);
if (index >= list.length) return resolve(messages);
return sendChunk(list, ++index);
});
}(content, 0));
} else {
this.rest.makeRequest('post', Constants.Endpoints.channelMessages(chan.id), true, {
content, tts, nonce, embed,
}, file).then(data => resolve(this.client.actions.MessageCreate.handle(data).message), reject);
}
};
if (channel instanceof User || channel instanceof GuildMember) { if (channel instanceof User || channel instanceof GuildMember) {
this.createDM(channel).then(chan => { this.createDM(channel).then(send, reject);
this._sendMessageRequest(chan, content, file, tts, nonce, embed, resolve, reject);
}, reject);
} else { } else {
this._sendMessageRequest(channel, content, file, tts, nonce, embed, resolve, reject); send(channel);
} }
}); });
} }
_sendMessageRequest(channel, content, file, tts, nonce, embed, resolve, reject) { updateMessage(message, content, { embed, code } = {}) {
if (content instanceof Array) { content = this.client.resolver.resolveString(content);
const datas = []; if (code) {
let promise = this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { content = escapeMarkdown(this.client.resolver.resolveString(content), true);
content: content[0], tts, nonce, content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``;
}, file).catch(reject);
for (let i = 1; i <= content.length; i++) {
if (i < content.length) {
const i2 = i;
promise = promise.then(data => {
datas.push(data);
return this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, {
content: content[i2], tts, nonce, embed,
}, file);
}, reject);
} else {
promise.then(data => {
datas.push(data);
resolve(this.rest.client.actions.MessageCreate.handle(datas).messages);
}, reject);
}
}
} else {
this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, {
content, tts, nonce, embed,
}, file)
.then(data => resolve(this.rest.client.actions.MessageCreate.handle(data).message), reject);
} }
return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, {
content, embed,
}).then(data => this.client.actions.MessageUpdate.handle(data).updated);
} }
deleteMessage(message) { deleteMessage(message) {
return this.rest.makeRequest('del', Constants.Endpoints.channelMessage(message.channel.id, message.id), true) return this.rest.makeRequest('del', Constants.Endpoints.channelMessage(message.channel.id, message.id), true)
.then(() => .then(() =>
this.rest.client.actions.MessageDelete.handle({ this.client.actions.MessageDelete.handle({
id: message.id, id: message.id,
channel_id: message.channel.id, channel_id: message.channel.id,
}).message }).message
@@ -107,39 +110,32 @@ class RESTMethods {
return this.rest.makeRequest('post', `${Constants.Endpoints.channelMessages(channel.id)}/bulk_delete`, true, { return this.rest.makeRequest('post', `${Constants.Endpoints.channelMessages(channel.id)}/bulk_delete`, true, {
messages, messages,
}).then(() => }).then(() =>
this.rest.client.actions.MessageDeleteBulk.handle({ this.client.actions.MessageDeleteBulk.handle({
channel_id: channel.id, channel_id: channel.id,
ids: messages, ids: messages,
}).messages }).messages
); );
} }
updateMessage(message, content, { embed } = {}) {
content = this.rest.client.resolver.resolveString(content);
return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, {
content, embed,
}).then(data => this.rest.client.actions.MessageUpdate.handle(data).updated);
}
createChannel(guild, channelName, channelType, overwrites) { createChannel(guild, channelName, channelType, overwrites) {
if (overwrites instanceof Collection) overwrites = overwrites.array(); if (overwrites instanceof Collection) overwrites = overwrites.array();
return this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, { return this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, {
name: channelName, name: channelName,
type: channelType, type: channelType,
permission_overwrites: overwrites, permission_overwrites: overwrites,
}).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); }).then(data => this.client.actions.ChannelCreate.handle(data).channel);
} }
createDM(recipient) { createDM(recipient) {
const dmChannel = this.getExistingDM(recipient); const dmChannel = this.getExistingDM(recipient);
if (dmChannel) return Promise.resolve(dmChannel); if (dmChannel) return Promise.resolve(dmChannel);
return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.rest.client.user.id), true, { return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.client.user.id), true, {
recipient_id: recipient.id, recipient_id: recipient.id,
}).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); }).then(data => this.client.actions.ChannelCreate.handle(data).channel);
} }
getExistingDM(recipient) { getExistingDM(recipient) {
return this.rest.client.channels.find(channel => return this.client.channels.find(channel =>
channel.recipient && channel.recipient.id === recipient.id channel.recipient && channel.recipient.id === recipient.id
); );
} }
@@ -149,7 +145,7 @@ class RESTMethods {
if (!channel) return Promise.reject(new Error('No channel to delete.')); if (!channel) return Promise.reject(new Error('No channel to delete.'));
return this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => { return this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => {
data.id = channel.id; data.id = channel.id;
return this.rest.client.actions.ChannelDelete.handle(data).channel; return this.client.actions.ChannelDelete.handle(data).channel;
}); });
} }
@@ -161,38 +157,38 @@ class RESTMethods {
data.bitrate = _data.bitrate || channel.bitrate; data.bitrate = _data.bitrate || channel.bitrate;
data.user_limit = _data.userLimit || channel.userLimit; data.user_limit = _data.userLimit || channel.userLimit;
return this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => return this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData =>
this.rest.client.actions.ChannelUpdate.handle(newData).updated this.client.actions.ChannelUpdate.handle(newData).updated
); );
} }
leaveGuild(guild) { leaveGuild(guild) {
if (guild.ownerID === this.rest.client.user.id) return Promise.reject(new Error('Guild is owned by the client.')); if (guild.ownerID === this.client.user.id) return Promise.reject(new Error('Guild is owned by the client.'));
return this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() => return this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() =>
this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild this.client.actions.GuildDelete.handle({ id: guild.id }).guild
); );
} }
createGuild(options) { createGuild(options) {
options.icon = this.rest.client.resolver.resolveBase64(options.icon) || null; options.icon = this.client.resolver.resolveBase64(options.icon) || null;
options.region = options.region || 'us-central'; options.region = options.region || 'us-central';
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.rest.makeRequest('post', Constants.Endpoints.guilds, true, options).then(data => { this.rest.makeRequest('post', Constants.Endpoints.guilds, true, options).then(data => {
if (this.rest.client.guilds.has(data.id)) { if (this.client.guilds.has(data.id)) {
resolve(this.rest.client.guilds.get(data.id)); resolve(this.client.guilds.get(data.id));
return; return;
} }
const handleGuild = guild => { const handleGuild = guild => {
if (guild.id === data.id) { if (guild.id === data.id) {
this.rest.client.removeListener('guildCreate', handleGuild); this.client.removeListener('guildCreate', handleGuild);
this.rest.client.clearTimeout(timeout); this.client.clearTimeout(timeout);
resolve(guild); resolve(guild);
} }
}; };
this.rest.client.on('guildCreate', handleGuild); this.client.on('guildCreate', handleGuild);
const timeout = this.rest.client.setTimeout(() => { const timeout = this.client.setTimeout(() => {
this.rest.client.removeListener('guildCreate', handleGuild); this.client.removeListener('guildCreate', handleGuild);
reject(new Error('Took too long to receive guild data.')); reject(new Error('Took too long to receive guild data.'));
}, 10000); }, 10000);
}, reject); }, reject);
@@ -202,28 +198,28 @@ class RESTMethods {
// untested but probably will work // untested but probably will work
deleteGuild(guild) { deleteGuild(guild) {
return this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() => return this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() =>
this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild this.client.actions.GuildDelete.handle({ id: guild.id }).guild
); );
} }
getUser(userID) { getUser(userID) {
return this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data => return this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data =>
this.rest.client.actions.UserGet.handle(data).user this.client.actions.UserGet.handle(data).user
); );
} }
updateCurrentUser(_data, password) { updateCurrentUser(_data, password) {
const user = this.rest.client.user; const user = this.client.user;
const data = {}; const data = {};
data.username = _data.username || user.username; data.username = _data.username || user.username;
data.avatar = this.rest.client.resolver.resolveBase64(_data.avatar) || user.avatar; data.avatar = this.client.resolver.resolveBase64(_data.avatar) || user.avatar;
if (!user.bot) { if (!user.bot) {
data.email = _data.email || user.email; data.email = _data.email || user.email;
data.password = password; data.password = password;
if (_data.new_password) data.new_password = _data.newPassword; if (_data.new_password) data.new_password = _data.newPassword;
} }
return this.rest.makeRequest('patch', Constants.Endpoints.me, true, data).then(newData => return this.rest.makeRequest('patch', Constants.Endpoints.me, true, data).then(newData =>
this.rest.client.actions.UserUpdate.handle(newData).updated this.client.actions.UserUpdate.handle(newData).updated
); );
} }
@@ -232,19 +228,19 @@ class RESTMethods {
if (_data.name) data.name = _data.name; if (_data.name) data.name = _data.name;
if (_data.region) data.region = _data.region; if (_data.region) data.region = _data.region;
if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel); if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel);
if (_data.afkChannel) data.afk_channel_id = this.rest.client.resolver.resolveChannel(_data.afkChannel).id; 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.afkTimeout) data.afk_timeout = Number(_data.afkTimeout);
if (_data.icon) data.icon = this.rest.client.resolver.resolveBase64(_data.icon); if (_data.icon) data.icon = this.client.resolver.resolveBase64(_data.icon);
if (_data.owner) data.owner_id = this.rest.client.resolver.resolveUser(_data.owner).id; if (_data.owner) data.owner_id = this.client.resolver.resolveUser(_data.owner).id;
if (_data.splash) data.splash = this.rest.client.resolver.resolveBase64(_data.splash); if (_data.splash) data.splash = this.client.resolver.resolveBase64(_data.splash);
return this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data).then(newData => return this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data).then(newData =>
this.rest.client.actions.GuildUpdate.handle(newData).updated this.client.actions.GuildUpdate.handle(newData).updated
); );
} }
kickGuildMember(guild, member) { kickGuildMember(guild, member) {
return this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true).then(() => return this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true).then(() =>
this.rest.client.actions.GuildMemberRemove.handle({ this.client.actions.GuildMemberRemove.handle({
guild_id: guild.id, guild_id: guild.id,
user: member.user, user: member.user,
}).member }).member
@@ -253,7 +249,7 @@ class RESTMethods {
createGuildRole(guild) { createGuildRole(guild) {
return this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true).then(role => return this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true).then(role =>
this.rest.client.actions.GuildRoleCreate.handle({ this.client.actions.GuildRoleCreate.handle({
guild_id: guild.id, guild_id: guild.id,
role, role,
}).role }).role
@@ -262,7 +258,7 @@ class RESTMethods {
deleteGuildRole(role) { deleteGuildRole(role) {
return this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true).then(() => return this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true).then(() =>
this.rest.client.actions.GuildRoleDelete.handle({ this.client.actions.GuildRoleDelete.handle({
guild_id: role.guild.id, guild_id: role.guild.id,
role_id: role.id, role_id: role.id,
}).role }).role
@@ -301,17 +297,17 @@ class RESTMethods {
getGuildMember(guild, user) { getGuildMember(guild, user) {
return this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data => return this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data =>
this.rest.client.actions.GuildMemberGet.handle(guild, data).member this.client.actions.GuildMemberGet.handle(guild, data).member
); );
} }
updateGuildMember(member, data) { updateGuildMember(member, data) {
if (data.channel) data.channel_id = this.rest.client.resolver.resolveChannel(data.channel).id; if (data.channel) data.channel_id = this.client.resolver.resolveChannel(data.channel).id;
if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role);
let endpoint = Constants.Endpoints.guildMember(member.guild.id, member.id); let endpoint = Constants.Endpoints.guildMember(member.guild.id, member.id);
// fix your endpoints, discord ;-; // fix your endpoints, discord ;-;
if (member.id === this.rest.client.user.id) { if (member.id === this.client.user.id) {
const keys = Object.keys(data); const keys = Object.keys(data);
if (keys.length === 1 && keys[0] === 'nick') { if (keys.length === 1 && keys[0] === 'nick') {
endpoint = Constants.Endpoints.guildMemberNickname(member.guild.id); endpoint = Constants.Endpoints.guildMemberNickname(member.guild.id);
@@ -348,7 +344,7 @@ class RESTMethods {
} }
banGuildMember(guild, member, deleteDays = 0) { banGuildMember(guild, member, deleteDays = 0) {
const id = this.rest.client.resolver.resolveUserID(member); const id = this.client.resolver.resolveUserID(member);
if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.')); if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.'));
return this.rest.makeRequest( return this.rest.makeRequest(
'put', `${Constants.Endpoints.guildBans(guild.id)}/${id}?delete-message-days=${deleteDays}`, true, { 'put', `${Constants.Endpoints.guildBans(guild.id)}/${id}?delete-message-days=${deleteDays}`, true, {
@@ -356,9 +352,9 @@ class RESTMethods {
} }
).then(() => { ).then(() => {
if (member instanceof GuildMember) return member; if (member instanceof GuildMember) return member;
const user = this.rest.client.resolver.resolveUser(id); const user = this.client.resolver.resolveUser(id);
if (user) { if (user) {
member = this.rest.client.resolver.resolveGuildMember(guild, user); member = this.client.resolver.resolveGuildMember(guild, user);
return member || user; return member || user;
} }
return id; return id;
@@ -367,26 +363,26 @@ class RESTMethods {
unbanGuildMember(guild, member) { unbanGuildMember(guild, member) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const id = this.rest.client.resolver.resolveUserID(member); const id = this.client.resolver.resolveUserID(member);
if (!id) throw new Error('Couldn\'t resolve the user ID to unban.'); if (!id) throw new Error('Couldn\'t resolve the user ID to unban.');
const listener = (eGuild, eUser) => { const listener = (eGuild, eUser) => {
if (eGuild.id === guild.id && eUser.id === id) { if (eGuild.id === guild.id && eUser.id === id) {
this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
this.rest.client.clearTimeout(timeout); this.client.clearTimeout(timeout);
resolve(eUser); resolve(eUser);
} }
}; };
this.rest.client.on(Constants.Events.GUILD_BAN_REMOVE, listener); this.client.on(Constants.Events.GUILD_BAN_REMOVE, listener);
const timeout = this.rest.client.setTimeout(() => { const timeout = this.client.setTimeout(() => {
this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
reject(new Error('Took too long to receive the ban remove event.')); reject(new Error('Took too long to receive the ban remove event.'));
}, 10000); }, 10000);
this.rest.makeRequest('del', `${Constants.Endpoints.guildBans(guild.id)}/${id}`, true).catch(err => { this.rest.makeRequest('del', `${Constants.Endpoints.guildBans(guild.id)}/${id}`, true).catch(err => {
this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
this.rest.client.clearTimeout(timeout); this.client.clearTimeout(timeout);
reject(err); reject(err);
}); });
}); });
@@ -396,7 +392,7 @@ class RESTMethods {
return this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true).then(banItems => { return this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true).then(banItems => {
const bannedUsers = new Collection(); const bannedUsers = new Collection();
for (const banItem of banItems) { for (const banItem of banItems) {
const user = this.rest.client.dataManager.newUser(banItem.user); const user = this.client.dataManager.newUser(banItem.user);
bannedUsers.set(user.id, user); bannedUsers.set(user.id, user);
} }
return bannedUsers; return bannedUsers;
@@ -428,7 +424,7 @@ class RESTMethods {
return this.rest.makeRequest( return this.rest.makeRequest(
'patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data 'patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data
).then(_role => ).then(_role =>
this.rest.client.actions.GuildRoleUpdate.handle({ this.client.actions.GuildRoleUpdate.handle({
role: _role, role: _role,
guild_id: role.guild.id, guild_id: role.guild.id,
}).updated }).updated
@@ -455,7 +451,7 @@ class RESTMethods {
payload.max_age = options.maxAge; payload.max_age = options.maxAge;
payload.max_uses = options.maxUses; payload.max_uses = options.maxUses;
return this.rest.makeRequest('post', `${Constants.Endpoints.channelInvites(channel.id)}`, true, payload) return this.rest.makeRequest('post', `${Constants.Endpoints.channelInvites(channel.id)}`, true, payload)
.then(invite => new Invite(this.rest.client, invite)); .then(invite => new Invite(this.client, invite));
} }
deleteInvite(invite) { deleteInvite(invite) {
@@ -464,7 +460,7 @@ class RESTMethods {
getInvite(code) { getInvite(code) {
return this.rest.makeRequest('get', Constants.Endpoints.invite(code), true).then(invite => return this.rest.makeRequest('get', Constants.Endpoints.invite(code), true).then(invite =>
new Invite(this.rest.client, invite) new Invite(this.client, invite)
); );
} }
@@ -472,7 +468,7 @@ class RESTMethods {
return this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true).then(inviteItems => { return this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true).then(inviteItems => {
const invites = new Collection(); const invites = new Collection();
for (const inviteItem of inviteItems) { for (const inviteItem of inviteItems) {
const invite = new Invite(this.rest.client, inviteItem); const invite = new Invite(this.client, inviteItem);
invites.set(invite.code, invite); invites.set(invite.code, invite);
} }
return invites; return invites;
@@ -486,24 +482,24 @@ class RESTMethods {
createEmoji(guild, image, name) { createEmoji(guild, image, name) {
return this.rest.makeRequest('post', `${Constants.Endpoints.guildEmojis(guild.id)}`, true, { name, image }) return this.rest.makeRequest('post', `${Constants.Endpoints.guildEmojis(guild.id)}`, true, { name, image })
.then(data => this.rest.client.actions.EmojiCreate.handle(data, guild).emoji); .then(data => this.client.actions.EmojiCreate.handle(data, guild).emoji);
} }
deleteEmoji(emoji) { deleteEmoji(emoji) {
return this.rest.makeRequest('delete', `${Constants.Endpoints.guildEmojis(emoji.guild.id)}/${emoji.id}`, true) return this.rest.makeRequest('delete', `${Constants.Endpoints.guildEmojis(emoji.guild.id)}/${emoji.id}`, true)
.then(() => this.rest.client.actions.EmojiDelete.handle(emoji).data); .then(() => this.client.actions.EmojiDelete.handle(emoji).data);
} }
getWebhook(id, token) { getWebhook(id, token) {
return this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), !token).then(data => return this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), !token).then(data =>
new Webhook(this.rest.client, data) new Webhook(this.client, data)
); );
} }
getGuildWebhooks(guild) { getGuildWebhooks(guild) {
return this.rest.makeRequest('get', Constants.Endpoints.guildWebhooks(guild.id), true).then(data => { return this.rest.makeRequest('get', Constants.Endpoints.guildWebhooks(guild.id), true).then(data => {
const hooks = new Collection(); const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.rest.client, hook)); for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks; return hooks;
}); });
} }
@@ -511,14 +507,14 @@ class RESTMethods {
getChannelWebhooks(channel) { getChannelWebhooks(channel) {
return this.rest.makeRequest('get', Constants.Endpoints.channelWebhooks(channel.id), true).then(data => { return this.rest.makeRequest('get', Constants.Endpoints.channelWebhooks(channel.id), true).then(data => {
const hooks = new Collection(); const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.rest.client, hook)); for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks; return hooks;
}); });
} }
createWebhook(channel, name, avatar) { createWebhook(channel, name, avatar) {
return this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, { name, avatar }) return this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, { name, avatar })
.then(data => new Webhook(this.rest.client, data)); .then(data => new Webhook(this.client, data));
} }
editWebhook(webhook, name, avatar) { editWebhook(webhook, name, avatar) {
@@ -537,9 +533,9 @@ class RESTMethods {
} }
sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds } = {}, file = null) { sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds } = {}, file = null) {
if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content);
if (content) { if (content) {
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) { if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) {
content = content.replace(/@(everyone|here)/g, '@\u200b$1'); content = content.replace(/@(everyone|here)/g, '@\u200b$1');
} }
} }
@@ -570,7 +566,7 @@ class RESTMethods {
return this.rest.makeRequest( return this.rest.makeRequest(
'get', 'get',
Constants.Endpoints.meMentions(options.limit, options.roles, options.everyone, options.guild) Constants.Endpoints.meMentions(options.limit, options.roles, options.everyone, options.guild)
).then(res => res.body.map(m => new Message(this.rest.client.channels.get(m.channel_id), m, this.rest.client))); ).then(res => res.body.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client)));
} }
addFriend(user) { addFriend(user) {
@@ -597,7 +593,7 @@ class RESTMethods {
setRolePositions(guildID, roles) { setRolePositions(guildID, roles) {
return this.rest.makeRequest('patch', Constants.Endpoints.guildRoles(guildID), true, roles).then(() => return this.rest.makeRequest('patch', Constants.Endpoints.guildRoles(guildID), true, roles).then(() =>
this.rest.client.actions.GuildRolesPositionUpdate.handle({ this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: guildID, guild_id: guildID,
roles, roles,
}).guild }).guild
@@ -608,8 +604,8 @@ class RESTMethods {
return this.rest.makeRequest( return this.rest.makeRequest(
'put', Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji), true 'put', Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji), true
).then(() => ).then(() =>
this.rest.client.actions.MessageReactionAdd.handle({ this.client.actions.MessageReactionAdd.handle({
user_id: this.rest.client.user.id, user_id: this.client.user.id,
message_id: message.id, message_id: message.id,
emoji: parseEmoji(emoji), emoji: parseEmoji(emoji),
channel_id: message.channel.id, channel_id: message.channel.id,
@@ -619,11 +615,11 @@ class RESTMethods {
removeMessageReaction(message, emoji, user) { removeMessageReaction(message, emoji, user) {
let endpoint = Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji); let endpoint = Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji);
if (user.id !== this.rest.client.user.id) { if (user.id !== this.client.user.id) {
endpoint = Constants.Endpoints.userMessageReaction(message.channel.id, message.id, emoji, null, user.id); endpoint = Constants.Endpoints.userMessageReaction(message.channel.id, message.id, emoji, null, user.id);
} }
return this.rest.makeRequest('delete', endpoint, true).then(() => return this.rest.makeRequest('delete', endpoint, true).then(() =>
this.rest.client.actions.MessageReactionRemove.handle({ this.client.actions.MessageReactionRemove.handle({
user_id: user.id, user_id: user.id,
message_id: message.id, message_id: message.id,
emoji: parseEmoji(emoji), emoji: parseEmoji(emoji),
@@ -645,7 +641,7 @@ class RESTMethods {
getMyApplication() { getMyApplication() {
return this.rest.makeRequest('get', Constants.Endpoints.myApplication, true).then(app => return this.rest.makeRequest('get', Constants.Endpoints.myApplication, true).then(app =>
new ClientOAuth2Application(this.rest.client, app) new ClientOAuth2Application(this.client, app)
); );
} }

View File

@@ -430,8 +430,8 @@ class GuildMember {
} }
// These are here only for documentation purposes - they are implemented by TextBasedChannel // These are here only for documentation purposes - they are implemented by TextBasedChannel
send() { return; }
sendMessage() { return; } sendMessage() { return; }
sendTTSMessage() { return; }
sendEmbed() { return; } sendEmbed() { return; }
sendFile() { return; } sendFile() { return; }
sendCode() { return; } sendCode() { return; }

View File

@@ -369,12 +369,13 @@ class Message {
* Options that can be passed into editMessage * Options that can be passed into editMessage
* @typedef {Object} MessageEditOptions * @typedef {Object} MessageEditOptions
* @property {Object} [embed] An embed to be added/edited * @property {Object} [embed] An embed to be added/edited
* @property {string} [code] Language for optional codeblock formatting to apply
*/ */
/** /**
* Edit the content of the message * Edit the content of the message
* @param {StringResolvable} content The new content for the message * @param {StringResolvable} [content] The new content for the message
* @param {MessageEditOptions} [options={}] The options to provide * @param {MessageEditOptions} [options] The options to provide
* @returns {Promise<Message>} * @returns {Promise<Message>}
* @example * @example
* // update the content of a message * // update the content of a message
@@ -382,7 +383,13 @@ class Message {
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`)) * .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
* .catch(console.error); * .catch(console.error);
*/ */
edit(content, options = {}) { edit(content, options) {
if (!options && typeof content === 'object') {
options = content;
content = '';
} else if (!options) {
options = {};
}
return this.client.rest.methods.updateMessage(this, content, options); return this.client.rest.methods.updateMessage(this, content, options);
} }
@@ -467,16 +474,8 @@ class Message {
* .catch(console.error); * .catch(console.error);
*/ */
reply(content, options = {}) { reply(content, options = {}) {
content = this.client.resolver.resolveString(content); content = `${this.guild || this.channel.type === 'group' ? `${this.author}, ` : ''}${content}`;
const prepend = this.guild ? `${this.author}, ` : ''; return this.channel.send(content, options);
content = `${prepend}${content}`;
if (options.split) {
if (typeof options.split !== 'object') options.split = {};
if (!options.split.prepend) options.split.prepend = prepend;
}
return this.client.rest.methods.sendMessage(this.channel, content, options);
} }
/** /**

View File

@@ -73,8 +73,8 @@ class TextChannel extends GuildChannel {
} }
// These are here only for documentation purposes - they are implemented by TextBasedChannel // These are here only for documentation purposes - they are implemented by TextBasedChannel
send() { return; }
sendMessage() { return; } sendMessage() { return; }
sendTTSMessage() { return; }
sendEmbed() { return; } sendEmbed() { return; }
sendFile() { return; } sendFile() { return; }
sendCode() { return; } sendCode() { return; }

View File

@@ -265,8 +265,8 @@ class User {
} }
// These are here only for documentation purposes - they are implemented by TextBasedChannel // These are here only for documentation purposes - they are implemented by TextBasedChannel
send() { return; }
sendMessage() { return; } sendMessage() { return; }
sendTTSMessage() { return; }
sendEmbed() { return; } sendEmbed() { return; }
sendFile() { return; } sendFile() { return; }
sendCode() { return; } sendCode() { return; }

View File

@@ -2,8 +2,7 @@ const path = require('path');
const Message = require('../Message'); const Message = require('../Message');
const MessageCollector = require('../MessageCollector'); const MessageCollector = require('../MessageCollector');
const Collection = require('../../util/Collection'); const Collection = require('../../util/Collection');
const RichEmbed = require('../RichEmbed');
const escapeMarkdown = require('../../util/EscapeMarkdown');
/** /**
* Interface for classes that have text-channel-like features * Interface for classes that have text-channel-like features
@@ -25,7 +24,7 @@ class TextBasedChannel {
} }
/** /**
* Options that can be passed into sendMessage, sendTTSMessage, sendFile, sendCode, or Message.reply * Options that can be passed into send, sendMessage, sendFile, sendEmbed, sendCode, and Message#reply
* @typedef {Object} MessageOptions * @typedef {Object} MessageOptions
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
* @property {string} [nonce=''] The nonce for the message * @property {string} [nonce=''] The nonce for the message
@@ -33,10 +32,18 @@ class TextBasedChannel {
* (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details)
* @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here * @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here
* should be replaced with plain-text * should be replaced with plain-text
* @property {FileOptions|string} [file] A file to send with the message
* @property {string} [code] Language for optional codeblock formatting to apply
* @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
* it exceeds the character limit. If an object is provided, these are the options for splitting the message. * it exceeds the character limit. If an object is provided, these are the options for splitting the message.
*/ */
/**
* @typedef {Object} FileOptions
* @property {BufferResolvable} attachment
* @property {string} [name='file.jpg']
*/
/** /**
* Options for splitting a message * Options for splitting a message
* @typedef {Object} SplitOptions * @typedef {Object} SplitOptions
@@ -46,6 +53,45 @@ class TextBasedChannel {
* @property {string} [append=''] Text to append to every piece except the last * @property {string} [append=''] Text to append to every piece except the last
*/ */
/**
* Send a message to this channel
* @param {StringResolvable} [content] The content to send
* @param {MessageOptions} [options={}] The options to provide
* @returns {Promise<Message|Message[]>}
* @example
* // send a message
* channel.send('hello!')
* .then(message => console.log(`Sent message: ${message.content}`))
* .catch(console.error);
*/
send(content, options) {
if (!options && typeof content === 'object') {
options = content;
content = '';
} else if (!options) {
options = {};
}
if (options.file) {
if (typeof options.file === 'string') options.file = { attachment: options.file };
if (!options.file.name) {
if (typeof options.file.attachment === 'string') {
options.file.name = path.basename(options.file.attachment);
} else if (options.file.attachment && options.file.attachment.path) {
options.file.name = path.basename(options.file.attachment.path);
} else {
options.file.name = 'file.jpg';
}
}
return this.client.resolver.resolveBuffer(options.file.attachment).then(file =>
this.client.rest.methods.sendMessage(this, content, options, {
file,
name: options.file.name,
})
);
}
return this.client.rest.methods.sendMessage(this, content, options);
}
/** /**
* Send a message to this channel * Send a message to this channel
* @param {StringResolvable} content The content to send * @param {StringResolvable} content The content to send
@@ -57,88 +103,48 @@ class TextBasedChannel {
* .then(message => console.log(`Sent message: ${message.content}`)) * .then(message => console.log(`Sent message: ${message.content}`))
* .catch(console.error); * .catch(console.error);
*/ */
sendMessage(content, options = {}) { sendMessage(content, options) {
return this.client.rest.methods.sendMessage(this, content, options); return this.send(content, options);
}
/**
* Send a text-to-speech message to this channel
* @param {StringResolvable} content The content to send
* @param {MessageOptions} [options={}] The options to provide
* @returns {Promise<Message|Message[]>}
* @example
* // send a TTS message
* channel.sendTTSMessage('hello!')
* .then(message => console.log(`Sent tts message: ${message.content}`))
* .catch(console.error);
*/
sendTTSMessage(content, options = {}) {
Object.assign(options, { tts: true });
return this.client.rest.methods.sendMessage(this, content, options);
} }
/** /**
* Send an embed to this channel * Send an embed to this channel
* @param {RichEmbed|Object} embed The embed to send * @param {RichEmbed|Object} embed The embed to send
* @param {string|MessageOptions} contentOrOptions Content to send or message options * @param {string} [content] Content to send
* @param {MessageOptions} options If contentOrOptions is content, this will be options * @param {MessageOptions} [options] The options to provide
* @returns {Promise<Message>} * @returns {Promise<Message>}
*/ */
sendEmbed(embed, contentOrOptions, options = {}) { sendEmbed(embed, content, options) {
if (!(embed instanceof RichEmbed)) embed = new RichEmbed(embed); if (!options && typeof content === 'object') {
let content; options = content;
if (contentOrOptions) { content = '';
if (typeof contentOrOptions === 'string') { } else if (!options) {
content = contentOrOptions; options = {};
} else {
options = contentOrOptions;
}
} }
options.embed = embed; return this.send(content, Object.assign(options, { embed }));
return this.sendMessage(content, options);
} }
/** /**
* Send a file to this channel * Send a file to this channel
* @param {BufferResolvable} attachment The file to send * @param {BufferResolvable} attachment The file to send
* @param {string} [fileName="file.jpg"] The name and extension of the file * @param {string} [name='file.jpg'] The name and extension of the file
* @param {StringResolvable} [content] Text message to send with the attachment * @param {StringResolvable} [content] Text message to send with the attachment
* @param {MessageOptions} [options] The options to provide * @param {MessageOptions} [options] The options to provide
* @returns {Promise<Message>} * @returns {Promise<Message>}
*/ */
sendFile(attachment, fileName, content, options = {}) { sendFile(attachment, name, content, options = {}) {
if (!fileName) { return this.send(content, Object.assign(options, { file: { attachment, name } }));
if (typeof attachment === 'string') {
fileName = path.basename(attachment);
} else if (attachment && attachment.path) {
fileName = path.basename(attachment.path);
} else {
fileName = 'file.jpg';
}
}
return this.client.resolver.resolveBuffer(attachment).then(file =>
this.client.rest.methods.sendMessage(this, content, options, {
file,
name: fileName,
})
);
} }
/** /**
* Send a code block to this channel * Send a code block to this channel
* @param {string} lang Language for the code block * @param {string} lang Language for the code block
* @param {StringResolvable} content Content of the code block * @param {StringResolvable} content Content of the code block
* @param {MessageOptions} options The options to provide * @param {MessageOptions} [options] The options to provide
* @returns {Promise<Message|Message[]>} * @returns {Promise<Message|Message[]>}
*/ */
sendCode(lang, content, options = {}) { sendCode(lang, content, options = {}) {
if (options.split) { return this.send(content, Object.assign(options, { code: lang }));
if (typeof options.split !== 'object') options.split = {};
if (!options.split.prepend) options.split.prepend = `\`\`\`${lang || ''}\n`;
if (!options.split.append) options.split.append = '\n```';
}
content = escapeMarkdown(this.client.resolver.resolveString(content), true);
return this.sendMessage(`\`\`\`${lang || ''}\n${content}\n\`\`\``, options);
} }
/** /**
@@ -349,19 +355,21 @@ class TextBasedChannel {
} }
exports.applyToClass = (structure, full = false) => { exports.applyToClass = (structure, full = false) => {
const props = ['sendMessage', 'sendTTSMessage', 'sendEmbed', 'sendFile', 'sendCode']; const props = ['send', 'sendMessage', 'sendEmbed', 'sendFile', 'sendCode'];
if (full) { if (full) {
props.push('_cacheMessage'); props.push(
props.push('fetchMessages'); '_cacheMessage',
props.push('fetchMessage'); 'fetchMessages',
props.push('bulkDelete'); 'fetchMessage',
props.push('startTyping'); 'bulkDelete',
props.push('stopTyping'); 'startTyping',
props.push('typing'); 'stopTyping',
props.push('typingCount'); 'typing',
props.push('fetchPinnedMessages'); 'typingCount',
props.push('createCollector'); 'fetchPinnedMessages',
props.push('awaitMessages'); 'createCollector',
'awaitMessages'
);
} }
for (const prop of props) { for (const prop of props) {
Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop)); Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop));