refactor: remove user bot methods (#2559)

* [WIP] Remove user bots

* more backend userbot removal

* Add mfaEnabled back

* revert client presences store removal

* partially revert getAuth changes

* remove more no longer used children of ClientUserGuildSettings

* fix a bug with this pr and TextBasedChannel.applyToClass

* remove a syncGuilds reference

* more user bot data handling

* various guildSync cleanup

* bots can't call logout

Had the user/bot portions of the code mixed up. Though, does this need to be a promise anymore?

* make ClientManager#destroy() sync

It nolonger needs to be a promise, and nothing depended on it being a promise that I can tell.

* requested change

* Fix massive error

* no longer used as it's userbot only
This commit is contained in:
bdistin
2018-07-25 21:14:23 -05:00
committed by Isabella
parent f963621ef1
commit 5afd77ab73
34 changed files with 15 additions and 1103 deletions

View File

@@ -97,8 +97,7 @@ class Client extends BaseClient {
this.channels = new ChannelStore(this);
/**
* Presences that have been received for the client user's friends, mapped by user IDs
* <warn>This is only filled when using a user account.</warn>
* Presences that have been received for the client user, mapped by user IDs
* @type {ClientPresenceStore<Snowflake, Presence>}
*/
this.presences = new ClientPresenceStore(this);
@@ -106,7 +105,7 @@ class Client extends BaseClient {
Object.defineProperty(this, 'token', { writable: true });
if (!browser && !this.token && 'CLIENT_TOKEN' in process.env) {
/**
* Authorization token for the logged in user/bot
* Authorization token for the logged in bot
* <warn>This should be kept private at all times.</warn>
* @type {?string}
*/
@@ -240,10 +239,6 @@ class Client extends BaseClient {
/**
* Logs the client in, establishing a websocket connection to Discord.
* <info>Both bot and regular user accounts are supported, but it is highly recommended to use a bot account whenever
* possible. User accounts are subject to harsher ratelimits and other restrictions that don't apply to bot accounts.
* Bot accounts also have access to many features that user accounts cannot utilise. User accounts that are found to
* be abusing/overusing the API will be banned, locking you out of Discord entirely.</info>
* @param {string} token Token of the account to log in with
* @returns {Promise<string>} Token of the account used
* @example
@@ -262,27 +257,13 @@ class Client extends BaseClient {
/**
* Logs out, terminates the connection to Discord, and destroys the client.
* @returns {Promise}
* @returns {void}
*/
destroy() {
super.destroy();
return this.manager.destroy();
}
/**
* Requests a sync of guild data with Discord.
* <info>This can be done automatically every 30 seconds by enabling {@link ClientOptions#sync}.</info>
* <warn>This is only available when using a user account.</warn>
* @param {Guild[]|Collection<Snowflake, Guild>} [guilds=this.guilds] An array or collection of guilds to sync
*/
syncGuilds(guilds = this.guilds) {
if (this.user.bot) return;
this.ws.send({
op: 12,
d: guilds instanceof Collection ? guilds.keyArray() : guilds.map(g => g.id),
});
}
/**
* Obtains an invite from Discord.
* @param {InviteResolvable} invite Invite code or URL
@@ -369,22 +350,16 @@ class Client extends BaseClient {
}
/**
* Obtains the OAuth Application of the bot from Discord.
* @param {Snowflake} [id='@me'] ID of application to fetch
* Obtains the OAuth Application of this bot from Discord.
* @returns {Promise<ClientApplication>}
* @example
* client.fetchApplication('id')
* .then(application => console.log(`Obtained application with name: ${application.name}`)
* .catch(console.error);
*/
fetchApplication(id = '@me') {
return this.api.oauth2.applications(id).get()
fetchApplication() {
return this.api.oauth2.applications('@me').get()
.then(app => new ClientApplication(this, app));
}
/**
* Generates a link that can be used to invite the bot to a guild.
* <warn>This is only available when using a bot account.</warn>
* @param {PermissionResolvable} [permissions] Permissions to request
* @returns {Promise<string>}
* @example

View File

@@ -63,15 +63,7 @@ class ClientManager {
destroy() {
this.client.ws.destroy();
if (!this.client.user) return Promise.resolve();
if (this.client.user.bot) {
this.client.token = null;
return Promise.resolve();
} else {
return this.client.api.logout.post().then(() => {
this.client.token = null;
});
}
if (this.client.user) this.client.token = null;
}
}

View File

@@ -20,8 +20,6 @@ class ActionsManager {
this.register(require('./GuildRoleDelete'));
this.register(require('./GuildRoleUpdate'));
this.register(require('./UserUpdate'));
this.register(require('./UserNoteUpdate'));
this.register(require('./GuildSync'));
this.register(require('./GuildEmojiCreate'));
this.register(require('./GuildEmojiDelete'));
this.register(require('./GuildEmojiUpdate'));

View File

@@ -1,29 +0,0 @@
const Action = require('./Action');
class GuildSync extends Action {
handle(data) {
const client = this.client;
const guild = client.guilds.get(data.id);
if (guild) {
if (data.presences) {
for (const presence of data.presences) guild.presences.add(presence);
}
if (data.members) {
for (const syncMember of data.members) {
const member = guild.members.get(syncMember.user.id);
if (member) {
member._patch(syncMember);
} else {
guild.members.add(syncMember, false);
}
}
}
if ('large' in data) guild.large = data.large;
}
}
}
module.exports = GuildSync;

View File

@@ -1,30 +0,0 @@
const Action = require('./Action');
const { Events } = require('../../util/Constants');
class UserNoteUpdateAction extends Action {
handle(data) {
const client = this.client;
const oldNote = client.user.notes.get(data.id);
const note = data.note.length ? data.note : null;
client.user.notes.set(data.id, note);
client.emit(Events.USER_NOTE_UPDATE, data.id, oldNote, note);
return {
old: oldNote,
updated: note,
};
}
}
/**
* Emitted whenever a note is updated.
* @event Client#userNoteUpdate
* @param {User} user The user the note belongs to
* @param {string} oldNote The note content before the update
* @param {string} newNote The note content after the update
*/
module.exports = UserNoteUpdateAction;

View File

@@ -37,9 +37,6 @@ class WebSocketPacketManager {
this.register(WSEvents.CHANNEL_PINS_UPDATE, require('./handlers/ChannelPinsUpdate'));
this.register(WSEvents.PRESENCE_UPDATE, require('./handlers/PresenceUpdate'));
this.register(WSEvents.USER_UPDATE, require('./handlers/UserUpdate'));
this.register(WSEvents.USER_NOTE_UPDATE, require('./handlers/UserNoteUpdate'));
this.register(WSEvents.USER_SETTINGS_UPDATE, require('./handlers/UserSettingsUpdate'));
this.register(WSEvents.USER_GUILD_SETTINGS_UPDATE, require('./handlers/UserGuildSettingsUpdate'));
this.register(WSEvents.VOICE_STATE_UPDATE, require('./handlers/VoiceStateUpdate'));
this.register(WSEvents.TYPING_START, require('./handlers/TypingStart'));
this.register(WSEvents.MESSAGE_CREATE, require('./handlers/MessageCreate'));
@@ -47,9 +44,6 @@ class WebSocketPacketManager {
this.register(WSEvents.MESSAGE_UPDATE, require('./handlers/MessageUpdate'));
this.register(WSEvents.MESSAGE_DELETE_BULK, require('./handlers/MessageDeleteBulk'));
this.register(WSEvents.VOICE_SERVER_UPDATE, require('./handlers/VoiceServerUpdate'));
this.register(WSEvents.GUILD_SYNC, require('./handlers/GuildSync'));
this.register(WSEvents.RELATIONSHIP_ADD, require('./handlers/RelationshipAdd'));
this.register(WSEvents.RELATIONSHIP_REMOVE, require('./handlers/RelationshipRemove'));
this.register(WSEvents.MESSAGE_REACTION_ADD, require('./handlers/MessageReactionAdd'));
this.register(WSEvents.MESSAGE_REACTION_REMOVE, require('./handlers/MessageReactionRemove'));
this.register(WSEvents.MESSAGE_REACTION_REMOVE_ALL, require('./handlers/MessageReactionRemoveAll'));

View File

@@ -1,11 +0,0 @@
const AbstractHandler = require('./AbstractHandler');
class GuildSyncHandler extends AbstractHandler {
handle(packet) {
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildSync.handle(data);
}
}
module.exports = GuildSyncHandler;

View File

@@ -9,9 +9,6 @@ class ReadyHandler extends AbstractHandler {
client.ws.heartbeat();
data.user.user_settings = data.user_settings;
data.user.user_guild_settings = data.user_guild_settings;
if (!ClientUser) ClientUser = require('../../../../structures/ClientUser');
const clientUser = new ClientUser(client, data.user);
client.user = clientUser;
@@ -20,27 +17,8 @@ class ReadyHandler extends AbstractHandler {
for (const guild of data.guilds) client.guilds.add(guild);
for (const privateDM of data.private_channels) client.channels.add(privateDM);
for (const relation of data.relationships) {
const user = client.users.add(relation.user);
if (relation.type === 1) {
client.user.friends.set(user.id, user);
} else if (relation.type === 2) {
client.user.blocked.set(user.id, user);
}
}
for (const presence of data.presences || []) client.presences.add(presence);
if (data.notes) {
for (const user in data.notes) {
let note = data.notes[user];
if (!note.length) note = null;
client.user.notes.set(user, note);
}
}
if (!client.users.has('1')) {
client.users.add({
id: '1',
@@ -61,7 +39,6 @@ class ReadyHandler extends AbstractHandler {
client.setMaxListeners(data.guilds.length + 10);
client.once('ready', () => {
client.syncGuilds();
client.setMaxListeners(10);
client.clearTimeout(t);
});

View File

@@ -1,19 +0,0 @@
const AbstractHandler = require('./AbstractHandler');
class RelationshipAddHandler extends AbstractHandler {
handle(packet) {
const client = this.packetManager.client;
const data = packet.d;
if (data.type === 1) {
client.users.fetch(data.id).then(user => {
client.user.friends.set(user.id, user);
});
} else if (data.type === 2) {
client.users.fetch(data.id).then(user => {
client.user.blocked.set(user.id, user);
});
}
}
}
module.exports = RelationshipAddHandler;

View File

@@ -1,19 +0,0 @@
const AbstractHandler = require('./AbstractHandler');
class RelationshipRemoveHandler extends AbstractHandler {
handle(packet) {
const client = this.packetManager.client;
const data = packet.d;
if (data.type === 2) {
if (client.user.blocked.has(data.id)) {
client.user.blocked.delete(data.id);
}
} else if (data.type === 1) {
if (client.user.friends.has(data.id)) {
client.user.friends.delete(data.id);
}
}
}
}
module.exports = RelationshipRemoveHandler;

View File

@@ -1,21 +0,0 @@
const AbstractHandler = require('./AbstractHandler');
const { Events } = require('../../../../util/Constants');
const ClientUserGuildSettings = require('../../../../structures/ClientUserGuildSettings');
class UserGuildSettingsUpdateHandler extends AbstractHandler {
handle(packet) {
const client = this.packetManager.client;
const settings = client.user.guildSettings.get(packet.d.guild_id);
if (settings) settings.patch(packet.d);
else client.user.guildSettings.set(packet.d.guild_id, new ClientUserGuildSettings(this.client, packet.d));
client.emit(Events.USER_GUILD_SETTINGS_UPDATE, client.user.guildSettings.get(packet.d.guild_id));
}
}
/**
* Emitted whenever the client user's settings update.
* @event Client#clientUserGuildSettingsUpdate
* @param {ClientUserGuildSettings} clientUserGuildSettings The new client user guild settings
*/
module.exports = UserGuildSettingsUpdateHandler;

View File

@@ -1,12 +0,0 @@
const AbstractHandler = require('./AbstractHandler');
class UserNoteUpdateHandler extends AbstractHandler {
handle(packet) {
const client = this.packetManager.client;
const data = packet.d;
client.actions.UserNoteUpdate.handle(data);
}
}
module.exports = UserNoteUpdateHandler;

View File

@@ -1,18 +0,0 @@
const AbstractHandler = require('./AbstractHandler');
const { Events } = require('../../../../util/Constants');
class UserSettingsUpdateHandler extends AbstractHandler {
handle(packet) {
const client = this.packetManager.client;
client.user.settings.patch(packet.d);
client.emit(Events.USER_SETTINGS_UPDATE, client.user.settings);
}
}
/**
* Emitted whenever the client user's settings update.
* @event Client#clientUserSettingsUpdate
* @param {ClientUserSettings} clientUserSettings The new client user settings
*/
module.exports = UserSettingsUpdateHandler;

View File

@@ -6,8 +6,6 @@ const Messages = {
TOKEN_INVALID: 'An invalid token was provided.',
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
FEATURE_USER_ONLY: 'Only user accounts are able to make use of this feature.',
WS_CONNECTION_TIMEOUT: 'The connection to the gateway timed out.',
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
WS_NOT_OPEN: (data = 'data') => `Websocket not open to send ${data}`,

View File

@@ -56,9 +56,6 @@ module.exports = {
// This is a getter so that it properly extends any custom User class
return require('./structures/ClientUser');
},
ClientUserChannelOverride: require('./structures/ClientUserChannelOverride'),
ClientUserGuildSettings: require('./structures/ClientUserGuildSettings'),
ClientUserSettings: require('./structures/ClientUserSettings'),
Collector: require('./structures/interfaces/Collector'),
DMChannel: require('./structures/DMChannel'),
Emoji: require('./structures/Emoji'),

View File

@@ -25,7 +25,7 @@ class RESTManager {
getAuth() {
const token = this.client.token || this.client.accessToken;
const prefixed = !!this.client.application || (this.client.user && this.client.user.bot);
const prefixed = !!this.client.application || this.client.user;
if (token && prefixed) return `${this.tokenPrefix} ${token}`;
else if (token) return token;
throw new Error('TOKEN_MISSING');

View File

@@ -39,7 +39,7 @@ class GuildStore extends DataStore {
/**
* Creates a guild.
* <warn>This is only available to bots in less than 10 guilds and user accounts.</warn>
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
* @param {string} name The name of the guild
* @param {Object} [options] Options for the creating
* @param {string} [options.region] The region for the server, defaults to the closest one available

View File

@@ -1,7 +1,6 @@
const DataStore = require('./DataStore');
const Collection = require('../util/Collection');
const Message = require('../structures/Message');
const { Error } = require('../errors');
/**
* Stores messages for text-based channels.
@@ -80,12 +79,6 @@ class MessageStore extends DataStore {
}
async _fetchId(messageID) {
if (!this.client.user.bot) {
const messages = await this._fetchMany({ limit: 1, around: messageID });
const msg = messages.get(messageID);
if (!msg) throw new Error('MESSAGE_MISSING');
return msg;
}
const data = await this.client.api.channels[this.channel.id].messages[messageID].get();
return this.add(data);
}

View File

@@ -45,7 +45,6 @@ class UserStore extends DataStore {
/**
* Obtains a user from Discord, or the user cache if it's already available.
* <warn>This is only available when using a bot account.</warn>
* @param {Snowflake} id ID of the user
* @param {boolean} [cache=true] Whether to cache the new user object if it isn't already
* @returns {Promise<User>}

View File

@@ -170,26 +170,6 @@ class ClientApplication extends Base {
} });
}
/**
* Resets the app's secret.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<ClientApplication>}
*/
resetSecret() {
return this.client.api.oauth2.applications[this.id].reset.post()
.then(app => new ClientApplication(this.client, app));
}
/**
* Resets the app's bot token.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<ClientApplication>}
*/
resetToken() {
return this.client.api.oauth2.applications[this.id].bot.reset.post()
.then(app => new ClientApplication(this.client, Object.assign({}, this, { bot: app })));
}
/**
* When concatenated with a string, this automatically returns the application's name instead of the
* ClientApplication object.

View File

@@ -1,10 +1,5 @@
const Structures = require('../util/Structures');
const Collection = require('../util/Collection');
const ClientUserSettings = require('./ClientUserSettings');
const ClientUserGuildSettings = require('./ClientUserGuildSettings');
const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Guild = require('./Guild');
/**
* Represents the logged in client's Discord user.
@@ -20,75 +15,14 @@ class ClientUser extends Structures.get('User') {
*/
this.verified = data.verified;
/**
* The email of this account
* <warn>This is only filled when using a user account.</warn>
* @type {?string}
*/
this.email = data.email;
this._typing = new Map();
/**
* A Collection of friends for the logged in user
* <warn>This is only filled when using a user account.</warn>
* @type {Collection<Snowflake, User>}
*/
this.friends = new Collection();
/**
* A Collection of blocked users for the logged in user
* <warn>This is only filled when using a user account.</warn>
* @type {Collection<Snowflake, User>}
*/
this.blocked = new Collection();
/**
* A Collection of notes for the logged in user
* <warn>This is only filled when using a user account.</warn>
* @type {Collection<Snowflake, string>}
*/
this.notes = new Collection();
/**
* If the user has Discord premium (nitro)
* <warn>This is only filled when using a user account.</warn>
* @type {?boolean}
*/
this.premium = typeof data.premium === 'boolean' ? data.premium : null;
/**
* If the user has MFA enabled on their account
* <warn>This is only filled when using a user account.</warn>
* If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account
* @type {?boolean}
*/
this.mfaEnabled = typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null;
/**
* If the user has ever used a mobile device on Discord
* <warn>This is only filled when using a user account.</warn>
* @type {?boolean}
*/
this.mobile = typeof data.mobile === 'boolean' ? data.mobile : null;
/**
* Various settings for this user
* <warn>This is only filled when using a user account.</warn>
* @type {?ClientUserSettings}
*/
this.settings = data.user_settings ? new ClientUserSettings(this, data.user_settings) : null;
/**
* All of the user's guild settings
* <warn>This is only filled when using a user account.</warn>
* @type {Collection<Snowflake, ClientUserGuildSettings>}
*/
this.guildSettings = new Collection();
if (data.user_guild_settings) {
for (const settings of data.user_guild_settings) {
this.guildSettings.set(settings.guild_id, new ClientUserGuildSettings(this.client, settings));
}
}
if (data.token) this.client.token = data.token;
}
@@ -101,15 +35,7 @@ class ClientUser extends Structures.get('User') {
return this.client.presences.clientPresence;
}
edit(data, passcode) {
if (!this.bot) {
if (typeof passcode !== 'object') {
data.password = passcode;
} else {
data.code = passcode.mfaCode;
data.password = passcode.password;
}
}
edit(data) {
return this.client.api.users('@me').patch({ data })
.then(newData => {
this.client.token = newData.token;
@@ -122,7 +48,6 @@ class ClientUser extends Structures.get('User') {
* <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
* every hour. Use this sparingly!</info>
* @param {string} username The new username
* @param {string} [password] Current password (only for user accounts)
* @returns {Promise<ClientUser>}
* @example
* // Set username
@@ -130,43 +55,8 @@ class ClientUser extends Structures.get('User') {
* .then(user => console.log(`My new username is ${user.username}`))
* .catch(console.error);
*/
setUsername(username, password) {
return this.edit({ username }, password);
}
/**
* Changes the email for the client user's account.
* <warn>This is only available when using a user account.</warn>
* @param {string} email New email to change to
* @param {string} password Current password
* @returns {Promise<ClientUser>}
* @example
* // Set email
* client.user.setEmail('bob@gmail.com', 'some amazing password 123')
* .then(user => console.log(`My new email is ${user.email}`))
* .catch(console.error);
*/
setEmail(email, password) {
return this.edit({ email }, password);
}
/**
* Changes the password for the client user's account.
* <warn>This is only available when using a user account.</warn>
* @param {string} newPassword New password to change to
* @param {Object|string} options Object containing an MFA code, password or both.
* Can be just a string for the password.
* @param {string} [options.oldPassword] Current password
* @param {string} [options.mfaCode] Timed MFA Code
* @returns {Promise<ClientUser>}
* @example
* // Set password
* client.user.setPassword('some new amazing password 456', 'some amazing password 123')
* .then(user => console.log('New password set!'))
* .catch(console.error);
*/
setPassword(newPassword, options) {
return this.edit({ new_password: newPassword }, { password: options.oldPassword, mfaCode: options.mfaCode });
setUsername(username) {
return this.edit({ username });
}
/**
@@ -262,36 +152,6 @@ class ClientUser extends Structures.get('User') {
return this.setPresence({ afk });
}
/**
* Fetches messages that mentioned the client's user.
* <warn>This is only available when using a user account.</warn>
* @param {Object} [options={}] Options for the fetch
* @param {number} [options.limit=25] Maximum number of mentions to retrieve
* @param {boolean} [options.roles=true] Whether to include role mentions
* @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
* @param {GuildResolvable} [options.guild] Limit the search to a specific guild
* @returns {Promise<Message[]>}
* @example
* // Fetch mentions
* client.user.fetchMentions()
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch mentions from a guild
* client.user.fetchMentions({
* guild: '222078108977594368'
* })
* .then(console.log)
* .catch(console.error);
*/
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.client.api.users('@me').mentions.get({ query: options })
.then(data => data.map(m => this.client.channels.get(m.channel_id).messages.add(m, false)));
}
/**
* An object containing either a user or access token, and an optional nickname.
* @typedef {Object} GroupDMRecipientOptions
@@ -327,16 +187,6 @@ class ClientUser extends Structures.get('User') {
return this.client.api.users('@me').channels.post({ data })
.then(res => this.client.channels.add(res));
}
toJSON() {
return super.toJSON({
friends: false,
blocked: false,
notes: false,
settings: false,
guildSettings: false,
});
}
}
module.exports = ClientUser;

View File

@@ -1,28 +0,0 @@
const { UserChannelOverrideMap } = require('../util/Constants');
/**
* A wrapper around the ClientUser's channel overrides.
*/
class ClientUserChannelOverride {
constructor(data) {
this.patch(data);
}
/**
* Patch the data contained in this class with new partial data.
* @param {Object} data Data to patch this with
* @private
*/
patch(data) {
for (const [key, value] of Object.entries(UserChannelOverrideMap)) {
if (!data.hasOwnProperty(key)) continue;
if (typeof value === 'function') {
this[value.name] = value(data[key]);
} else {
this[value] = data[key];
}
}
}
}
module.exports = ClientUserChannelOverride;

View File

@@ -1,60 +0,0 @@
const { UserGuildSettingsMap } = require('../util/Constants');
const Collection = require('../util/Collection');
const ClientUserChannelOverride = require('./ClientUserChannelOverride');
/**
* A wrapper around the ClientUser's guild settings.
*/
class ClientUserGuildSettings {
constructor(client, data) {
/**
* The client that created the instance of the ClientUserGuildSettings
* @name ClientUserGuildSettings#client
* @type {Client}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* The ID of the guild these settings are for
* @type {Snowflake}
*/
this.guildID = data.guild_id;
this.channelOverrides = new Collection();
this.patch(data);
}
/**
* Patch the data contained in this class with new partial data.
* @param {Object} data Data to patch this with
* @private
*/
patch(data) {
for (const [key, value] of Object.entries(UserGuildSettingsMap)) {
if (!data.hasOwnProperty(key)) continue;
if (key === 'channel_overrides') {
for (const channel of data[key]) {
const override = this.channelOverrides.get(channel.channel_id);
if (override) override.patch(channel);
else this.channelOverrides.set(channel.channel_id, new ClientUserChannelOverride(channel));
}
} else if (typeof value === 'function') {
this[value.name] = value(data[key]);
} else {
this[value] = data[key];
}
}
}
/**
* Update a specific property of the guild settings.
* @param {string} name Name of property
* @param {*} value Value to patch
* @returns {Promise<Object>}
* @private
*/
update(name, value) {
return this.client.api.users('@me').guilds(this.guildID).settings.patch({ data: { [name]: value } });
}
}
module.exports = ClientUserGuildSettings;

View File

@@ -1,80 +0,0 @@
const { UserSettingsMap } = require('../util/Constants');
const Util = require('../util/Util');
const { Error } = require('../errors');
/**
* A wrapper around the ClientUser's settings.
*/
class ClientUserSettings {
constructor(user, data) {
this.user = user;
this.patch(data);
}
/**
* Patch the data contained in this class with new partial data.
* @param {Object} data Data to patch this with
* @private
*/
patch(data) {
for (const [key, value] of Object.entries(UserSettingsMap)) {
if (!data.hasOwnProperty(key)) continue;
if (typeof value === 'function') {
this[value.name] = value(data[key]);
} else {
this[value] = data[key];
}
}
}
/**
* Update a specific property of of user settings.
* @param {string} name Name of property
* @param {*} value Value to patch
* @returns {Promise<Object>}
* @private
*/
update(name, value) {
return this.user.client.api.users['@me'].settings.patch({ data: { [name]: value } });
}
/**
* Sets the position of the guild in the guild listing.
* @param {Guild} guild The guild to move
* @param {number} position Absolute or relative position
* @param {boolean} [relative=false] Whether to position relatively or absolutely
* @returns {Promise<Guild>}
*/
setGuildPosition(guild, position, relative) {
const temp = Object.assign([], this.guildPositions);
Util.moveElementInArray(temp, guild.id, position, relative);
return this.update('guild_positions', temp).then(() => guild);
}
/**
* Adds a guild to the list of restricted guilds.
* @param {Guild} guild The guild to add
* @returns {Promise<Guild>}
*/
addRestrictedGuild(guild) {
const temp = Object.assign([], this.restrictedGuilds);
if (temp.includes(guild.id)) return Promise.reject(new Error('GUILD_RESTRICTED', true));
temp.push(guild.id);
return this.update('restricted_guilds', temp).then(() => guild);
}
/**
* Removes a guild from the list of restricted guilds.
* @param {Guild} guild The guild to remove
* @returns {Promise<Guild>}
*/
removeRestrictedGuild(guild) {
const temp = Object.assign([], this.restrictedGuilds);
const index = temp.indexOf(guild.id);
if (index < 0) return Promise.reject(new Error('GUILD_RESTRICTED'));
temp.splice(index, 1);
return this.update('restricted_guilds', temp).then(() => guild);
}
}
module.exports = ClientUserSettings;

View File

@@ -184,16 +184,12 @@ class GroupDMChannel extends Channel {
* @param {Object} options Options for this method
* @param {UserResolvable} options.user User to add to this Group DM
* @param {string} [options.accessToken] Access token to use to add the user to this Group DM
* (only available under a bot account)
* @param {string} [options.nick] Permanent nickname to give the user (only available under a bot account)
* @param {string} [options.nick] Permanent nickname to give the user
* @returns {Promise<GroupDMChannel>}
*/
addUser({ user, accessToken, nick }) {
const id = this.client.users.resolveID(user);
const data = this.client.user.bot ?
{ nick, access_token: accessToken } :
{ recipient: id };
return this.client.api.channels[this.id].recipients[id].put({ data })
return this.client.api.channels[this.id].recipients[id].put({ nick, access_token: accessToken })
.then(() => this);
}

View File

@@ -7,7 +7,6 @@ const Collection = require('../util/Collection');
const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Snowflake = require('../util/Snowflake');
const Shared = require('./shared');
const GuildMemberStore = require('../stores/GuildMemberStore');
const RoleStore = require('../stores/RoleStore');
const GuildEmojiStore = require('../stores/GuildEmojiStore');
@@ -352,79 +351,6 @@ class Guild extends Base {
return this.client.voice.connections.get(this.id) || null;
}
/**
* The position of this guild
* <warn>This is only available when using a user account.</warn>
* @type {?number}
* @readonly
*/
get position() {
if (this.client.user.bot) return null;
if (!this.client.user.settings.guildPositions) return null;
return this.client.user.settings.guildPositions.indexOf(this.id);
}
/**
* Whether the guild is muted
* <warn>This is only available when using a user account.</warn>
* @type {?boolean}
* @readonly
*/
get muted() {
if (this.client.user.bot) return null;
try {
return this.client.user.guildSettings.get(this.id).muted;
} catch (err) {
return false;
}
}
/**
* The type of message that should notify you
* one of `EVERYTHING`, `MENTIONS`, `NOTHING`
* <warn>This is only available when using a user account.</warn>
* @type {?string}
* @readonly
*/
get messageNotifications() {
if (this.client.user.bot) return null;
try {
return this.client.user.guildSettings.get(this.id).messageNotifications;
} catch (err) {
return null;
}
}
/**
* Whether to receive mobile push notifications
* <warn>This is only available when using a user account.</warn>
* @type {?boolean}
* @readonly
*/
get mobilePush() {
if (this.client.user.bot) return null;
try {
return this.client.user.guildSettings.get(this.id).mobilePush;
} catch (err) {
return false;
}
}
/**
* Whether to suppress everyone messages
* <warn>This is only available when using a user account.</warn>
* @type {?boolean}
* @readonly
*/
get suppressEveryone() {
if (this.client.user.bot) return null;
try {
return this.client.user.guildSettings.get(this.id).suppressEveryone;
} catch (err) {
return null;
}
}
/**
* The `@everyone` role of the guild
* @type {?Role}
@@ -597,26 +523,6 @@ class Guild extends Base {
.then(data => this.members.add(data));
}
/**
* Performs a search within the entire guild.
* <warn>This is only available when using a user account.</warn>
* @param {MessageSearchOptions} [options={}] Options to pass to the search
* @returns {Promise<MessageSearchResult>}
* @example
* guild.search({
* content: 'discord.js',
* before: '2016-11-17'
* })
* .then(res => {
* const hit = res.results[0].find(m => m.hit).content;
* console.log(`I found: **${hit}**, total results: ${res.total}`);
* })
* .catch(console.error);
*/
search(options = {}) {
return Shared.search(this, options);
}
/**
* The data for editing a guild.
* @typedef {Object} GuildEditData
@@ -831,55 +737,6 @@ class Guild extends Base {
return this.edit({ splash: await DataResolver.resolveImage(splash), reason });
}
/**
* Sets the position of the guild in the guild listing.
* <warn>This is only available when using a user account.</warn>
* @param {number} position Absolute or relative position
* @param {boolean} [relative=false] Whether to position relatively or absolutely
* @returns {Promise<Guild>}
*/
setPosition(position, relative) {
if (this.client.user.bot) {
return Promise.reject(new Error('FEATURE_USER_ONLY'));
}
return this.client.user.settings.setGuildPosition(this, position, relative);
}
/**
* Marks all messages in this guild as read.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<Guild>}
*/
acknowledge() {
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;
});
}
/**
* Whether to allow direct messages from guild members.
* <warn>This is only available when using a user account.</warn>
* @param {boolean} allow Whether to allow direct messages
* @returns {Promise<Guild>}
*/
allowDMs(allow) {
if (this.client.user.bot) return Promise.reject(new Error('FEATURE_USER_ONLY'));
const settings = this.client.user.settings;
if (allow) return settings.removeRestrictedGuild(this);
else return settings.addRestrictedGuild(this);
}
/**
* Syncs this guild (already done automatically every 30 seconds).
* <warn>This is only available when using a user account.</warn>
*/
sync() {
if (!this.client.user.bot) this.client.syncGuilds([this]);
}
/**
* The data needed for updating a channel's position.
* @typedef {Object} ChannelPosition

View File

@@ -6,7 +6,6 @@ const PermissionOverwrites = require('./PermissionOverwrites');
const Util = require('../util/Util');
const Permissions = require('../util/Permissions');
const Collection = require('../util/Collection');
const { MessageNotificationTypes } = require('../util/Constants');
const { Error, TypeError } = require('../errors');
/**
@@ -564,37 +563,6 @@ class GuildChannel extends Channel {
delete(reason) {
return this.client.api.channels(this.id).delete({ reason }).then(() => this);
}
/**
* Whether the channel is muted
* <warn>This is only available when using a user account.</warn>
* @type {?boolean}
* @readonly
*/
get muted() {
if (this.client.user.bot) return null;
try {
return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).muted;
} catch (err) {
return false;
}
}
/**
* The type of message that should notify you
* one of `EVERYTHING`, `MENTIONS`, `NOTHING`, `INHERIT`
* <warn>This is only available when using a user account.</warn>
* @type {?string}
* @readonly
*/
get messageNotifications() {
if (this.client.user.bot) return null;
try {
return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).messageNotifications;
} catch (err) {
return MessageNotificationTypes[3];
}
}
}
module.exports = GuildChannel;

View File

@@ -499,20 +499,6 @@ class Message extends Base {
return this.channel.send(content, Object.assign(options, { reply: this.member || this.author }));
}
/**
* Marks the message as read.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<Message>}
*/
acknowledge() {
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;
});
}
/**
* Fetches the webhook used to create this message.
* @returns {Promise<?Webhook>}

View File

@@ -1,6 +1,5 @@
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { Presence } = require('./Presence');
const UserProfile = require('./UserProfile');
const Snowflake = require('../util/Snowflake');
const Base = require('./Base');
const { Error } = require('../errors');
@@ -151,16 +150,6 @@ class User extends Base {
return `${this.username}#${this.discriminator}`;
}
/**
* The note that is set for the user
* <warn>This is only available when using a user account.</warn>
* @type {?string}
* @readonly
*/
get note() {
return this.client.user.notes.get(this.id) || null;
}
/**
* Checks whether the user is typing in a channel.
* @param {ChannelResolvable} channel The channel to check in
@@ -222,26 +211,6 @@ class User extends Base {
.then(data => this.client.actions.ChannelDelete.handle(data).channel);
}
/**
* Gets the profile of the user.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<UserProfile>}
*/
fetchProfile() {
return this.client.api.users(this.id).profile.get().then(data => new UserProfile(this, data));
}
/**
* Sets a note for the user.
* <warn>This is only available when using a user account.</warn>
* @param {string} note The note to set for the user
* @returns {Promise<User>}
*/
setNote(note) {
return this.client.api.users('@me').notes(this.id).put({ data: { note } })
.then(() => this);
}
/**
* Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags.
* It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.

View File

@@ -1,83 +0,0 @@
const Collection = require('../util/Collection');
const { UserFlags } = require('../util/Constants');
const UserConnection = require('./UserConnection');
const Base = require('./Base');
/**
* Represents a user's profile on Discord.
* @extends {Base}
*/
class UserProfile extends Base {
constructor(user, data) {
super(user.client);
/**
* The owner of the profile
* @type {User}
*/
this.user = user;
/**
* The guilds that the client user and the user share
* @type {Collection<Snowflake, Guild>}
*/
this.mutualGuilds = new Collection();
/**
* The user's connections
* @type {Collection<Snowflake, UserConnection>}
*/
this.connections = new Collection();
this._patch(data);
}
_patch(data) {
/**
* If the user has Discord Premium
* @type {boolean}
*/
this.premium = Boolean(data.premium_since);
/**
* The Bitfield of the users' flags
* @type {number}
* @private
*/
this._flags = data.user.flags;
/**
* The date since which the user has had Discord Premium
* @type {?Date}
*/
this.premiumSince = data.premium_since ? new Date(data.premium_since) : null;
for (const guild of data.mutual_guilds) {
if (this.client.guilds.has(guild.id)) {
this.mutualGuilds.set(guild.id, this.client.guilds.get(guild.id));
}
}
for (const connection of data.connected_accounts) {
this.connections.set(connection.id, new UserConnection(this.user, connection));
}
}
/**
* The flags the user has
* @type {UserFlags[]}
* @readonly
*/
get flags() {
const flags = [];
for (const [name, flag] of Object.entries(UserFlags)) {
if ((this._flags & flag) === flag) flags.push(name);
}
return flags;
}
toJSON() {
return super.toJSON({ flags: true });
}
}
module.exports = UserProfile;

View File

@@ -119,23 +119,6 @@ class TextBasedChannel {
return Shared.sendMessage(this, options);
}
/**
* Performs a search within the channel.
* <warn>This is only available when using a user account.</warn>
* @param {MessageSearchOptions} [options={}] Options to pass to the search
* @returns {Promise<MessageSearchResult>}
* @example
* channel.search({ content: 'discord.js', before: '2016-11-17' })
* .then(res => {
* const hit = res.results[0].find(m => m.hit).content;
* console.log(`I found: **${hit}**, total results: ${res.total}`);
* })
* .catch(console.error);
*/
search(options = {}) {
return Shared.search(this, options);
}
/**
* Starts a typing indicator in the channel.
* @param {number} [count=1] The number of times startTyping should be considered to have been called
@@ -273,7 +256,6 @@ class TextBasedChannel {
/**
* Bulk deletes given messages that are newer than two weeks.
* <warn>This is only available when using a bot account.</warn>
* @param {Collection<Snowflake, Message>|Message[]|Snowflake[]|number} messages
* Messages or number of messages to delete
* @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
@@ -316,28 +298,11 @@ class TextBasedChannel {
throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
}
/**
* Marks all messages in this channel as read.
* <warn>This is only available when using a user account.</warn>
* @returns {Promise<TextChannel|GroupDMChannel|DMChannel>}
*/
acknowledge() {
if (!this.lastMessageID) return Promise.resolve(this);
return this.client.api.channels[this.id].messages[this.lastMessageID].ack
.post({ data: { token: this.client.rest._ackToken } })
.then(res => {
if (res.token) this.client.rest._ackToken = res.token;
return this;
});
}
static applyToClass(structure, full = false, ignore = []) {
const props = ['send'];
if (full) {
props.push(
'acknowledge',
'lastMessage',
'search',
'bulkDelete',
'startTyping',
'stopTyping',

View File

@@ -1,100 +0,0 @@
const Util = require('../../util/Util');
const { TypeError } = require('../../errors');
/**
* @typedef {Object} MessageSearchOptions
* @property {string} [content] Message content
* @property {Snowflake} [maxID] Maximum ID for the filter
* @property {Snowflake} [minID] Minimum ID for the filter
* @property {string} [has] One of `link`, `embed`, `file`, `video`, `image`, or `sound`,
* or add `-` to negate (e.g. `-file`)
* @property {ChannelResolvable} [channel] Channel to limit search to (only for guild search endpoint)
* @property {UserResolvable} [author] Author to limit search
* @property {string} [authorType] One of `user`, `bot`, `webhook`, or add `-` to negate (e.g. `-webhook`)
* @property {string} [sortBy='timestamp'] `timestamp` or `relevant`
* @property {string} [sortOrder='descending'] `ascending` or `descending`
* @property {number} [contextSize=2] How many messages to get around the matched message (0 to 2)
* @property {number} [limit=25] Maximum number of results to get (1 to 25)
* @property {number} [offset=0] Offset the "pages" of results (since you can only see 25 at a time)
* @property {UserResolvable} [mentions] Mentioned user filter
* @property {boolean} [mentionsEveryone] If everyone is mentioned
* @property {string} [linkHostname] Filter links by hostname
* @property {string} [embedProvider] The name of an embed provider
* @property {string} [embedType] one of `image`, `video`, `url`, `rich`, or add `-` to negate (e.g. `-image`)
* @property {string} [attachmentFilename] The name of an attachment
* @property {string} [attachmentExtension] The extension of an attachment
* @property {Date} [before] Date to find messages before
* @property {Date} [after] Date to find messages before
* @property {Date} [during] Date to find messages during (range of date to date + 24 hours)
* @property {boolean} [nsfw=false] Include results from NSFW channels
*/
/**
* @typedef {Object} MessageSearchResult
* @property {number} total Total result count
* @property {Array<Message[]>} results Array of message results
* The message which has triggered the result will have the `hit` property set to `true`
*/
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 = Util.binaryToID((options.before.getTime() - 14200704e5).toString(2) + '0'.repeat(22));
}
if (options.after) {
if (!(options.after instanceof Date)) options.after = new Date(options.after);
options.minID = Util.binaryToID((options.after.getTime() - 14200704e5).toString(2) + '0'.repeat(22));
}
if (options.during) {
if (!(options.during instanceof Date)) options.during = new Date(options.during);
const t = options.during.getTime() - 14200704e5;
options.minID = Util.binaryToID(t.toString(2) + '0'.repeat(22));
options.maxID = Util.binaryToID((t + 864e5).toString(2) + '0'.repeat(22));
}
if (options.channel) options.channel = target.client.channels.resolveID(options.channel);
if (options.author) options.author = target.client.users.resolveID(options.author);
if (options.mentions) options.mentions = target.client.users.resolveID(options.options.mentions);
if (options.sortOrder) {
options.sortOrder = { ascending: 'asc', descending: 'desc' }[options.sortOrder] || options.sortOrder;
}
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,
include_nsfw: options.nsfw,
};
// Lazy load these because some of them use util
const Channel = require('../Channel');
const Guild = require('../Guild');
if (!(target instanceof Channel || target instanceof Guild)) throw new TypeError('SEARCH_CHANNEL_TYPE');
let endpoint = target.client.api[target instanceof Channel ? 'channels' : 'guilds'](target.id).messages().search;
return endpoint.get({ query: options }).then(body => {
const results = body.messages.map(x =>
x.map(m => target.client.channels.get(m.channel_id).messages.add(m, false))
);
return {
total: body.total_results,
results,
};
});
};

View File

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

View File

@@ -21,7 +21,6 @@ const browser = exports.browser = typeof window !== 'undefined';
* @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as
* upon joining a guild (should be avoided whenever possible)
* @property {boolean} [disableEveryone=false] Default value for {@link MessageOptions#disableEveryone}
* @property {boolean} [sync=false] Whether to periodically sync guilds (for user accounts)
* @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their
* corresponding websocket events
* @property {number} [restTimeOffset=500] Extra time in millseconds to wait before continuing to make REST
@@ -46,7 +45,6 @@ exports.DefaultOptions = {
messageSweepInterval: 0,
fetchAllMembers: false,
disableEveryone: false,
sync: false,
restWsBridgeTimeout: 5000,
disabledEvents: [],
restTimeOffset: 500,
@@ -246,7 +244,6 @@ exports.Events = {
USER_UPDATE: 'userUpdate',
USER_NOTE_UPDATE: 'userNoteUpdate',
USER_SETTINGS_UPDATE: 'clientUserSettingsUpdate',
USER_GUILD_SETTINGS_UPDATE: 'clientUserGuildSettingsUpdate',
PRESENCE_UPDATE: 'presenceUpdate',
VOICE_STATE_UPDATE: 'voiceStateUpdate',
VOICE_BROADCAST_SUBSCRIBE: 'subscribe',
@@ -264,7 +261,6 @@ exports.Events = {
* The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
* * READY
* * RESUMED
* * GUILD_SYNC
* * GUILD_CREATE
* * GUILD_DELETE
* * GUILD_UPDATE
@@ -302,7 +298,6 @@ exports.Events = {
exports.WSEvents = keyMirror([
'READY',
'RESUMED',
'GUILD_SYNC',
'GUILD_CREATE',
'GUILD_DELETE',
'GUILD_UPDATE',
@@ -328,15 +323,10 @@ exports.WSEvents = keyMirror([
'MESSAGE_REACTION_REMOVE',
'MESSAGE_REACTION_REMOVE_ALL',
'USER_UPDATE',
'USER_NOTE_UPDATE',
'USER_SETTINGS_UPDATE',
'USER_GUILD_SETTINGS_UPDATE',
'PRESENCE_UPDATE',
'VOICE_STATE_UPDATE',
'TYPING_START',
'VOICE_SERVER_UPDATE',
'RELATIONSHIP_ADD',
'RELATIONSHIP_REMOVE',
]);
/**
@@ -392,13 +382,6 @@ exports.ExplicitContentFilterTypes = [
'FRIENDS_AND_NON_FRIENDS',
];
exports.MessageNotificationTypes = [
'EVERYTHING',
'MENTIONS',
'NOTHING',
'INHERIT',
];
exports.UserSettingsMap = {
/**
* Automatically convert emoticons in your messages to emoji,
@@ -532,60 +515,6 @@ exports.UserSettingsMap = {
},
};
exports.UserGuildSettingsMap = {
message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching
/**
* The type of message that should notify you.
* One of `EVERYTHING`, `MENTIONS`, `NOTHING`
* @name ClientUserGuildSettings#messageNotifications
* @type {string}
*/
return exports.MessageNotificationTypes[type];
},
/**
* Whether to receive mobile push notifications
* @name ClientUserGuildSettings#mobilePush
* @type {boolean}
*/
mobile_push: 'mobilePush',
/**
* Whether the guild is muted or not
* @name ClientUserGuildSettings#muted
* @type {boolean}
*/
muted: 'muted',
/**
* Whether to suppress everyone messages
* @name ClientUserGuildSettings#suppressEveryone
* @type {boolean}
*/
suppress_everyone: 'suppressEveryone',
/**
* A collection containing all the channel overrides
* @name ClientUserGuildSettings#channelOverrides
* @type {Collection<ClientUserChannelOverride>}
*/
channel_overrides: 'channelOverrides',
};
exports.UserChannelOverrideMap = {
message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching
/**
* The type of message that should notify you.
* One of `EVERYTHING`, `MENTIONS`, `NOTHING`, `INHERIT`
* @name ClientUserChannelOverride#messageNotifications
* @type {string}
*/
return exports.MessageNotificationTypes[type];
},
/**
* Whether the channel is muted or not
* @name ClientUserChannelOverride#muted
* @type {boolean}
*/
muted: 'muted',
};
/**
* All flags users can have:
* * STAFF