models/Guild.js

"use strict";

const BannedUser = require('./BannedUser');
const Store = require('../utils/Store');
const UnavailableGuild = require('./UnavailableGuild');
const TextChannel = require('./TextChannel');
const VoiceChannel = require('./VoiceChannel');
const CategoryChannel = require('./CategoryChannel');
const User = require('./User');
const Member = require('./Member');
const Role = require('./Role');
const { ENDPOINTS } = require('../utils/Constants').HTTP;

/**
 * @extends UnavailableGuild Represents an available guild on Discord
 * @prop {GuildChannel?} afkChannel Represents the afk Channel
 * @prop {Snowflake?} afkChannelID The id of the afk channel
 * @prop {Boolean} available Whether the guild is available or not
 * @prop {Store<GuildChannel>} channel A Store of the Channels in the Guild
 * @prop {Number} createdTimestamp The timestamp in ms when the guild was created
 * @prop {Number} defaultMessageNotificaions The default message notification for the Guild
 * @prop {Store<Emoji>} emojis A Store of Guild Emojis
 * @prop {Array} features An array of guild features
 * @prop {String?} icon The icon hash of the guild
 * @prop {String?} iconURL The url of the guild's icon
 * @prop {Snowflake} id The id of the guild
 * @prop {Boolean} large Whether the guild is large or not, depends on `Client.largeThreshold`
 * @prop {Number} memberCount The guilds's member count
 * @prop {Store<Member>} members The amount of Members that the guild is in
 * @prop {String} name The name of the guild
 * @prop {Member?} owner The member object of the guild owner
 * @prop {Member?} ownerID The id of the guild owner
 * @prop {Array} presences An array of member presence
 * @prop {String} region The region of the guild
 * @prop {Store<Role>} roles A Store of Roles ( Where Roles are cached )
 * @prop {Object?} systemChannel The system channel of the guild
 * @prop {Snowflake?} systemChannelID The id of the system channel
 */

class Guild extends UnavailableGuild {
  constructor(client, data) {
    super(client, data);
    Object.defineProperty(this, 'client', { value: client });

    this.afkChannelID = data.afk_channel_id;
    this.afkTimeout = data.afk_timeout;
    this.applicationID = data.application_id;
    this.channels = new Store();
    this.createdTimestamp = new Date(data.joined_at).getTime();
    this.defaultMessageNotificaions = data.default_message_notifications;
    this.emojis = new Store();
    this.explicitContentFilter = data.explicit_content_filter;
    this.features = data.features;
    this.icon = data.icon || null;
    this.iconURL = data.icon ? `https://cdn.discordapp.com/icons/${data.id}/${this.icon}.png` : null;
    this.large = data.large;
    this.lazy = data.lazy;
    this.memberCount = data.member_count;
    this.members = new Store();
    this.mfaLevel = data.mfa_level;
    this.name = data.name;
    this.ownerID = data.owner_id || null;
    this.presences = data.presences;
    this.region = data.region;
    this.roles = new Store();
    this.shard = data.shard;
    this.splash = data.splash;
    this.systemChannelID = data.system_channel_id;
    this.verificationLevel = data.verification_level;
    this.voiceStates = data.voice_states;

    for (var i = 0; i < data.channels.length; i++) {
      data.channels[i].guild = this;
      switch(data.channels[i].type) {
        case 0: {
          this.channels.set(data.channels[i].id, new TextChannel(this.client, data.channels[i]));
          this.client.channels.set(data.channels[i].id, new TextChannel(this.client, data.channels[i]));
          break;
        }

        case 2: {
          this.channels.set(data.channels[i].id, new VoiceChannel(this.client, data.channels[i]));
          this.client.channels.set(data.channels[i].id, new VoiceChannel(this.client, data.channels[i]));
          break;
        }

        case 4: {
          this.channels.set(data.channels[i].id, new CategoryChannel(this.client, data.channels[i]));
          this.client.channels.set(data.channels[i].id, new CategoryChannel(this.client, data.channels[i]));
          break;
        }

        default: {
          this.client.emit('debug', 'Invalid Guild Channel type Received: ' + data.channels[i].type);
          break;
        }
      };
    };

    for (var i = 0; i < data.roles.length; i++) {
      this.roles.set(data.roles[i].id, new Role(this.client, data.roles[i]));
    };

    for (var i = 0; i < data.members.length; i++) {
      this.client.users.set(data.members[i].user.id, new User(this.client, data.members[i].user));
      data.members[i].guild = this;
      this.members.set(data.members[i].user.id, new Member(this.client, data.members[i]));
    };
  }

  get afkChannel() {
    return this.afkChannelID ? this.channels.get(this.afkChannelID) : null;
  }

  get bans() {
    return (async () => {
      return await this.client.rest.request("GET", ENDPOINTS.GUILD_BANS(this.id)).data;
  })();
}

  get bot() {
    return this.members.get(this.client.user.id);
  }

  get owner() {
    return this.ownerID ? this.members.get(this.ownerID) : null;
  }

  get systemChannel() {
    return this.systemChannelID ? this.channels.get(this.systemChannelID) : null;
  }

