From dec7f63b43a6946be8cfa72261843cb79982bb53 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 13 Jul 2022 13:16:56 -0500 Subject: [PATCH] fix: fmt --- README.md | 10 +- egg.json | 3 +- examples/deno.ts | 2 +- packages/biscuit/structures/Invite.ts | 2 +- packages/biscuit/structures/Message.ts | 6 +- packages/biscuit/structures/ThreadMember.ts | 2 +- packages/biscuit/structures/guilds.ts | 1409 ++++++++++--------- packages/discordeno/types/shared.ts | 2 +- 8 files changed, 727 insertions(+), 709 deletions(-) diff --git a/README.md b/README.md index 794ee80..cc0e228 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # biscuit ## A brand new bleeding edge non bloated Discord library + biscuit ETA: **biscuit will be on the npm registry the next week!** @@ -79,7 +80,8 @@ session.start(); - We got the library running on WSL (Ubuntu) without any trouble ### Known issues: -* some properties may be not implemented yet -* some structures are not implemented (see https://github.com/oasisjs/biscuit/issues) -* cache (wip) -* no optimal way to create embeds, should be fixed in builders tho + +- some properties may be not implemented yet +- some structures are not implemented (see https://github.com/oasisjs/biscuit/issues) +- cache (wip) +- no optimal way to create embeds, should be fixed in builders tho diff --git a/egg.json b/egg.json index bba1267..34ad530 100644 --- a/egg.json +++ b/egg.json @@ -4,14 +4,13 @@ "entry": "./mod.ts", "description": "A brand new bleeding edge non bloated Discord library", "homepage": "https://github.com/oasisjs/biscuit", - "version": "0.0.1", + "version": "0.1.0", "releaseType": "minor", "unstable": false, "unlisted": false, "files": [ "./packages/**/*", "./mod.ts", - "./deps.ts", "LICENSE", "README.md" ], diff --git a/examples/deno.ts b/examples/deno.ts index 6519d62..9da4800 100644 --- a/examples/deno.ts +++ b/examples/deno.ts @@ -1,6 +1,6 @@ /** * Deno example -*/ + */ import "https://deno.land/std@0.146.0/dotenv/load.ts"; // TODO: remove and include the library appropriately diff --git a/packages/biscuit/structures/Invite.ts b/packages/biscuit/structures/Invite.ts index 24c9119..759f8aa 100644 --- a/packages/biscuit/structures/Invite.ts +++ b/packages/biscuit/structures/Invite.ts @@ -14,7 +14,7 @@ import type { import { TargetTypes } from "../../discordeno/mod.ts"; import { GuildChannel } from "./channels.ts"; import { Member } from "./Member.ts"; -import { InviteGuild, Guild } from "./guilds.ts"; +import { Guild, InviteGuild } from "./guilds.ts"; import User from "./User.ts"; import Application from "./Application.ts"; diff --git a/packages/biscuit/structures/Message.ts b/packages/biscuit/structures/Message.ts index 3eef46e..7f0b8ad 100644 --- a/packages/biscuit/structures/Message.ts +++ b/packages/biscuit/structures/Message.ts @@ -137,9 +137,9 @@ export class Message implements Model { return { id: si.id, name: si.name, - formatType: si.format_type - } - }) + formatType: si.format_type, + }; + }); } } diff --git a/packages/biscuit/structures/ThreadMember.ts b/packages/biscuit/structures/ThreadMember.ts index bc4c194..416f21e 100644 --- a/packages/biscuit/structures/ThreadMember.ts +++ b/packages/biscuit/structures/ThreadMember.ts @@ -7,7 +7,7 @@ import * as Routes from "../Routes.ts"; /** * A member that comes from a thread * @link https://discord.com/developers/docs/resources/channel#thread-member-object - * * */ + * **/ export class ThreadMember implements Model { constructor(session: Session, data: DiscordThreadMember) { this.session = session; diff --git a/packages/biscuit/structures/guilds.ts b/packages/biscuit/structures/guilds.ts index dd15af4..eb5350b 100644 --- a/packages/biscuit/structures/guilds.ts +++ b/packages/biscuit/structures/guilds.ts @@ -1,696 +1,713 @@ -import type { Model } from "./Base.ts"; -import type { Session } from "../Session.ts"; -import type { ChannelTypes, DefaultMessageNotificationLevels, DiscordEmoji, DiscordGuild, DiscordInviteMetadata, DiscordListActiveThreads, DiscordMemberWithUser, DiscordOverwrite, DiscordRole, ExplicitContentFilterLevels, GuildNsfwLevel, MakeRequired, SystemChannelFlags, VerificationLevels, VideoQualityModes } from "../../discordeno/mod.ts"; -import type { ImageFormat, ImageSize } from "../Util.ts"; -import { GuildFeatures } from "../../discordeno/mod.ts"; -import { Snowflake } from "../Snowflake.ts"; -import Util from "../Util.ts"; -import * as Routes from "../Routes.ts"; -import WelcomeScreen from "./WelcomeScreen.ts"; -import { GuildChannel, ThreadChannel } from "./channels.ts"; -import ThreadMember from "./ThreadMember.ts"; -import Member from "./Member.ts"; -import Role from "./Role.ts"; -import GuildEmoji from "./GuildEmoji.ts"; -import { urlToBase64 } from "../util/urlToBase64.ts"; -import Invite from "./Invite.ts"; - -/** BaseGuild */ -/** - * Class for {@link Guild} and {@link AnonymousGuild} - */ -export abstract class BaseGuild implements Model { - constructor(session: Session, data: DiscordGuild) { - this.session = session; - this.id = data.id; - - this.name = data.name; - this.iconHash = data.icon ? Util.iconHashToBigInt(data.icon) : undefined; - - this.features = data.features; - } - - readonly session: Session; - readonly id: Snowflake; - - name: string; - iconHash?: bigint; - features: GuildFeatures[]; - - get createdTimestamp() { - return Snowflake.snowflakeToTimestamp(this.id); - } - - get createdAt() { - return new Date(this.createdTimestamp); - } - - get partnered() { - return this.features.includes(GuildFeatures.Partnered); - } - - get verified() { - return this.features.includes(GuildFeatures.Verified); - } - - iconURL(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) { - if (this.iconHash) { - return Util.formatImageURL( - Routes.GUILD_ICON(this.id, Util.iconBigintToHash(this.iconHash)), - options.size, - options.format, - ); - } - } - - toString() { - return this.name; - } -} - -/** AnonymousGuild */ -export class AnonymousGuild extends BaseGuild implements Model { - constructor(session: Session, data: Partial); // TODO: Improve this type (name and id are required) - constructor(session: Session, data: DiscordGuild) { - super(session, data); - - this.splashHash = data.splash ? Util.iconHashToBigInt(data.splash) : undefined; - this.bannerHash = data.banner ? Util.iconHashToBigInt(data.banner) : undefined; - - this.verificationLevel = data.verification_level; - this.vanityUrlCode = data.vanity_url_code ? data.vanity_url_code : undefined; - this.nsfwLevel = data.nsfw_level; - this.description = data.description ? data.description : undefined; - this.premiumSubscriptionCount = data.premium_subscription_count; - } - - splashHash?: bigint; - bannerHash?: bigint; - - verificationLevel: VerificationLevels; - vanityUrlCode?: string; - nsfwLevel: GuildNsfwLevel; - description?: string; - premiumSubscriptionCount?: number; - - splashURL(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) { - if (this.splashHash) { - return Util.formatImageURL( - Routes.GUILD_SPLASH(this.id, Util.iconBigintToHash(this.splashHash)), - options.size, - options.format, - ); - } - } - - bannerURL(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) { - if (this.bannerHash) { - return Util.formatImageURL( - Routes.GUILD_BANNER(this.id, Util.iconBigintToHash(this.bannerHash)), - options.size, - options.format, - ); - } - } -} - -/** InviteGuild */ -export class InviteGuild extends AnonymousGuild implements Model { - constructor(session: Session, data: Partial) { - super(session, data); - - if (data.welcome_screen) { - this.welcomeScreen = new WelcomeScreen(session, data.welcome_screen); - } - } - - welcomeScreen?: WelcomeScreen; -} - -/** Guild */ -export interface CreateRole { - name?: string; - color?: number; - iconHash?: string | bigint; - unicodeEmoji?: string; - hoist?: boolean; - mentionable?: boolean; -} - -export interface ModifyGuildRole { - name?: string; - color?: number; - hoist?: boolean; - mentionable?: boolean; - unicodeEmoji?: string; -} - -export interface CreateGuildEmoji { - name: string; - image: string; - roles?: Snowflake[]; - reason?: string; -} - -export interface ModifyGuildEmoji { - name?: string; - roles?: Snowflake[]; -} - -/** - * @link https://discord.com/developers/docs/resources/guild#create-guild-ban - */ -export interface CreateGuildBan { - deleteMessageDays?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; - reason?: string; -} - -/** - * @link https://discord.com/developers/docs/resources/guild#modify-guild-member - */ -export interface ModifyGuildMember { - nick?: string; - roles?: Snowflake[]; - mute?: boolean; - deaf?: boolean; - channelId?: Snowflake; - communicationDisabledUntil?: number; -} - -/** - * @link https://discord.com/developers/docs/resources/guild#begin-guild-prune - */ -export interface BeginGuildPrune { - days?: number; - computePruneCount?: boolean; - includeRoles?: Snowflake[]; -} - -export interface ModifyRolePositions { - id: Snowflake; - position?: number | null; -} - -export interface GuildCreateOptionsRole { - id: Snowflake; - name?: string; - color?: number; - hoist?: boolean; - position?: number; - permissions?: bigint; - mentionable?: boolean; - iconURL?: string; - unicodeEmoji?: string | null; -} - -export interface GuildCreateOptionsRole { - id: Snowflake; - name?: string; - color?: number; - hoist?: boolean; - position?: number; - permissions?: bigint; - mentionable?: boolean; - iconHash?: bigint; - unicodeEmoji?: string | null; -} - -export interface GuildCreateOptionsChannel { - id?: Snowflake; - parentId?: Snowflake; - type?: ChannelTypes.GuildText | ChannelTypes.GuildVoice | ChannelTypes.GuildCategory; - name: string; - topic?: string | null; - nsfw?: boolean; - bitrate?: number; - userLimit?: number; - rtcRegion?: string | null; - videoQualityMode?: VideoQualityModes; - permissionOverwrites?: MakeRequired, "id">[]; - rateLimitPerUser?: number; -} - -/** - * @link https://discord.com/developers/docs/resources/guild#create-guild - */ -export interface GuildCreateOptions { - name: string; - afkChannelId?: Snowflake; - afkTimeout?: number; - channels?: GuildCreateOptionsChannel[]; - defaultMessageNotifications?: DefaultMessageNotificationLevels; - explicitContentFilter?: ExplicitContentFilterLevels; - iconURL?: string; - roles?: GuildCreateOptionsRole[]; - systemChannelFlags?: SystemChannelFlags; - systemChannelId?: Snowflake; - verificationLevel?: VerificationLevels; -} - -export interface GuildCreateOptions { - name: string; - afkChannelId?: Snowflake; - afkTimeout?: number; - channels?: GuildCreateOptionsChannel[]; - defaultMessageNotifications?: DefaultMessageNotificationLevels; - explicitContentFilter?: ExplicitContentFilterLevels; - iconHash?: bigint; - roles?: GuildCreateOptionsRole[]; - systemChannelFlags?: SystemChannelFlags; - systemChannelId?: Snowflake; - verificationLevel?: VerificationLevels; -} - -/** - * @link https://discord.com/developers/docs/resources/guild#modify-guild-json-params - */ -export interface GuildEditOptions extends Omit { - ownerId?: Snowflake; - splashURL?: string; - bannerURL?: string; - discoverySplashURL?: string; - features?: GuildFeatures[]; - rulesChannelId?: Snowflake; - description?: string; - premiumProgressBarEnabled?: boolean; -} - -export interface GuildEditOptions extends Omit { - ownerId?: Snowflake; - splashHash?: bigint; - bannerHash?: bigint; - discoverySplashHash?: bigint; - features?: GuildFeatures[]; - rulesChannelId?: Snowflake; - publicUpdatesChannelId?: Snowflake; - preferredLocale?: string | null; - description?: string; - premiumProgressBarEnabled?: boolean; -} - -/** - * Represents a guild - * @link https://discord.com/developers/docs/resources/guild#guild-object - */ -export class Guild extends BaseGuild implements Model { - constructor(session: Session, data: DiscordGuild) { - super(session, data); - - this.splashHash = data.splash ? Util.iconHashToBigInt(data.splash) : undefined; - this.discoverySplashHash = data.discovery_splash ? Util.iconHashToBigInt(data.discovery_splash) : undefined; - this.ownerId = data.owner_id; - this.widgetEnabled = !!data.widget_enabled; - this.widgetChannelId = data.widget_channel_id ? data.widget_channel_id : undefined; - this.vefificationLevel = data.verification_level; - this.defaultMessageNotificationLevel = data.default_message_notifications; - this.explicitContentFilterLevel = data.explicit_content_filter; - - this.members = new Map( - data.members?.map((member) => [data.id, new Member(session, { ...member, user: member.user! }, data.id)]), - ); - - this.roles = new Map( - data.roles.map((role) => [data.id, new Role(session, role, data.id)]), - ); - - this.emojis = new Map( - data.emojis.map((guildEmoji) => [guildEmoji.id!, new GuildEmoji(session, guildEmoji, data.id)]), - ); - - this.channels = new Map( - data.channels?.map((guildChannel) => [guildChannel.id, new GuildChannel(session, guildChannel, data.id)]), - ); - } - - splashHash?: bigint; - discoverySplashHash?: bigint; - ownerId: Snowflake; - widgetEnabled: boolean; - widgetChannelId?: Snowflake; - vefificationLevel: VerificationLevels; - defaultMessageNotificationLevel: DefaultMessageNotificationLevels; - explicitContentFilterLevel: ExplicitContentFilterLevels; - members: Map; - roles: Map; - emojis: Map; - channels: Map; - - /** - * 'null' would reset the nickname - */ - async editBotNickname(options: { nick: string | null; reason?: string }) { - const result = await this.session.rest.runMethod<{ nick?: string } | undefined>( - this.session.rest, - "PATCH", - Routes.USER_NICK(this.id), - options, - ); - - return result?.nick; - } - - async createEmoji(options: CreateGuildEmoji): Promise { - if (options.image && !options.image.startsWith("data:image/")) { - options.image = await urlToBase64(options.image); - } - - const emoji = await this.session.rest.runMethod( - this.session.rest, - "POST", - Routes.GUILD_EMOJIS(this.id), - options, - ); - - return new GuildEmoji(this.session, emoji, this.id); - } - - async deleteEmoji(id: Snowflake, { reason }: { reason?: string } = {}): Promise { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.GUILD_EMOJI(this.id, id), - { reason }, - ); - } - - async editEmoji(id: Snowflake, options: ModifyGuildEmoji): Promise { - const emoji = await this.session.rest.runMethod( - this.session.rest, - "PATCH", - Routes.GUILD_EMOJI(this.id, id), - options, - ); - - return new GuildEmoji(this.session, emoji, this.id); - } - - async createRole(options: CreateRole): Promise { - let icon: string | undefined; - - if (options.iconHash) { - if (typeof options.iconHash === "string") { - icon = options.iconHash; - } else { - icon = Util.iconBigintToHash(options.iconHash); - } - } - - const role = await this.session.rest.runMethod( - this.session.rest, - "PUT", - Routes.GUILD_ROLES(this.id), - { - name: options.name, - color: options.color, - icon, - unicode_emoji: options.unicodeEmoji, - hoist: options.hoist, - mentionable: options.mentionable, - }, - ); - - return new Role(this.session, role, this.id); - } - - async deleteRole(roleId: Snowflake): Promise { - await this.session.rest.runMethod(this.session.rest, "DELETE", Routes.GUILD_ROLE(this.id, roleId)); - } - - async editRole(roleId: Snowflake, options: ModifyGuildRole): Promise { - const role = await this.session.rest.runMethod( - this.session.rest, - "PATCH", - Routes.GUILD_ROLE(this.id, roleId), - { - name: options.name, - color: options.color, - hoist: options.hoist, - mentionable: options.mentionable, - }, - ); - - return new Role(this.session, role, this.id); - } - - async addRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { - await this.session.rest.runMethod( - this.session.rest, - "PUT", - Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId), - { reason }, - ); - } - - async removeRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId), - { reason }, - ); - } - - /** - * Returns the roles moved - */ - async moveRoles(options: ModifyRolePositions[]) { - const roles = await this.session.rest.runMethod( - this.session.rest, - "PATCH", - Routes.GUILD_ROLES(this.id), - options, - ); - - return roles.map((role) => new Role(this.session, role, this.id)); - } - - async deleteInvite(inviteCode: string): Promise { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.INVITE(inviteCode), - {}, - ); - } - - async fetchInvite(inviteCode: string, options: Routes.GetInvite): Promise { - const inviteMetadata = await this.session.rest.runMethod( - this.session.rest, - "GET", - Routes.INVITE(inviteCode, options), - ); - - return new Invite(this.session, inviteMetadata); - } - - async fetchInvites(): Promise { - const invites = await this.session.rest.runMethod( - this.session.rest, - "GET", - Routes.GUILD_INVITES(this.id), - ); - - return invites.map((invite) => new Invite(this.session, invite)); - } - - /** - * Bans the member - */ - async banMember(memberId: Snowflake, options: CreateGuildBan) { - await this.session.rest.runMethod( - this.session.rest, - "PUT", - Routes.GUILD_BAN(this.id, memberId), - options - ? { - delete_message_days: options.deleteMessageDays, - reason: options.reason, - } - : {}, - ); - } - - /** - * Kicks the member - */ - async kickMember(memberId: Snowflake, { reason }: { reason?: string }) { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.GUILD_MEMBER(this.id, memberId), - { reason }, - ); - } - - /* - * Unbans the member - * */ - async unbanMember(memberId: Snowflake) { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.GUILD_BAN(this.id, memberId), - ); - } - - async editMember(memberId: Snowflake, options: ModifyGuildMember) { - const member = await this.session.rest.runMethod( - this.session.rest, - "PATCH", - Routes.GUILD_MEMBER(this.id, memberId), - { - nick: options.nick, - roles: options.roles, - mute: options.mute, - deaf: options.deaf, - channel_id: options.channelId, - communication_disabled_until: options.communicationDisabledUntil - ? new Date(options.communicationDisabledUntil).toISOString() - : undefined, - }, - ); - - return new Member(this.session, member, this.id); - } - - async pruneMembers(options: BeginGuildPrune): Promise { - const result = await this.session.rest.runMethod<{ pruned: number }>( - this.session.rest, - "POST", - Routes.GUILD_PRUNE(this.id), - { - days: options.days, - compute_prune_count: options.computePruneCount, - include_roles: options.includeRoles, - }, - ); - - return result.pruned; - } - - async getPruneCount(): Promise { - const result = await this.session.rest.runMethod<{ pruned: number }>( - this.session.rest, - "GET", - Routes.GUILD_PRUNE(this.id), - ); - - return result.pruned; - } - - async getActiveThreads() { - const { threads, members } = await this.session.rest.runMethod( - this.session.rest, - "GET", - Routes.THREAD_ACTIVE(this.id), - ); - - return { - threads: Object.fromEntries( - threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]), - ) as Record, - members: Object.fromEntries( - members.map((threadMember) => [threadMember.id, new ThreadMember(this.session, threadMember)]), - ) as Record, - }; - } - - /** * - * Makes the bot leave the guild - */ - async leave() { - } - - /** * - * Deletes a guild - */ - async delete() { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.GUILDS(), - ); - } - - /** - * Creates a guild and returns its data, the bot joins the guild - * This was modified from discord.js to make it compatible - * precondition: Bot should be in less than 10 servers - */ - static async create(session: Session, options: GuildCreateOptions) { - const guild = await session.rest.runMethod(session.rest, "POST", Routes.GUILDS(), { - name: options.name, - afk_channel_id: options.afkChannelId, - afk_timeout: options.afkTimeout, - default_message_notifications: options.defaultMessageNotifications, - explicit_content_filter: options.explicitContentFilter, - system_channel_flags: options.systemChannelFlags, - verification_level: options.verificationLevel, - icon: "iconURL" in options - ? options.iconURL || urlToBase64(options.iconURL!) - : options.iconHash || Util.iconBigintToHash(options.iconHash!), - channels: options.channels?.map((channel) => ({ - name: channel.name, - nsfw: channel.nsfw, - id: channel.id, - bitrate: channel.bitrate, - parent_id: channel.parentId, - permission_overwrites: channel.permissionOverwrites, - rtc_region: channel.rtcRegion, - user_limit: channel.userLimit, - video_quality_mode: channel.videoQualityMode, - rate_limit_per_user: channel.rateLimitPerUser, - })), - roles: options.roles?.map((role) => ({ - name: role.name, - id: role.id, - color: role.color, - mentionable: role.mentionable, - hoist: role.hoist, - position: role.position, - unicode_emoji: role.unicodeEmoji, - icon: options.iconURL || urlToBase64(options.iconURL!), - })), - }); - - return new Guild(session, guild); - } - - /** - * Edits a guild and returns its data - */ - async edit(session: Session, options: GuildEditOptions) { - const guild = await session.rest.runMethod(session.rest, "PATCH", Routes.GUILDS(), { - name: options.name, - afk_channel_id: options.afkChannelId, - afk_timeout: options.afkTimeout, - default_message_notifications: options.defaultMessageNotifications, - explicit_content_filter: options.explicitContentFilter, - system_channel_flags: options.systemChannelFlags, - verification_level: options.verificationLevel, - icon: "iconURL" in options - ? options.iconURL || urlToBase64(options.iconURL!) - : options.iconHash || Util.iconBigintToHash(options.iconHash!), - // extra props - splash: "splashURL" in options - ? options.splashURL || urlToBase64(options.splashURL!) - : options.splashHash || Util.iconBigintToHash(options.iconHash!), - banner: "bannerURL" in options ? options.bannerURL || urlToBase64(options.bannerURL!) - : options.bannerHash || Util.iconBigintToHash(options.bannerHash!), - discovery_splash: "discoverySplashURL" in options - ? options.discoverySplashURL || urlToBase64(options.discoverySplashURL!) - : options.discoverySplashHash || Util.iconBigintToHash(options.discoverySplashHash!), - owner_id: options.ownerId, - rules_channel_id: options.rulesChannelId, - public_updates_channel_id: options.publicUpdatesChannelId, - preferred_locale: options.preferredLocale, - features: options.features, - description: options.description, - premiumProgressBarEnabled: options.premiumProgressBarEnabled, - }); - - return new Guild(session, guild); - } -} - -export default Guild \ No newline at end of file +import type { Model } from "./Base.ts"; +import type { Session } from "../Session.ts"; +import type { + ChannelTypes, + DefaultMessageNotificationLevels, + DiscordEmoji, + DiscordGuild, + DiscordInviteMetadata, + DiscordListActiveThreads, + DiscordMemberWithUser, + DiscordOverwrite, + DiscordRole, + ExplicitContentFilterLevels, + GuildNsfwLevel, + MakeRequired, + SystemChannelFlags, + VerificationLevels, + VideoQualityModes, +} from "../../discordeno/mod.ts"; +import type { ImageFormat, ImageSize } from "../Util.ts"; +import { GuildFeatures } from "../../discordeno/mod.ts"; +import { Snowflake } from "../Snowflake.ts"; +import Util from "../Util.ts"; +import * as Routes from "../Routes.ts"; +import WelcomeScreen from "./WelcomeScreen.ts"; +import { GuildChannel, ThreadChannel } from "./channels.ts"; +import ThreadMember from "./ThreadMember.ts"; +import Member from "./Member.ts"; +import Role from "./Role.ts"; +import GuildEmoji from "./GuildEmoji.ts"; +import { urlToBase64 } from "../util/urlToBase64.ts"; +import Invite from "./Invite.ts"; + +/** BaseGuild */ +/** + * Class for {@link Guild} and {@link AnonymousGuild} + */ +export abstract class BaseGuild implements Model { + constructor(session: Session, data: DiscordGuild) { + this.session = session; + this.id = data.id; + + this.name = data.name; + this.iconHash = data.icon ? Util.iconHashToBigInt(data.icon) : undefined; + + this.features = data.features; + } + + readonly session: Session; + readonly id: Snowflake; + + name: string; + iconHash?: bigint; + features: GuildFeatures[]; + + get createdTimestamp() { + return Snowflake.snowflakeToTimestamp(this.id); + } + + get createdAt() { + return new Date(this.createdTimestamp); + } + + get partnered() { + return this.features.includes(GuildFeatures.Partnered); + } + + get verified() { + return this.features.includes(GuildFeatures.Verified); + } + + iconURL(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) { + if (this.iconHash) { + return Util.formatImageURL( + Routes.GUILD_ICON(this.id, Util.iconBigintToHash(this.iconHash)), + options.size, + options.format, + ); + } + } + + toString() { + return this.name; + } +} + +/** AnonymousGuild */ +export class AnonymousGuild extends BaseGuild implements Model { + constructor(session: Session, data: Partial); // TODO: Improve this type (name and id are required) + constructor(session: Session, data: DiscordGuild) { + super(session, data); + + this.splashHash = data.splash ? Util.iconHashToBigInt(data.splash) : undefined; + this.bannerHash = data.banner ? Util.iconHashToBigInt(data.banner) : undefined; + + this.verificationLevel = data.verification_level; + this.vanityUrlCode = data.vanity_url_code ? data.vanity_url_code : undefined; + this.nsfwLevel = data.nsfw_level; + this.description = data.description ? data.description : undefined; + this.premiumSubscriptionCount = data.premium_subscription_count; + } + + splashHash?: bigint; + bannerHash?: bigint; + + verificationLevel: VerificationLevels; + vanityUrlCode?: string; + nsfwLevel: GuildNsfwLevel; + description?: string; + premiumSubscriptionCount?: number; + + splashURL(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) { + if (this.splashHash) { + return Util.formatImageURL( + Routes.GUILD_SPLASH(this.id, Util.iconBigintToHash(this.splashHash)), + options.size, + options.format, + ); + } + } + + bannerURL(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) { + if (this.bannerHash) { + return Util.formatImageURL( + Routes.GUILD_BANNER(this.id, Util.iconBigintToHash(this.bannerHash)), + options.size, + options.format, + ); + } + } +} + +/** InviteGuild */ +export class InviteGuild extends AnonymousGuild implements Model { + constructor(session: Session, data: Partial) { + super(session, data); + + if (data.welcome_screen) { + this.welcomeScreen = new WelcomeScreen(session, data.welcome_screen); + } + } + + welcomeScreen?: WelcomeScreen; +} + +/** Guild */ +export interface CreateRole { + name?: string; + color?: number; + iconHash?: string | bigint; + unicodeEmoji?: string; + hoist?: boolean; + mentionable?: boolean; +} + +export interface ModifyGuildRole { + name?: string; + color?: number; + hoist?: boolean; + mentionable?: boolean; + unicodeEmoji?: string; +} + +export interface CreateGuildEmoji { + name: string; + image: string; + roles?: Snowflake[]; + reason?: string; +} + +export interface ModifyGuildEmoji { + name?: string; + roles?: Snowflake[]; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#create-guild-ban + */ +export interface CreateGuildBan { + deleteMessageDays?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + reason?: string; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#modify-guild-member + */ +export interface ModifyGuildMember { + nick?: string; + roles?: Snowflake[]; + mute?: boolean; + deaf?: boolean; + channelId?: Snowflake; + communicationDisabledUntil?: number; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#begin-guild-prune + */ +export interface BeginGuildPrune { + days?: number; + computePruneCount?: boolean; + includeRoles?: Snowflake[]; +} + +export interface ModifyRolePositions { + id: Snowflake; + position?: number | null; +} + +export interface GuildCreateOptionsRole { + id: Snowflake; + name?: string; + color?: number; + hoist?: boolean; + position?: number; + permissions?: bigint; + mentionable?: boolean; + iconURL?: string; + unicodeEmoji?: string | null; +} + +export interface GuildCreateOptionsRole { + id: Snowflake; + name?: string; + color?: number; + hoist?: boolean; + position?: number; + permissions?: bigint; + mentionable?: boolean; + iconHash?: bigint; + unicodeEmoji?: string | null; +} + +export interface GuildCreateOptionsChannel { + id?: Snowflake; + parentId?: Snowflake; + type?: ChannelTypes.GuildText | ChannelTypes.GuildVoice | ChannelTypes.GuildCategory; + name: string; + topic?: string | null; + nsfw?: boolean; + bitrate?: number; + userLimit?: number; + rtcRegion?: string | null; + videoQualityMode?: VideoQualityModes; + permissionOverwrites?: MakeRequired, "id">[]; + rateLimitPerUser?: number; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#create-guild + */ +export interface GuildCreateOptions { + name: string; + afkChannelId?: Snowflake; + afkTimeout?: number; + channels?: GuildCreateOptionsChannel[]; + defaultMessageNotifications?: DefaultMessageNotificationLevels; + explicitContentFilter?: ExplicitContentFilterLevels; + iconURL?: string; + roles?: GuildCreateOptionsRole[]; + systemChannelFlags?: SystemChannelFlags; + systemChannelId?: Snowflake; + verificationLevel?: VerificationLevels; +} + +export interface GuildCreateOptions { + name: string; + afkChannelId?: Snowflake; + afkTimeout?: number; + channels?: GuildCreateOptionsChannel[]; + defaultMessageNotifications?: DefaultMessageNotificationLevels; + explicitContentFilter?: ExplicitContentFilterLevels; + iconHash?: bigint; + roles?: GuildCreateOptionsRole[]; + systemChannelFlags?: SystemChannelFlags; + systemChannelId?: Snowflake; + verificationLevel?: VerificationLevels; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#modify-guild-json-params + */ +export interface GuildEditOptions extends Omit { + ownerId?: Snowflake; + splashURL?: string; + bannerURL?: string; + discoverySplashURL?: string; + features?: GuildFeatures[]; + rulesChannelId?: Snowflake; + description?: string; + premiumProgressBarEnabled?: boolean; +} + +export interface GuildEditOptions extends Omit { + ownerId?: Snowflake; + splashHash?: bigint; + bannerHash?: bigint; + discoverySplashHash?: bigint; + features?: GuildFeatures[]; + rulesChannelId?: Snowflake; + publicUpdatesChannelId?: Snowflake; + preferredLocale?: string | null; + description?: string; + premiumProgressBarEnabled?: boolean; +} + +/** + * Represents a guild + * @link https://discord.com/developers/docs/resources/guild#guild-object + */ +export class Guild extends BaseGuild implements Model { + constructor(session: Session, data: DiscordGuild) { + super(session, data); + + this.splashHash = data.splash ? Util.iconHashToBigInt(data.splash) : undefined; + this.discoverySplashHash = data.discovery_splash ? Util.iconHashToBigInt(data.discovery_splash) : undefined; + this.ownerId = data.owner_id; + this.widgetEnabled = !!data.widget_enabled; + this.widgetChannelId = data.widget_channel_id ? data.widget_channel_id : undefined; + this.vefificationLevel = data.verification_level; + this.defaultMessageNotificationLevel = data.default_message_notifications; + this.explicitContentFilterLevel = data.explicit_content_filter; + + this.members = new Map( + data.members?.map((member) => [data.id, new Member(session, { ...member, user: member.user! }, data.id)]), + ); + + this.roles = new Map( + data.roles.map((role) => [data.id, new Role(session, role, data.id)]), + ); + + this.emojis = new Map( + data.emojis.map((guildEmoji) => [guildEmoji.id!, new GuildEmoji(session, guildEmoji, data.id)]), + ); + + this.channels = new Map( + data.channels?.map((guildChannel) => [guildChannel.id, new GuildChannel(session, guildChannel, data.id)]), + ); + } + + splashHash?: bigint; + discoverySplashHash?: bigint; + ownerId: Snowflake; + widgetEnabled: boolean; + widgetChannelId?: Snowflake; + vefificationLevel: VerificationLevels; + defaultMessageNotificationLevel: DefaultMessageNotificationLevels; + explicitContentFilterLevel: ExplicitContentFilterLevels; + members: Map; + roles: Map; + emojis: Map; + channels: Map; + + /** + * 'null' would reset the nickname + */ + async editBotNickname(options: { nick: string | null; reason?: string }) { + const result = await this.session.rest.runMethod<{ nick?: string } | undefined>( + this.session.rest, + "PATCH", + Routes.USER_NICK(this.id), + options, + ); + + return result?.nick; + } + + async createEmoji(options: CreateGuildEmoji): Promise { + if (options.image && !options.image.startsWith("data:image/")) { + options.image = await urlToBase64(options.image); + } + + const emoji = await this.session.rest.runMethod( + this.session.rest, + "POST", + Routes.GUILD_EMOJIS(this.id), + options, + ); + + return new GuildEmoji(this.session, emoji, this.id); + } + + async deleteEmoji(id: Snowflake, { reason }: { reason?: string } = {}): Promise { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_EMOJI(this.id, id), + { reason }, + ); + } + + async editEmoji(id: Snowflake, options: ModifyGuildEmoji): Promise { + const emoji = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_EMOJI(this.id, id), + options, + ); + + return new GuildEmoji(this.session, emoji, this.id); + } + + async createRole(options: CreateRole): Promise { + let icon: string | undefined; + + if (options.iconHash) { + if (typeof options.iconHash === "string") { + icon = options.iconHash; + } else { + icon = Util.iconBigintToHash(options.iconHash); + } + } + + const role = await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_ROLES(this.id), + { + name: options.name, + color: options.color, + icon, + unicode_emoji: options.unicodeEmoji, + hoist: options.hoist, + mentionable: options.mentionable, + }, + ); + + return new Role(this.session, role, this.id); + } + + async deleteRole(roleId: Snowflake): Promise { + await this.session.rest.runMethod(this.session.rest, "DELETE", Routes.GUILD_ROLE(this.id, roleId)); + } + + async editRole(roleId: Snowflake, options: ModifyGuildRole): Promise { + const role = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_ROLE(this.id, roleId), + { + name: options.name, + color: options.color, + hoist: options.hoist, + mentionable: options.mentionable, + }, + ); + + return new Role(this.session, role, this.id); + } + + async addRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { + await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId), + { reason }, + ); + } + + async removeRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId), + { reason }, + ); + } + + /** + * Returns the roles moved + */ + async moveRoles(options: ModifyRolePositions[]) { + const roles = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_ROLES(this.id), + options, + ); + + return roles.map((role) => new Role(this.session, role, this.id)); + } + + async deleteInvite(inviteCode: string): Promise { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.INVITE(inviteCode), + {}, + ); + } + + async fetchInvite(inviteCode: string, options: Routes.GetInvite): Promise { + const inviteMetadata = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.INVITE(inviteCode, options), + ); + + return new Invite(this.session, inviteMetadata); + } + + async fetchInvites(): Promise { + const invites = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.GUILD_INVITES(this.id), + ); + + return invites.map((invite) => new Invite(this.session, invite)); + } + + /** + * Bans the member + */ + async banMember(memberId: Snowflake, options: CreateGuildBan) { + await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_BAN(this.id, memberId), + options + ? { + delete_message_days: options.deleteMessageDays, + reason: options.reason, + } + : {}, + ); + } + + /** + * Kicks the member + */ + async kickMember(memberId: Snowflake, { reason }: { reason?: string }) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_MEMBER(this.id, memberId), + { reason }, + ); + } + + /* + * Unbans the member + * */ + async unbanMember(memberId: Snowflake) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_BAN(this.id, memberId), + ); + } + + async editMember(memberId: Snowflake, options: ModifyGuildMember) { + const member = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_MEMBER(this.id, memberId), + { + nick: options.nick, + roles: options.roles, + mute: options.mute, + deaf: options.deaf, + channel_id: options.channelId, + communication_disabled_until: options.communicationDisabledUntil + ? new Date(options.communicationDisabledUntil).toISOString() + : undefined, + }, + ); + + return new Member(this.session, member, this.id); + } + + async pruneMembers(options: BeginGuildPrune): Promise { + const result = await this.session.rest.runMethod<{ pruned: number }>( + this.session.rest, + "POST", + Routes.GUILD_PRUNE(this.id), + { + days: options.days, + compute_prune_count: options.computePruneCount, + include_roles: options.includeRoles, + }, + ); + + return result.pruned; + } + + async getPruneCount(): Promise { + const result = await this.session.rest.runMethod<{ pruned: number }>( + this.session.rest, + "GET", + Routes.GUILD_PRUNE(this.id), + ); + + return result.pruned; + } + + async getActiveThreads() { + const { threads, members } = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.THREAD_ACTIVE(this.id), + ); + + return { + threads: Object.fromEntries( + threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]), + ) as Record, + members: Object.fromEntries( + members.map((threadMember) => [threadMember.id, new ThreadMember(this.session, threadMember)]), + ) as Record, + }; + } + + /** * + * Makes the bot leave the guild + */ + async leave() { + } + + /** * + * Deletes a guild + */ + async delete() { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILDS(), + ); + } + + /** + * Creates a guild and returns its data, the bot joins the guild + * This was modified from discord.js to make it compatible + * precondition: Bot should be in less than 10 servers + */ + static async create(session: Session, options: GuildCreateOptions) { + const guild = await session.rest.runMethod(session.rest, "POST", Routes.GUILDS(), { + name: options.name, + afk_channel_id: options.afkChannelId, + afk_timeout: options.afkTimeout, + default_message_notifications: options.defaultMessageNotifications, + explicit_content_filter: options.explicitContentFilter, + system_channel_flags: options.systemChannelFlags, + verification_level: options.verificationLevel, + icon: "iconURL" in options + ? options.iconURL || urlToBase64(options.iconURL!) + : options.iconHash || Util.iconBigintToHash(options.iconHash!), + channels: options.channels?.map((channel) => ({ + name: channel.name, + nsfw: channel.nsfw, + id: channel.id, + bitrate: channel.bitrate, + parent_id: channel.parentId, + permission_overwrites: channel.permissionOverwrites, + rtc_region: channel.rtcRegion, + user_limit: channel.userLimit, + video_quality_mode: channel.videoQualityMode, + rate_limit_per_user: channel.rateLimitPerUser, + })), + roles: options.roles?.map((role) => ({ + name: role.name, + id: role.id, + color: role.color, + mentionable: role.mentionable, + hoist: role.hoist, + position: role.position, + unicode_emoji: role.unicodeEmoji, + icon: options.iconURL || urlToBase64(options.iconURL!), + })), + }); + + return new Guild(session, guild); + } + + /** + * Edits a guild and returns its data + */ + async edit(session: Session, options: GuildEditOptions) { + const guild = await session.rest.runMethod(session.rest, "PATCH", Routes.GUILDS(), { + name: options.name, + afk_channel_id: options.afkChannelId, + afk_timeout: options.afkTimeout, + default_message_notifications: options.defaultMessageNotifications, + explicit_content_filter: options.explicitContentFilter, + system_channel_flags: options.systemChannelFlags, + verification_level: options.verificationLevel, + icon: "iconURL" in options + ? options.iconURL || urlToBase64(options.iconURL!) + : options.iconHash || Util.iconBigintToHash(options.iconHash!), + // extra props + splash: "splashURL" in options + ? options.splashURL || urlToBase64(options.splashURL!) + : options.splashHash || Util.iconBigintToHash(options.iconHash!), + banner: "bannerURL" in options + ? options.bannerURL || urlToBase64(options.bannerURL!) + : options.bannerHash || Util.iconBigintToHash(options.bannerHash!), + discovery_splash: "discoverySplashURL" in options + ? options.discoverySplashURL || urlToBase64(options.discoverySplashURL!) + : options.discoverySplashHash || Util.iconBigintToHash(options.discoverySplashHash!), + owner_id: options.ownerId, + rules_channel_id: options.rulesChannelId, + public_updates_channel_id: options.publicUpdatesChannelId, + preferred_locale: options.preferredLocale, + features: options.features, + description: options.description, + premiumProgressBarEnabled: options.premiumProgressBarEnabled, + }); + + return new Guild(session, guild); + } +} + +export default Guild; diff --git a/packages/discordeno/types/shared.ts b/packages/discordeno/types/shared.ts index 8d86e7a..b3c189a 100644 --- a/packages/discordeno/types/shared.ts +++ b/packages/discordeno/types/shared.ts @@ -1237,7 +1237,7 @@ export type CamelCase = S extends `${infer P1}_${infer P2}${in : Lowercase; export type Camelize = { [K in keyof T as CamelCase]: T[K] extends Array ? U extends {} ? Array> - : T[K] + : T[K] : T[K] extends {} ? Camelize : never; };