diff --git a/.eggignore b/.eggignore new file mode 100644 index 0000000..ecde67e --- /dev/null +++ b/.eggignore @@ -0,0 +1,3 @@ +extends .gitignore +./src/test/**/* + diff --git a/README.md b/README.md index d2191f7..f750ffe 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,15 @@ You can import the package from https://deno.land/x/harmony/mod.ts (with latest version) or can add a version too, and raw GitHub URL (latest unpublished version) https://raw.githubusercontent.com/harmonyland/harmony/main/mod.ts too. +You can also check(not import) the module in https://nest.land/package/harmony (link for importing is in the site). + For a quick example, run this: ```bash deno run --allow-net https://deno.land/x/harmony/examples/ping.ts ``` -And input your bot's token and Intents. +And input your bot's token. Here is a small example of how to use harmony, diff --git a/egg.json b/egg.json new file mode 100644 index 0000000..43e6d96 --- /dev/null +++ b/egg.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://x.nest.land/eggs@0.3.4/src/schema.json", + "name": "harmony", + "entry": "./mod.ts", + "description": "An easy to use Discord API Library for Deno.", + "homepage": "https://github.com/harmonyland/harmony", + "version": "v1.1.3", + "files": [ + "./src/**/*", + "./deps.ts", + "./README.md", + "./LICENSE", + "./banner.png", + "./CONTRIBUTING.md", + "./CODE_OF_CONDUCT.md", + "./examples/*" + ], + "checkFormat": "npx eslint src", + "checkTests": false, + "checkInstallation": false, + "check": true, + "unlisted": false, + "ignore": [] +} diff --git a/mod.ts b/mod.ts index bdfbde3..227ef63 100644 --- a/mod.ts +++ b/mod.ts @@ -14,7 +14,8 @@ export { CommandBuilder, CommandCategory, CommandsManager, - CategoriesManager + CategoriesManager, + CommandsLoader } from './src/models/command.ts' export type { CommandContext, CommandOptions } from './src/models/command.ts' export { @@ -42,6 +43,7 @@ export { ReactionUsersManager } from './src/managers/reactionUsers.ts' export { MessagesManager } from './src/managers/messages.ts' export { RolesManager } from './src/managers/roles.ts' export { UsersManager } from './src/managers/users.ts' +export { InviteManager } from './src/managers/invites.ts' export { Application } from './src/structures/application.ts' // export { ImageURL } from './src/structures/cdn.ts' export { Channel } from './src/structures/channel.ts' diff --git a/src/managers/base.ts b/src/managers/base.ts index e522d31..442b7fc 100644 --- a/src/managers/base.ts +++ b/src/managers/base.ts @@ -60,6 +60,13 @@ export class BaseManager { return collection } + async *[Symbol.asyncIterator](): AsyncIterableIterator { + const arr = (await this.array()) ?? [] + const { readable, writable } = new TransformStream() + arr.forEach((el) => writable.getWriter().write(el)) + yield* readable.getIterator() + } + /** Deletes everything from Cache */ flush(): any { return this.client.cache.deleteCache(this.cacheName) diff --git a/src/managers/baseChild.ts b/src/managers/baseChild.ts index adc96f8..0842859 100644 --- a/src/managers/baseChild.ts +++ b/src/managers/baseChild.ts @@ -39,4 +39,11 @@ export class BaseChildManager { } return collection } + + async *[Symbol.asyncIterator](): AsyncIterableIterator { + const arr = (await this.array()) ?? [] + const { readable, writable } = new TransformStream() + arr.forEach((el: unknown) => writable.getWriter().write(el)) + yield* readable.getIterator() + } } diff --git a/src/managers/guildChannels.ts b/src/managers/guildChannels.ts index 9d86342..2bc6837 100644 --- a/src/managers/guildChannels.ts +++ b/src/managers/guildChannels.ts @@ -66,8 +66,7 @@ export class GuildChannelsManager extends BaseChildManager< async create(options: CreateChannelOptions): Promise { if (options.name === undefined) throw new Error('name is required for GuildChannelsManager#create') - const res = ((await this.client.rest.post(GUILD_CHANNELS(this.guild.id)), - { + const res = ((await this.client.rest.post(GUILD_CHANNELS(this.guild.id), { name: options.name, type: options.type, topic: options.topic, @@ -83,7 +82,7 @@ export class GuildChannelsManager extends BaseChildManager< ? options.parent.id : options.parent, nsfw: options.nsfw - }) as unknown) as GuildChannelPayload + })) as unknown) as GuildChannelPayload await this.set(res.id, res) const channel = await this.get(res.id) diff --git a/src/managers/guilds.ts b/src/managers/guilds.ts index 8faf478..772f476 100644 --- a/src/managers/guilds.ts +++ b/src/managers/guilds.ts @@ -147,6 +147,7 @@ export class GuildManager extends BaseManager { /** Sets a value to Cache */ async set(key: string, value: GuildPayload): Promise { + value = { ...value } if ('roles' in value) value.roles = [] if ('emojis' in value) value.emojis = [] if ('members' in value) value.members = [] diff --git a/src/models/rest.ts b/src/models/rest.ts index daab5d1..2914d2e 100644 --- a/src/models/rest.ts +++ b/src/models/rest.ts @@ -37,6 +37,7 @@ export interface DiscordAPIErrorPayload { code?: number message?: string errors: object + requestData: { [key: string]: any } } export class DiscordAPIError extends Error { @@ -319,7 +320,9 @@ export class RESTManager { } } const form = new FormData() - files.forEach((file, index) => form.append(`file${index + 1}`, file.blob, file.name)) + files.forEach((file, index) => + form.append(`file${index + 1}`, file.blob, file.name) + ) const json = JSON.stringify(body) form.append('payload_json', json) if (body === undefined) body = {} @@ -465,7 +468,8 @@ export class RESTManager { ).map((entry) => { return [entry[0], entry[1]._errors ?? []] }) - ) + ), + requestData: data } // if (typeof error.errors === 'object') { diff --git a/src/structures/cdn.ts b/src/structures/cdn.ts index 83ff27c..78c8495 100644 --- a/src/structures/cdn.ts +++ b/src/structures/cdn.ts @@ -3,11 +3,13 @@ import { ImageFormats, ImageSize } from '../types/cdn.ts' /** Function to get Image URL from a resource on Discord CDN */ export const ImageURL = ( url: string, - format: ImageFormats | undefined = 'png', - size: ImageSize | undefined = 128 + format: ImageFormats = 'png', + size: ImageSize = 128 ): string => { - size = size === undefined ? 128 : size if (url.includes('a_')) { - return `${url}.${format === undefined ? 'gif' : format}?size=${size}` - } else return `${url}.${format === 'gif' ? 'png' : format}?size=${size}` + return `${url}.${format === 'dynamic' ? 'gif' : format}?size=${size}` + } else + return `${url}.${ + format === 'gif' || format === 'dynamic' ? 'png' : format + }?size=${size}` } diff --git a/src/structures/emoji.ts b/src/structures/emoji.ts index fe71889..19cd8e1 100644 --- a/src/structures/emoji.ts +++ b/src/structures/emoji.ts @@ -1,8 +1,10 @@ import { Client } from '../models/client.ts' +import { ImageSize } from '../types/cdn.ts' import { EmojiPayload } from '../types/emoji.ts' -import { EMOJI } from '../types/endpoint.ts' +import { CUSTOM_EMOJI, EMOJI } from '../types/endpoint.ts' import { Snowflake } from '../utils/snowflake.ts' import { Base } from './base.ts' +import { ImageURL } from './cdn.ts' import { Guild } from './guild.ts' import { Role } from './role.ts' import { User } from './user.ts' @@ -54,6 +56,18 @@ export class Emoji extends Base { this.available = data.available } + /** + * Gets emoji image URL + */ + emojiImageURL( + format: 'png' | 'gif' | 'dynamic' = 'png', + size: ImageSize = 512 + ): string | undefined { + return this.id != null + ? `${ImageURL(CUSTOM_EMOJI(this.id), format, size)}` + : undefined + } + /** Modify the given emoji. Requires the MANAGE_EMOJIS permission. Returns the updated emoji object on success. Fires a Guild Emojis Update Gateway event. */ async edit(data: ModifyGuildEmojiParams): Promise { if (this.id === null) throw new Error('Emoji ID is not valid.') diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 11a9d93..81bbca7 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -32,9 +32,13 @@ import { User } from './user.ts' import { Application } from './application.ts' import { GUILD_BAN, + GUILD_BANNER, GUILD_BANS, + GUILD_DISCOVERY_SPLASH, + GUILD_ICON, GUILD_INTEGRATIONS, - GUILD_PRUNE + GUILD_PRUNE, + GUILD_SPLASH } from '../types/endpoint.ts' import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' import { RequestMembersOptions } from '../gateway/index.ts' @@ -42,6 +46,8 @@ import { GuildPresencesManager } from '../managers/presences.ts' import { TemplatePayload } from '../types/template.ts' import { Template } from './template.ts' import { DiscordAPIError } from '../models/rest.ts' +import { ImageFormats, ImageSize } from '../types/cdn.ts' +import { ImageURL } from './cdn.ts' export class GuildBan extends Base { guild: Guild @@ -258,6 +264,58 @@ export class Guild extends SnowflakeBase { } } + /** + * Gets guild icon URL + */ + iconURL( + format: ImageFormats = 'png', + size: ImageSize = 512 + ): string | undefined { + return this.icon != null + ? `${ImageURL(GUILD_ICON(this.id, this.icon), format, size)}` + : undefined + } + + /** + * Gets guild splash URL + */ + splashURL( + format: ImageFormats = 'png', + size: ImageSize = 512 + ): string | undefined { + return this.splash != null + ? `${ImageURL(GUILD_SPLASH(this.id, this.splash), format, size)}` + : undefined + } + + /** + * Gets guild discover splash URL + */ + discoverSplashURL( + format: ImageFormats = 'png', + size: ImageSize = 512 + ): string | undefined { + return this.discoverySplash != null + ? `${ImageURL( + GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), + format, + size + )}` + : undefined + } + + /** + * Gets guild banner URL + */ + bannerURL( + format: ImageFormats = 'png', + size: ImageSize = 512 + ): string | undefined { + return this.banner != null + ? `${ImageURL(GUILD_BANNER(this.id, this.banner), format, size)}` + : undefined + } + /** * Gets Everyone role of the Guild */ diff --git a/src/test/index.ts b/src/test/index.ts index ba93b2a..e9df716 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -215,6 +215,30 @@ client.on('messageCreate', async (msg: Message) => { .map((e) => `${e[0]}: ${e[1] === true ? '`✅`' : '`❌`'}`) .join('\n') ) + msg.channel.send(`Your permissions:\n${permissions.toArray().join('\n')}`) + } else if (msg.content === '!addAllRoles') { + const roles = await msg.guild?.roles.array() + if (roles !== undefined) { + roles.forEach(async (role) => { + await msg.member?.roles.add(role) + console.log(role) + }) + } + } else if (msg.content === '!createAndAddRole') { + if (msg.guild !== undefined) { + const role = await msg.guild.roles.create({ + name: 'asdf', + permissions: 0 + }) + await msg.member?.roles.add(role) + } + } else if (msg.content === '!roles') { + let buf = 'Roles:' + if (msg.member === undefined) return + for await (const role of msg.member.roles) { + buf += `\n${role.name}` + } + msg.reply(buf) } }) diff --git a/src/types/cdn.ts b/src/types/cdn.ts index 4fbfc13..b78bd48 100644 --- a/src/types/cdn.ts +++ b/src/types/cdn.ts @@ -1,2 +1,2 @@ export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 -export type ImageFormats = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' +export type ImageFormats = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' | 'dynamic' diff --git a/src/utils/bitfield.ts b/src/utils/bitfield.ts index 8631f1f..9e77396 100644 --- a/src/utils/bitfield.ts +++ b/src/utils/bitfield.ts @@ -14,7 +14,10 @@ export class BitField { #flags: { [name: string]: number | bigint } = {} bitfield: bigint - constructor(flags: { [name: string]: number | bigint }, bits: any) { + constructor( + flags: { [name: string]: number | bigint }, + bits: BitFieldResolvable + ) { this.#flags = flags this.bitfield = BitField.resolve(this.#flags, bits) } @@ -104,11 +107,11 @@ export class BitField { if (bit instanceof BitField) return this.resolve(flags, bit.bitfield) if (Array.isArray(bit)) return (bit.map as any)((p: any) => this.resolve(flags, p)).reduce( - (prev: any, p: any) => prev | p, - 0 + (prev: bigint, p: bigint) => prev | p, + 0n ) if (typeof bit === 'string' && typeof flags[bit] !== 'undefined') - return flags[bit] + return BigInt(flags[bit]) const error = new RangeError('BITFIELD_INVALID') throw error }