  /**
   * Bans a member from the guild
   * @param {Snowflake} user The id of the member to ban
   * @param {Object} [options] Options for the guild ban
   * @param {Number} [options.days=0] Number of days to delete messages for
   * @param {String} [options.reason=''] Reasom for the ban
   * @returns {Promise<User>}
   */

  ban(user, options = { days: 0, reason: '' }) {
    return this.client.rest.request("PUT", `${ENDPOINTS.GUILD_BAN(this.id, user)}?delete-message-days=${options.days}&reason=${options.reason}`)
    .then(() => {
      return this.client.getUser(user);
    });
  }

  /**
   * Begins a prune operation. Returns an object with one 'pruned' key indicating the number of members that would be removed in a prune operation.
   * @param {Number} days Number of days to count prune for (1 or more)
   * @returns {Promise<Object>}
   */

  beginPrune(days) {
    return this.client.rest.request("POST", ENDPOINTS.GUILD_PRUNE(this.id), {
      data: {
        days
      }
    }).then(res => {
      return res.data;
    });
  }

  /**
   * Deletes a guild
   * @returns {Promise<Guild>}
   */

  delete() {
    return this.client.rest.request("DELETE", ENDPOINTS.GUILD(this.id))
    .then(() => {
      return this;
    });
  }

  /**
   * Deletes a role from the guild
   * @param {Snowflake} role The id of the role to delete
   * @returns {Promise<Role>}
   */

  deleteRole(role) {
    let Role = this.roles.get(role);

    return this.client.rest.request("DELETE", ENDPOINTS.GUILD_ROLE(this.id, role))
    .then(() => {
      return Role;
    });
  }

  /**
   * Gets information about the banned user id
   * @param {Snowflake} user The id of the banned user
   * @returns {Promise<BannedUser>}
   */

  getBan(user) {
    return this.client.rest.request("GET", ENDPOINTS.GUILD_BAN(this.guild, user))
    .then(res => {
      return new BannedUser(this.client, res.data);
    });
  }

  /**
   * Returns an array of banned users
   * @returns {Promise<Array<BannedUsers>>}
   */

  getBans() {
    return this.client.rest.request("GET", ENDPOINTS.GUILD_BANS(this.guild))
    .then(res => {
      return res.data.map(data => {
        return new BannedUser(this.client, data);
      });
    });
  }

  /**
   * Kicks a member from the guild
   * @param {Snowflake} user The id of the member to kick
   * @param {String} reason The reason for the kick
   * @returns {Promise<User>}
   */

  kick(user, reason) {
    return this.client.rest.request("DELETE", `${ENDPOINTS.GUILD_MEMBER(this.id, user)}?reason=${reason}`)
    .then(() => {
      return this.client.getUser(user);
    });
  }

  /**
   * Fetch all the guild channels using the REST API and sends an array of channels that were fetched from the cache
   * @returns {Promise<TextChannel|VoiceChannel|CategoryChannel>}
   */

  getChannels() {
    return this.client.rest.request("GET", ENDPOINTS.GUILD_CHANNELS(this.id))
    .then(res => {
      return res.data.map(channel => {
        return this.channels.get(channel.id);
      });
    });
  }

  /**
   * Returns an array of guild invites
   * @returns {Promise<Array<Invite>>}
   */

  getInvites() {
    return this.client.rest.request("GET", ENDPOINTS.GUILD_INVITES(this.id))
    .then(res => {
      return res.data;
    });
  }

  /**
   * Fetches a guild member from the cache, if not present will use the REST API and set it inside the cache
   * @param {Snowflake} user The id of the member
   * @returns {Promise<Member>}
   */

  getMember(user) {
    return this.client.rest.request("GET", ENDPOINTS.GUILD_MEMBER(this.id, user))
    .then(res => {
      if (!this.members.has(res.data.user.id)) {
        return this.members.set(res.data.user.id, new Member(this.client, res.data));
      } else {
        return this.members.get(res.data.user.id);
      }
    });
  }

  /**
   * Gives a member a role
   * @param {Snowflake} user The id of the member to give the role
   * @param {Snowflake} role The role to give to the member
   * @returns {Promise<Member>}
   */

  giveRole(user, role) {
    return this.client.rest.request("PUT", ENDPOINTS.GUILD_MEMBER_ROLE(this.id, user, role))
    .then(() => {
      return this.members.get(user);
    });
  }

  /**
   * Similiar to `Client#leaveGuild()`, makes the bot leave the current guild
   * @returns {Promise<Guild>}
   */

  async leave() {
    await this.client.leaveGuild(this.id);
    
    return Promise.resolve(this);
  }

  /**
   * Creates a Guild Channel
   * Guild Channel types:
   * * `text` - Text Channel
   * * `voice` - Voice Channel
   * * `category` - Category Channel
   * @param {String} name The name of the channel
   * @param {String} [type='text'] The type of the channel
   * @returns {Promise<TextChannel|VoiceChannel|CategoryChannel>}
   */

