refactor(helpers): separate functions into files (#667)

* refactor(helpers): separate functions into files

* idk

* idk
This commit is contained in:
ayntee
2021-03-13 08:10:31 -05:00
committed by GitHub
parent 88ce4da555
commit e9cbbbff7c
143 changed files with 3362 additions and 2915 deletions
+18
View File
@@ -0,0 +1,18 @@
import { Member } from "../../structures/mod.ts";
import { ImageFormats, ImageSize } from "../../types/mod.ts";
import { rawAvatarURL } from "./raw_avatar_url.ts";
/** The users custom avatar or the default avatar */
export function avatarURL(
member: Member,
size: ImageSize = 128,
format?: ImageFormats,
) {
return rawAvatarURL(
member.id,
member.discriminator,
member.avatar,
size,
format,
);
}
+19
View File
@@ -0,0 +1,19 @@
import { RequestManager } from "../../rest/request_manager.ts";
import { BanOptions } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import { requireBotGuildPermissions } from "../../util/permissions.ts";
/** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */
export async function ban(guildID: string, id: string, options: BanOptions) {
await requireBotGuildPermissions(guildID, ["BAN_MEMBERS"]);
const result = await RequestManager.put(endpoints.GUILD_BAN(guildID, id), {
...options,
delete_message_days: options.days,
});
return result;
}
// aliases
export { ban as banMember };
+9
View File
@@ -0,0 +1,9 @@
import { editMember } from "./edit_member.ts";
/** Kicks a member from a voice channel */
export function kickFromVoiceChannel(guildID: string, memberID: string) {
return editMember(guildID, memberID, { channel_id: null });
}
// aliases
export { kickFromVoiceChannel as disconnectMember };
+18
View File
@@ -0,0 +1,18 @@
import { RequestManager } from "../../rest/request_manager.ts";
import { endpoints } from "../../util/constants.ts";
import { requireBotGuildPermissions } from "../../util/permissions.ts";
/** Edit the nickname of the bot in this guild */
export async function editBotNickname(
guildID: string,
nickname: string | null,
) {
await requireBotGuildPermissions(guildID, ["CHANGE_NICKNAME"]);
const response = await RequestManager.patch(
endpoints.USER_NICK(guildID),
{ nick: nickname },
) as { nick: string };
return response.nick;
}
+38
View File
@@ -0,0 +1,38 @@
import { RequestManager } from "../../rest/request_manager.ts";
import { Errors } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import { urlToBase64 } from "../../util/utils.ts";
/** Modifies the bot's username or avatar.
* NOTE: username: if changed may cause the bot's discriminator to be randomized.
*/
export async function editBotProfile(username?: string, botAvatarURL?: string) {
// Nothing was edited
if (!username && !botAvatarURL) return;
// Check username requirements if username was provided
if (username) {
if (username.length > 32) {
throw new Error(Errors.USERNAME_MAX_LENGTH);
}
if (username.length < 2) {
throw new Error(Errors.USERNAME_MIN_LENGTH);
}
if (["@", "#", ":", "```"].some((char) => username.includes(char))) {
throw new Error(Errors.USERNAME_INVALID_CHARACTER);
}
if (["discordtag", "everyone", "here"].includes(username)) {
throw new Error(Errors.USERNAME_INVALID_USERNAME);
}
}
const avatar = botAvatarURL ? await urlToBase64(botAvatarURL) : undefined;
const result = await RequestManager.patch(
endpoints.USER_BOT,
{
username: username?.trim(),
avatar,
},
);
return result;
}
+80
View File
@@ -0,0 +1,80 @@
import { cacheHandlers } from "../../cache.ts";
import { RequestManager } from "../../rest/request_manager.ts";
import { structures } from "../../structures/mod.ts";
import {
EditMemberOptions,
Errors,
MemberCreatePayload,
Permission,
} from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import {
requireBotChannelPermissions,
requireBotGuildPermissions,
} from "../../util/permissions.ts";
/** Edit the member */
export async function editMember(
guildID: string,
memberID: string,
options: EditMemberOptions,
) {
const requiredPerms: Set<Permission> = new Set();
if (options.nick) {
if (options.nick.length > 32) {
throw new Error(Errors.NICKNAMES_MAX_LENGTH);
}
requiredPerms.add("MANAGE_NICKNAMES");
}
if (options.roles) requiredPerms.add("MANAGE_ROLES");
if (
typeof options.mute !== "undefined" ||
typeof options.deaf !== "undefined" ||
(typeof options.channel_id !== "undefined" || "null")
) {
const memberVoiceState = (await cacheHandlers.get("guilds", guildID))
?.voiceStates.get(memberID);
if (!memberVoiceState?.channelID) {
throw new Error(Errors.MEMBER_NOT_IN_VOICE_CHANNEL);
}
if (typeof options.mute !== "undefined") {
requiredPerms.add("MUTE_MEMBERS");
}
if (typeof options.deaf !== "undefined") {
requiredPerms.add("DEAFEN_MEMBERS");
}
if (options.channel_id) {
const requiredVoicePerms: Set<Permission> = new Set([
"CONNECT",
"MOVE_MEMBERS",
]);
if (memberVoiceState) {
await requireBotChannelPermissions(
memberVoiceState?.channelID,
[...requiredVoicePerms],
);
}
await requireBotChannelPermissions(
options.channel_id,
[...requiredVoicePerms],
);
}
}
await requireBotGuildPermissions(guildID, [...requiredPerms]);
const result = await RequestManager.patch(
endpoints.GUILD_MEMBER(guildID, memberID),
options,
) as MemberCreatePayload;
const member = await structures.createMemberStruct(result, guildID);
return member;
}
+31
View File
@@ -0,0 +1,31 @@
import { identifyPayload } from "../../bot.ts";
import { Guild, Member } from "../../structures/mod.ts";
import { Errors, FetchMembersOptions, Intents } from "../../types/mod.ts";
import { Collection } from "../../util/collection.ts";
import { requestAllMembers } from "../../ws/shard_manager.ts";
/**
* ⚠️ BEGINNER DEVS!! YOU SHOULD ALMOST NEVER NEED THIS AND YOU CAN GET FROM cache.members.get()
*
* ADVANCED:
* Highly recommended to use this function to fetch members instead of getMember from REST.
* REST: 50/s global(across all shards) rate limit with ALL requests this included
* GW(this function): 120/m(PER shard) rate limit. Meaning if you have 8 shards your limit is now 960/m.
*/
export function fetchMembers(guild: Guild, options?: FetchMembersOptions) {
// You can request 1 member without the intent
if (
(!options?.limit || options.limit > 1) &&
!(identifyPayload.intents && Intents.GUILD_MEMBERS)
) {
throw new Error(Errors.MISSING_INTENT_GUILD_MEMBERS);
}
if (options?.userIDs?.length) {
options.limit = options.userIDs.length;
}
return new Promise((resolve) => {
return requestAllMembers(guild, resolve, options);
}) as Promise<Collection<string, Member>>;
}
+27
View File
@@ -0,0 +1,27 @@
import { cacheHandlers } from "../../cache.ts";
import { RequestManager } from "../../rest/request_manager.ts";
import { structures } from "../../structures/mod.ts";
import { MemberCreatePayload } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
/** Returns a guild member object for the specified user.
*
* ⚠️ **ADVANCED USE ONLY: Your members will be cached in your guild most likely. Only use this when you are absolutely sure the member is not cached.**
*/
export async function getMember(
guildID: string,
id: string,
options?: { force?: boolean },
) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild && !options?.force) return;
const data = (await RequestManager.get(
endpoints.GUILD_MEMBER(guildID, id),
)) as MemberCreatePayload;
const memberStruct = await structures.createMemberStruct(data, guildID);
await cacheHandlers.set("members", memberStruct.id, memberStruct);
return memberStruct;
}
+82
View File
@@ -0,0 +1,82 @@
import { identifyPayload } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { RequestManager } from "../../rest/request_manager.ts";
import { Member, structures } from "../../structures/mod.ts";
import {
Errors,
GetMemberOptions,
Intents,
MemberCreatePayload,
} from "../../types/mod.ts";
import { Collection } from "../../util/collection.ts";
import { endpoints } from "../../util/constants.ts";
/**
* ⚠️ BEGINNER DEVS!! YOU SHOULD ALMOST NEVER NEED THIS AND YOU CAN GET FROM cache.members.get()
*
* ADVANCED:
* Highly recommended to **NOT** use this function to get members instead use fetchMembers().
* REST(this function): 50/s global(across all shards) rate limit with ALL requests this included
* GW(fetchMembers): 120/m(PER shard) rate limit. Meaning if you have 8 shards your limit is 960/m.
*/
export async function getMembers(guildID: string, options?: GetMemberOptions) {
if (!(identifyPayload.intents && Intents.GUILD_MEMBERS)) {
throw new Error(Errors.MISSING_INTENT_GUILD_MEMBERS);
}
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
const members = new Collection<string, Member>();
let membersLeft = options?.limit ?? guild.memberCount;
let loops = 1;
while (
(options?.limit ?? guild.memberCount) > members.size &&
membersLeft > 0
) {
if (options?.limit && options.limit > 1000) {
console.log(
`Paginating get members from REST. #${loops} / ${
Math.ceil(
(options?.limit ?? 1) / 1000,
)
}`,
);
}
const result = (await RequestManager.get(
`${endpoints.GUILD_MEMBERS(guildID)}?limit=${
membersLeft > 1000 ? 1000 : membersLeft
}${options?.after ? `&after=${options.after}` : ""}`,
)) as MemberCreatePayload[];
const memberStructures = await Promise.all(
result.map(async (member) => {
const memberStruct = await structures.createMemberStruct(
member,
guildID,
);
await cacheHandlers.set("members", memberStruct.id, memberStruct);
return memberStruct;
}),
) as Member[];
if (!memberStructures.length) break;
memberStructures.forEach((member) => members.set(member.id, member));
options = {
limit: options?.limit,
after: memberStructures[memberStructures.length - 1].id,
};
membersLeft -= 1000;
loops++;
}
return members;
}
@@ -0,0 +1,21 @@
import { cacheHandlers } from "../../cache.ts";
import { Member } from "../../structures/mod.ts";
import { Collection } from "../../util/collection.ts";
import { requestAllMembers } from "../../ws/shard_manager.ts";
/** Returns guild member objects for the specified user by their nickname/username.
*
* ⚠️ **ADVANCED USE ONLY: Your members will be cached in your guild most likely. Only use this when you are absolutely sure the member is not cached.**
*/
export async function getMembersByQuery(
guildID: string,
name: string,
limit = 1,
) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) return;
return new Promise((resolve) => {
return requestAllMembers(guild, resolve, { query: name, limit });
}) as Promise<Collection<string, Member>>;
}
+32
View File
@@ -0,0 +1,32 @@
import { botID } from "../../bot.ts";
import { RequestManager } from "../../rest/request_manager.ts";
import { Errors } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import {
highestRole,
requireBotGuildPermissions,
} from "../../util/permissions.ts";
/** Kick a member from the server */
export async function kick(guildID: string, memberID: string, reason?: string) {
const botsHighestRole = await highestRole(guildID, botID);
const membersHighestRole = await highestRole(guildID, memberID);
if (
botsHighestRole && membersHighestRole &&
botsHighestRole.position <= membersHighestRole.position
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
await requireBotGuildPermissions(guildID, ["KICK_MEMBERS"]);
const result = await RequestManager.delete(
endpoints.GUILD_MEMBER(guildID, memberID),
{ reason },
);
return result;
}
// aliases
export { kick as kickMember };
+15
View File
@@ -0,0 +1,15 @@
import { editMember } from "./edit_member.ts";
/**
* Move a member from a voice channel to another.
* @param guildID the id of the guild which the channel exists in
* @param memberID the id of the member to move.
* @param channelID id of channel to move user to (if they are connected to voice)
*/
export function moveMember(
guildID: string,
memberID: string,
channelID: string,
) {
return editMember(guildID, memberID, { channel_id: channelID });
}
+27
View File
@@ -0,0 +1,27 @@
import { RequestManager } from "../../rest/request_manager.ts";
import { Errors, PruneOptions } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import { requireBotGuildPermissions } from "../../util/permissions.ts";
import { camelKeysToSnakeCase } from "../../util/utils.ts";
/**
* Begin a prune operation. Requires the KICK_MEMBERS permission. Returns an object with one 'pruned' key indicating the number of members that were removed in the prune operation. For large guilds it's recommended to set the computePruneCount option to false, forcing 'pruned' to null. Fires multiple Guild Member Remove Gateway events.
*
* By default, prune will not remove users with roles. You can optionally include specific roles in your prune by providing the roles (resolved to include_roles internally) parameter. Any inactive user that has a subset of the provided role(s) will be included in the prune and users with additional roles will not.
*/
export async function pruneMembers(
guildID: string,
options: PruneOptions,
) {
if (options.days && options.days < 1) throw new Error(Errors.PRUNE_MIN_DAYS);
if (options.days && options.days > 30) throw new Error(Errors.PRUNE_MAX_DAYS);
await requireBotGuildPermissions(guildID, ["KICK_MEMBERS"]);
const result = await RequestManager.post(
endpoints.GUILD_PRUNE(guildID),
camelKeysToSnakeCase(options),
);
return result;
}
+16
View File
@@ -0,0 +1,16 @@
import { ImageFormats, ImageSize } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import { formatImageURL } from "../../util/utils.ts";
/** The users custom avatar or the default avatar if you don't have a member object. */
export function rawAvatarURL(
userID: string,
discriminator: string,
avatar?: string | null,
size: ImageSize = 128,
format?: ImageFormats,
) {
return avatar
? formatImageURL(endpoints.USER_AVATAR(userID, avatar), size, format)
: endpoints.USER_DEFAULT_AVATAR(Number(discriminator) % 5);
}
@@ -0,0 +1,34 @@
import { cacheHandlers } from "../../cache.ts";
import { RequestManager } from "../../rest/request_manager.ts";
import { structures } from "../../structures/mod.ts";
import {
ChannelCreatePayload,
DMChannelCreatePayload,
MessageContent,
} from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import { sendMessage } from "../messages/send_message.ts";
/** Send a message to a users DM. Note: this takes 2 API calls. 1 is to fetch the users dm channel. 2 is to send a message to that channel. */
export async function sendDirectMessage(
memberID: string,
content: string | MessageContent,
) {
let dmChannel = await cacheHandlers.get("channels", memberID);
if (!dmChannel) {
// If not available in cache create a new one.
const dmChannelData = await RequestManager.post(
endpoints.USER_DM,
{ recipient_id: memberID },
) as DMChannelCreatePayload;
const channelStruct = await structures.createChannelStruct(
dmChannelData as unknown as ChannelCreatePayload,
);
// Recreate the channel and add it undert he users id
await cacheHandlers.set("channels", memberID, channelStruct);
dmChannel = channelStruct;
}
// If it does exist try sending a message to this user
return sendMessage(dmChannel.id, content);
}
+15
View File
@@ -0,0 +1,15 @@
import { RequestManager } from "../../rest/request_manager.ts";
import { endpoints } from "../../util/constants.ts";
import { requireBotGuildPermissions } from "../../util/permissions.ts";
/** Remove the ban for a user. Requires BAN_MEMBERS permission */
export async function unban(guildID: string, id: string) {
await requireBotGuildPermissions(guildID, ["BAN_MEMBERS"]);
const result = await RequestManager.delete(endpoints.GUILD_BAN(guildID, id));
return result;
}
// aliases
export { unban as unbanMember };