  makeChannel(name, type = 'text') {
    let types = ['text', 'voice', 'category'];

    if (typeof type !== 'string' || typeof type === 'string' && !types.includes(type.toLowerCase()))
      return this.client.emit('error', new Error('Invalid Channel type!'));

    type = type.toLowerCase();

    if (type === 'text') {
      type = 0;
    } else if (type === 'voice') {
      type = 2;
    } else if (type === 'category') {
      type = 4;
    }

    return this.client.rest.request("POST", ENDPOINTS.GUILD_CHANNELS(this.id), {
      data: {
        name,
        type
      }
    }).then(res => {
      return this.channels.get(res.data.id);
    });
  }

  /**
   * Creates a guild role
   * @param {String} name The name of the role
   * @param {Boolean} [isHoisted=false] Whether the role should be seperate from the `@everyone` role
   * @param {Boolean} [isMentionable=false] Whther the role should be mentionable
   * @returns {Promise<Role>}
   */

  makeRole(name, isHoisted = false, isMentionable = false) {
    return this.client.rest.request("POST", ENDPOINTS.GUILD_ROLES(this.id), {
      data: {
        name,
        hoist: isHoisted,
        mentionable: isMentionable
      }
    }).then(res => {
      return this.roles.get(res.data.id);
    });
  }

  /**
   * Edits a channel's position
   * @param {Snowflake} channel The id of the channel to set the position of
   * @param {Number} position The new position of the channel
   * @returns {Promise<Channel>}
   */

  modifyChannelPosition(channel, position) {
    return this.client.rest.request("PATCH", ENDPOINTS.GUILD_CHANNEL(this.id, channel), {
      data: {
        id: channel,
        position
      }
    }).then(() => {
      return this.channels.get(channel);
    });
  }

  /**
   * Edits a guild role
   * @param {Snowflake} role The id of the role to edit
   * @param {Object} [options] The options for the role
   * @param {String} [options.name] The new name of the role
   * @param {Number} [options.color] The color of the role in RGB Form. e.g `(1, 1, 1)`
   * @param {Boolean} [options.isHoisted=false] Whether the role should be displayed separately in the sidebar
   * @param {Boolean} [options.isMentionable=false] Whether the role is mentionable
   * @returns {Promise<Role>}
   */

  modifyRole(role, options = {}) {
    return this.client.rest.request("PATCH", ENDPOINTS.GUILD_ROLE(this.id, role), {
      data: {
        name: options.name,
        color: options.color,
        hoist: options.isHoisted || false,
        mentionable: options.isMentionable || false
      }
    }).then(() => {
      return this.roles.get(role);
    });
  }

  /**
   * Returns an object with one 'pruned' key indicating the number of members that would be removed in a prune operation.
   * @param {Number} days Number of days to count prune for (1 or more)
   * @returns {Promise<Object>}
   */

  pruneCount(days) {
    return this.client.rest.request("GET", ENDPOINTS.GUILD_PRUNE(this.id), {
      data: {
        days
      }
    }).then(res => {
      return res.data;
    });
  }

  /**
   * Removes a role from a member
   * @param {Snowflake} user The id of the user to remove the role from
   * @param {Snowflake} role The id of the role to remove
   * @returns {Promise<Member>}
   */

  removeRole(user, role) {
    return this.client.rest.request("DELTE", ENDPOINTS.GUILD_MEMBER_ROLE(this.id, user, role))
    .then(() => {
      return this.members.get(user);
    });
  }

  /**
   * Edits a channel position
   * @param {Snowflake} channel The id of the guild channel
   * @param {Number} position The new position of the channel
   * @returns {Promise<TextChannel|VoiceChannel|GuildChannel>}
   */

  setChannelPosition(channel, position) {
    return this.client.rest.request("PATCH", ENDPOINTS.GUILD_CHANNELS(this.id), {
      data: {
        id: channel,
        position
      }
    }).then(() => {
      return this.channels.get(channel);
    });
  }

  /**
   * Sets the nickname of the client
   * @param {String} nick The new nickname for the bot
   * @returns {Promise<Member>}
   */

  setOwnNick(nick) {
    return this.client.rest.request("PATCH", ENDPOINTS.GUILD_MEMBER_NICK(this.id, '@me'), {
      data: {
        nick
      }
    }).then(() => {
      return this.bot;
    });
  }

  /**
   * Softbans a member from the guild
   * @param {Snowflake} user The id of the member to softban
   * @param {String} reason The reason for the softban
   * @returns {Promise<User>}
   */

  async softban(user, reason) {
    this.ban(user, { days: 7, reason });
    this.unban(user, reason);

    return await this.client.getUser(user);
  }

  /**
   * Unbans a member from the guild
   * @param {Snowflake} user The id of the member to unban
   * @param {String} reason The reason for the unban
   * @returns {Promise<User>}
   */

  unban(user, reason) {
    return this.client.rest.request("DELETE", `${ENDPOINTS.GUILD_BAN(this.id, user)}?reason=${reason}`)
    .then(() => {
      return this.client.getUser(user);
    });
  }
};

module.exports = Guild;