Merge pull request #29 from DjDeveloperr/main
Fixed Member Caching, Message#member, Member#roles
This commit is contained in:
commit
d68f168a55
35 changed files with 489 additions and 363 deletions
|
@ -8,7 +8,7 @@
|
|||
* Lightweight and easy to use.
|
||||
* Built-in Command Framework,
|
||||
* Easily build Commands on the fly.
|
||||
* Compltely Customizable.
|
||||
* Completely Customizable.
|
||||
* Complete Object-Oriented approach.
|
||||
* 100% Discord API Coverage.
|
||||
* Customizable caching.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Channel } from '../../structures/channel.ts'
|
||||
import { Guild } from "../../structures/guild.ts"
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts'
|
||||
import getChannelByType from '../../utils/getChannelByType.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
|
|
|
@ -8,9 +8,7 @@ export const guildBanAdd: GatewayEventHandler = async (
|
|||
d: GuildBanAddPayload
|
||||
) => {
|
||||
const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id)
|
||||
const user: User =
|
||||
(await gateway.client.users.get(d.user.id)) ??
|
||||
new User(gateway.client, d.user)
|
||||
const user: User = await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user)
|
||||
|
||||
if (guild !== undefined) {
|
||||
await guild.members.delete(user.id)
|
||||
|
|
|
@ -9,10 +9,9 @@ export const guildBanRemove: GatewayEventHandler = async (
|
|||
) => {
|
||||
const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id)
|
||||
const user: User =
|
||||
(await gateway.client.users.get(d.user.id)) ??
|
||||
new User(gateway.client, d.user)
|
||||
await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user)
|
||||
|
||||
if (guild !== undefined) {
|
||||
gateway.client.emit('guildBanRemove', guild, user)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,8 +10,8 @@ import { guildUpdate } from './guildUpdate.ts'
|
|||
import { guildBanAdd } from './guildBanAdd.ts'
|
||||
import { ready } from './ready.ts'
|
||||
import { guildBanRemove } from './guildBanRemove.ts'
|
||||
import { messageCreate } from "./messageCreate.ts"
|
||||
import { resume } from "./resume.ts"
|
||||
import { messageCreate } from './messageCreate.ts'
|
||||
import { resume } from './resume.ts'
|
||||
import { reconnect } from './reconnect.ts'
|
||||
|
||||
export const gatewayHandlers: {
|
||||
|
|
|
@ -17,11 +17,26 @@ export const messageCreate: GatewayEventHandler = async (
|
|||
const user = new User(gateway.client, d.author)
|
||||
await gateway.client.users.set(d.author.id, d.author)
|
||||
let guild
|
||||
let member
|
||||
if (d.guild_id !== undefined) {
|
||||
guild = await gateway.client.guilds.get(d.guild_id)
|
||||
}
|
||||
if (guild !== undefined && d.member !== undefined) {
|
||||
d.member.user = d.author
|
||||
await guild.members.set(d.author.id, d.member)
|
||||
member = await guild.members.get(d.author.id)
|
||||
}
|
||||
const mentions = new MessageMentions()
|
||||
const message = new Message(gateway.client, d, channel as any, user, mentions)
|
||||
message.member = member
|
||||
if (guild !== undefined) message.guild = guild
|
||||
if (message.member !== undefined) {
|
||||
if (message.member.user === undefined) {
|
||||
const user = await gateway.client.users.get(message.member.id)
|
||||
if (user !== undefined) {
|
||||
message.member.user = user
|
||||
}
|
||||
}
|
||||
}
|
||||
gateway.client.emit('messageCreate', message)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway , GatewayEventHandler } from "../index.ts"
|
||||
import { Gateway , GatewayEventHandler } from '../index.ts'
|
||||
|
||||
export const reconnect: GatewayEventHandler = async (gateway: Gateway, d: any) => {
|
||||
gateway.reconnect()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { User } from "../../structures/user.ts"
|
||||
import { CLIENT_USER } from "../../types/endpoint.ts"
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { CLIENT_USER } from '../../types/endpoint.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
|
||||
export const resume: GatewayEventHandler = async (gateway: Gateway, d: any) => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Client } from "../models/client.ts";
|
||||
import { Collection } from "../utils/collection.ts";
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
|
||||
export class BaseManager<T, T2> {
|
||||
client: Client
|
||||
/** Cache Name or Key used to differentiate caches */
|
||||
cacheName: string
|
||||
/** Which data type does this cache have */
|
||||
DataType: any
|
||||
|
||||
constructor (client: Client, cacheName: string, DataType: any) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Client } from "../models/client.ts";
|
||||
import { Collection } from "../utils/collection.ts";
|
||||
import { BaseManager } from "./base.ts";
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class BaseChildManager<T, T2> {
|
||||
client: Client
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Client } from "../models/client.ts";
|
||||
import { Channel } from "../structures/channel.ts";
|
||||
import { ChannelPayload } from "../types/channel.ts";
|
||||
import { CHANNEL } from "../types/endpoint.ts";
|
||||
import getChannelByType from "../utils/getChannelByType.ts";
|
||||
import { BaseManager } from "./base.ts";
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Channel } from '../structures/channel.ts'
|
||||
import { ChannelPayload } from '../types/channel.ts'
|
||||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import getChannelByType from '../utils/getChannelByType.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
|
||||
constructor(client: Client) {
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { EmojiPayload } from '../types/emoji.ts'
|
||||
import { GUILD_EMOJI } from '../types/endpoint.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class EmojisManager extends BaseManager<EmojiPayload, Emoji> {
|
||||
guild: Guild
|
||||
|
||||
constructor (client: Client, guild: Guild) {
|
||||
super(client, `emojis:${guild.id}`, Emoji)
|
||||
this.guild = guild
|
||||
constructor (client: Client) {
|
||||
super(client, `emojis`, Emoji)
|
||||
}
|
||||
|
||||
async fetch (id: string): Promise<Emoji> {
|
||||
async get (key: string): Promise<Emoji | undefined> {
|
||||
const raw = await this._get(key)
|
||||
if (raw === undefined) return
|
||||
const emoji = new this.DataType(this.client, raw)
|
||||
if ((raw as any).guild_id !== undefined) {
|
||||
const guild = await this.client.guilds.get((raw as any).guild_id)
|
||||
if (guild !== undefined) emoji.guild = guild
|
||||
}
|
||||
return emoji
|
||||
}
|
||||
|
||||
async fetch (guildID: string, id: string): Promise<Emoji> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest
|
||||
.get(GUILD_EMOJI(this.guild.id, id))
|
||||
.then(data => {
|
||||
this.set(id, data as EmojiPayload)
|
||||
.get(GUILD_EMOJI(guildID, id))
|
||||
.then(async data => {
|
||||
await this.set(id, data as EmojiPayload)
|
||||
resolve(new Emoji(this.client, data as EmojiPayload))
|
||||
})
|
||||
.catch(e => reject(e))
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
GuildTextChannelPayload,
|
||||
GuildVoiceChannelPayload
|
||||
} from '../types/channel.ts'
|
||||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import { ChannelsManager } from './channels.ts'
|
||||
|
||||
|
@ -34,6 +35,10 @@ export class GuildChannelsManager extends BaseChildManager<
|
|||
if (res !== undefined && res.guild.id === this.guild.id) return res
|
||||
else return undefined
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
return this.client.rest.delete(CHANNEL(id))
|
||||
}
|
||||
|
||||
async array (): Promise<GuildChannel[]> {
|
||||
const arr = (await this.parent.array()) as Channel[]
|
||||
|
|
91
src/managers/guildEmojis.ts
Normal file
91
src/managers/guildEmojis.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { Role } from '../structures/role.ts'
|
||||
import { EmojiPayload } from '../types/emoji.ts'
|
||||
import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import { EmojisManager } from './emojis.ts'
|
||||
import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts'
|
||||
|
||||
export class GuildEmojisManager extends BaseChildManager<
|
||||
EmojiPayload,
|
||||
Emoji
|
||||
> {
|
||||
guild: Guild
|
||||
|
||||
constructor(client: Client, parent: EmojisManager, guild: Guild) {
|
||||
super(client, parent as any)
|
||||
this.guild = guild
|
||||
}
|
||||
|
||||
async get(id: string): Promise<Emoji | undefined> {
|
||||
const res = await this.parent.get(id)
|
||||
if (res !== undefined && res.guild?.id === this.guild.id) return res
|
||||
else return undefined
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
return this.client.rest.delete(CHANNEL(id))
|
||||
}
|
||||
|
||||
async fetch(id: string): Promise<Emoji | undefined> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest
|
||||
.get(GUILD_EMOJI(this.guild.id, id))
|
||||
.then(async data => {
|
||||
const emoji = new Emoji(this.client, data as EmojiPayload)
|
||||
data.guild_id = this.guild.id
|
||||
await this.set(id, data as EmojiPayload)
|
||||
emoji.guild = this.guild
|
||||
resolve(emoji)
|
||||
})
|
||||
.catch(e => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
async create(name: string, url: string, roles?: Role[] | string[] | string): Promise<Emoji | undefined> {
|
||||
let data = url
|
||||
if (!data.startsWith("data:")) {
|
||||
data = await fetchAuto(url)
|
||||
}
|
||||
return await new Promise((resolve, reject) => {
|
||||
let roleIDs: string[] = []
|
||||
if (roles !== undefined && typeof roles === "string") roleIDs = [roles]
|
||||
else if (roles !== undefined) {
|
||||
if (roles?.length === 0) reject(new Error("Empty Roles array was provided"))
|
||||
if (roles[0] instanceof Role) roleIDs = (roles as any).map((r: Role) => r.id)
|
||||
else roleIDs = roles as string[]
|
||||
} else roles = [this.guild.id]
|
||||
this.client.rest
|
||||
.post(GUILD_EMOJIS(this.guild.id), {
|
||||
name,
|
||||
image: data,
|
||||
roles: roleIDs
|
||||
})
|
||||
.then(async data => {
|
||||
const emoji = new Emoji(this.client, data as EmojiPayload)
|
||||
data.guild_id = this.guild.id
|
||||
await this.set(data.id, data as EmojiPayload)
|
||||
emoji.guild = this.guild
|
||||
resolve(emoji)
|
||||
})
|
||||
.catch(e => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
async array(): Promise<Emoji[]> {
|
||||
const arr = (await this.parent.array()) as Emoji[]
|
||||
return arr.filter(
|
||||
(c: any) => c.guild !== undefined && c.guild.id === this.guild.id
|
||||
) as any
|
||||
}
|
||||
|
||||
async flush(): Promise<boolean> {
|
||||
const arr = await this.array()
|
||||
for (const elem of arr) {
|
||||
this.parent.delete(elem.id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import { Client } from "../models/client.ts";
|
||||
import { Guild } from "../structures/guild.ts";
|
||||
import { Member } from "../structures/member.ts";
|
||||
import { GUILD_MEMBER } from "../types/endpoint.ts";
|
||||
import { MemberPayload } from "../types/guild.ts";
|
||||
import { BaseManager } from "./base.ts";
|
||||
import { User } from '../structures/user.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { Member } from '../structures/member.ts'
|
||||
import { GUILD_MEMBER } from '../types/endpoint.ts'
|
||||
import { MemberPayload } from '../types/guild.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class MembersManager extends BaseManager<MemberPayload, Member> {
|
||||
guild: Guild
|
||||
|
@ -13,11 +14,29 @@ export class MembersManager extends BaseManager<MemberPayload, Member> {
|
|||
this.guild = guild
|
||||
}
|
||||
|
||||
async get (key: string): Promise<Member | undefined> {
|
||||
const raw = await this._get(key)
|
||||
if (raw === undefined) return
|
||||
const user = new User(this.client, raw.user)
|
||||
const res = new this.DataType(this.client, raw, user)
|
||||
for (const roleid of res.roleIDs as string[]) {
|
||||
const role = await this.guild.roles.get(roleid)
|
||||
if (role !== undefined) res.roles.push(role)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async fetch(id: string): Promise<Member> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(data => {
|
||||
this.set(id, data as MemberPayload)
|
||||
resolve(new Member(this.client, data as MemberPayload))
|
||||
this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(async data => {
|
||||
await this.set(id, data as MemberPayload)
|
||||
const user: User = new User(this.client, data.user)
|
||||
const res = new Member(this.client, data as MemberPayload, user)
|
||||
for (const roleid of res.roleIDs as string[]) {
|
||||
const role = await this.guild.roles.get(roleid)
|
||||
if (role !== undefined) res.roles.push(role)
|
||||
}
|
||||
resolve(res)
|
||||
}).catch(e => reject(e))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
let caches: any = {}
|
||||
|
||||
const get = (cacheName: string, key: string): any => {
|
||||
const gotCache: Map<string, any> = caches[cacheName]
|
||||
if (gotCache === undefined || !(gotCache instanceof Map)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const gotMap = gotCache.get(key)
|
||||
return gotMap
|
||||
}
|
||||
|
||||
const set = (cacheName: string, key: string, value: any): any => {
|
||||
let gotCache: Map<string, any> = caches[cacheName]
|
||||
if (gotCache === undefined || !(gotCache instanceof Map)) {
|
||||
gotCache = caches[cacheName] = new Map<string, any>()
|
||||
}
|
||||
|
||||
gotCache.set(key, value)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
const del = (cacheName: string, key: string): boolean | undefined => {
|
||||
const gotCache: Map<string, any> = caches[cacheName]
|
||||
if (gotCache === undefined || !(gotCache instanceof Map)) {
|
||||
return
|
||||
}
|
||||
|
||||
return gotCache.delete(key)
|
||||
}
|
||||
|
||||
const deleteCache = (cacheName: string): void => {
|
||||
const gotCache = caches[cacheName]
|
||||
if (gotCache === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete caches[cacheName]
|
||||
}
|
||||
|
||||
const resetCaches = (): void => {
|
||||
caches = {}
|
||||
}
|
||||
|
||||
export default { get, set, del, deleteCache, resetCaches }
|
||||
export { get, set, del, deleteCache, resetCaches }
|
|
@ -13,15 +13,23 @@ import {
|
|||
ClientActivity,
|
||||
ClientPresence
|
||||
} from '../structures/presence.ts'
|
||||
import { EmojisManager } from '../managers/emojis.ts'
|
||||
|
||||
/** Some Client Options to modify behaviour */
|
||||
export interface ClientOptions {
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
cache?: ICacheAdapter
|
||||
forceNewSession?: boolean
|
||||
/** Cache Adapter to use, defaults to Collections one */
|
||||
cache?: ICacheAdapter,
|
||||
/** Force New Session and don't use cached Session (by persistent caching) */
|
||||
forceNewSession?: boolean,
|
||||
/** Startup presence of client */
|
||||
presence?: ClientPresence | ClientActivity | ActivityGame
|
||||
/** Whether it's a bot user or not? Use this if selfbot! */
|
||||
bot?: boolean
|
||||
/** Force all requests to Canary API */
|
||||
canary?: boolean
|
||||
}
|
||||
|
||||
|
@ -29,21 +37,34 @@ export interface ClientOptions {
|
|||
* Discord Client.
|
||||
*/
|
||||
export class Client extends EventEmitter {
|
||||
/** Gateway object */
|
||||
gateway?: Gateway
|
||||
/** REST Manager - used to make all requests */
|
||||
rest: RESTManager = new RESTManager(this)
|
||||
/** User which Client logs in to, undefined until logs in */
|
||||
user?: User
|
||||
/** WebSocket ping of Client */
|
||||
ping = 0
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Cache Adapter */
|
||||
cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Whether to force new session or not */
|
||||
forceNewSession?: boolean
|
||||
|
||||
users: UserManager = new UserManager(this)
|
||||
guilds: GuildManager = new GuildManager(this)
|
||||
channels: ChannelsManager = new ChannelsManager(this)
|
||||
messages: MessagesManager = new MessagesManager(this)
|
||||
emojis: EmojisManager = new EmojisManager(this)
|
||||
|
||||
/** Whether this client will login as bot user or not */
|
||||
bot: boolean = true
|
||||
/** Whether the REST Manager will use Canary API or not */
|
||||
canary: boolean = false
|
||||
|
||||
/** Client's presence. Startup one if set before connecting */
|
||||
presence: ClientPresence = new ClientPresence()
|
||||
|
||||
constructor (options: ClientOptions = {}) {
|
||||
|
@ -61,11 +82,13 @@ export class Client extends EventEmitter {
|
|||
if (options.canary === true) this.canary = true
|
||||
}
|
||||
|
||||
/** Set Cache Adapter */
|
||||
setAdapter (adapter: ICacheAdapter): Client {
|
||||
this.cache = adapter
|
||||
return this
|
||||
}
|
||||
|
||||
/** Change Presence of Client */
|
||||
setPresence (presence: ClientPresence | ClientActivity | ActivityGame): void {
|
||||
if (presence instanceof ClientPresence) {
|
||||
this.presence = presence
|
||||
|
@ -73,6 +96,7 @@ export class Client extends EventEmitter {
|
|||
this.gateway?.sendPresence(this.presence.create())
|
||||
}
|
||||
|
||||
/** Emit debug event */
|
||||
debug (tag: string, msg: string): void {
|
||||
this.emit('debug', `[${tag}] ${msg}`)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ import { delay } from '../utils/index.ts'
|
|||
import * as baseEndpoints from '../consts/urlsAndVersions.ts'
|
||||
import { Client } from './client.ts'
|
||||
import { getBuildInfo } from '../utils/buildInfo.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
|
||||
export type RequestMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete'
|
||||
|
||||
export enum HttpResponseCode {
|
||||
Ok = 200,
|
||||
|
@ -17,122 +20,102 @@ export enum HttpResponseCode {
|
|||
GatewayUnavailable = 502
|
||||
}
|
||||
|
||||
export type RequestMethods =
|
||||
| 'get'
|
||||
| 'post'
|
||||
| 'put'
|
||||
| 'patch'
|
||||
| 'head'
|
||||
| 'delete'
|
||||
export interface RequestHeaders {
|
||||
[name: string]: string
|
||||
}
|
||||
|
||||
export interface QueuedRequest {
|
||||
callback: () => Promise<
|
||||
| {
|
||||
rateLimited: any
|
||||
beforeFetch: boolean
|
||||
bucketID?: string | null
|
||||
}
|
||||
| undefined
|
||||
>
|
||||
bucketID?: string | null
|
||||
export interface QueuedItem {
|
||||
onComplete: () => Promise<{
|
||||
rateLimited: any
|
||||
bucket?: string | null
|
||||
before: boolean
|
||||
} | undefined>
|
||||
bucket?: string | null
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface RateLimitedPath {
|
||||
export interface RateLimit {
|
||||
url: string
|
||||
resetTimestamp: number
|
||||
bucketID: string | null
|
||||
resetAt: number
|
||||
bucket: string | null
|
||||
}
|
||||
|
||||
export class RESTManager {
|
||||
client: Client
|
||||
globallyRateLimited: boolean = false
|
||||
queueInProcess: boolean = false
|
||||
pathQueues: { [key: string]: QueuedRequest[] } = {}
|
||||
ratelimitedPaths = new Map<string, RateLimitedPath>()
|
||||
queues: { [key: string]: QueuedItem[] } = {}
|
||||
rateLimits = new Collection<string, RateLimit>()
|
||||
globalRateLimit: boolean = false
|
||||
processing: boolean = false
|
||||
|
||||
constructor (client: Client) {
|
||||
constructor(client: Client) {
|
||||
this.client = client
|
||||
setTimeout(() => this.processRateLimitedPaths, 1000)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.handleRateLimits()
|
||||
}
|
||||
|
||||
async processRateLimitedPaths (): Promise<void> {
|
||||
const now = Date.now()
|
||||
this.ratelimitedPaths.forEach((value, key) => {
|
||||
if (value.resetTimestamp > now) return
|
||||
this.ratelimitedPaths.delete(key)
|
||||
if (key === 'global') this.globallyRateLimited = false
|
||||
async checkQueues(): Promise<void> {
|
||||
Object.entries(this.queues).forEach(([key, value]) => {
|
||||
if (value.length === 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete this.queues[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addToQueue (request: QueuedRequest): void {
|
||||
queue(request: QueuedItem): void {
|
||||
const route = request.url.substring(
|
||||
// eslint seriously?
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
baseEndpoints.DISCORD_API_URL.length + 1
|
||||
)
|
||||
const parts = route.split('/')
|
||||
// Remove the major param
|
||||
parts.shift()
|
||||
const [id] = parts
|
||||
|
||||
if (this.pathQueues[id] !== undefined) {
|
||||
this.pathQueues[id].push(request)
|
||||
if (this.queues[id] !== undefined) {
|
||||
this.queues[id].push(request)
|
||||
} else {
|
||||
this.pathQueues[id] = [request]
|
||||
this.queues[id] = [request]
|
||||
}
|
||||
}
|
||||
|
||||
async cleanupQueues (): Promise<void> {
|
||||
Object.entries(this.pathQueues).forEach(([key, value]) => {
|
||||
if (value.length === 0) {
|
||||
// Remove it entirely
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete this.pathQueues[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async processQueue (): Promise<void> {
|
||||
if (
|
||||
Object.keys(this.pathQueues).length !== 0 &&
|
||||
!this.globallyRateLimited
|
||||
) {
|
||||
async processQueue(): Promise<void> {
|
||||
if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) {
|
||||
await Promise.allSettled(
|
||||
Object.values(this.pathQueues).map(async pathQueue => {
|
||||
Object.values(this.queues).map(async pathQueue => {
|
||||
const request = pathQueue.shift()
|
||||
if (request === undefined) return
|
||||
|
||||
const rateLimitedURLResetIn = await this.checkRatelimits(request.url)
|
||||
const rateLimitedURLResetIn = await this.isRateLimited(request.url)
|
||||
|
||||
if (typeof request.bucketID === 'string') {
|
||||
const rateLimitResetIn = await this.checkRatelimits(
|
||||
request.bucketID
|
||||
if (typeof request.bucket === 'string') {
|
||||
const rateLimitResetIn = await this.isRateLimited(
|
||||
request.bucket
|
||||
)
|
||||
if (rateLimitResetIn !== false) {
|
||||
// This request is still rate limited read to queue
|
||||
this.addToQueue(request)
|
||||
this.queue(request)
|
||||
} else {
|
||||
// This request is not rate limited so it should be run
|
||||
const result = await request.callback()
|
||||
const result = await request.onComplete()
|
||||
if (result?.rateLimited !== undefined) {
|
||||
this.addToQueue({
|
||||
this.queue({
|
||||
...request,
|
||||
bucketID: result.bucketID ?? request.bucketID
|
||||
bucket: result.bucket ?? request.bucket
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (rateLimitedURLResetIn !== false) {
|
||||
// This URL is rate limited readd to queue
|
||||
this.addToQueue(request)
|
||||
this.queue(request)
|
||||
} else {
|
||||
// This request has no bucket id so it should be processed
|
||||
const result = await request.callback()
|
||||
const result = await request.onComplete()
|
||||
if (result?.rateLimited !== undefined) {
|
||||
this.addToQueue({
|
||||
this.queue({
|
||||
...request,
|
||||
bucketID: result.bucketID ?? request.bucketID
|
||||
bucket: result.bucket ?? request.bucket
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -141,27 +124,28 @@ export class RESTManager {
|
|||
)
|
||||
}
|
||||
|
||||
if (Object.keys(this.pathQueues).length !== 0) {
|
||||
if (Object.keys(this.queues).length !== 0) {
|
||||
await delay(1000)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.processQueue()
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.cleanupQueues()
|
||||
} else this.queueInProcess = false
|
||||
this.checkQueues()
|
||||
} else this.processing = false
|
||||
}
|
||||
|
||||
createRequestBody (
|
||||
prepare(
|
||||
body: any,
|
||||
method: RequestMethods
|
||||
): { [key: string]: any } {
|
||||
const headers: { [key: string]: string } = {
|
||||
Authorization: `Bot ${this.client.token}`,
|
||||
'User-Agent': `DiscordBot (harmony)`
|
||||
|
||||
const headers: RequestHeaders = {
|
||||
'Authorization': `Bot ${this.client.token}`,
|
||||
'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)`
|
||||
}
|
||||
|
||||
if (this.client.token === undefined) delete headers.Authorization
|
||||
|
||||
if (method === 'get') body = undefined
|
||||
if (method === 'get' || method === 'head' || method === 'delete') body = undefined
|
||||
|
||||
if (body?.reason !== undefined) {
|
||||
headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason)
|
||||
|
@ -203,53 +187,125 @@ export class RESTManager {
|
|||
return data
|
||||
}
|
||||
|
||||
async checkRatelimits (url: string): Promise<number | false> {
|
||||
const ratelimited = this.ratelimitedPaths.get(url)
|
||||
const global = this.ratelimitedPaths.get('global')
|
||||
async isRateLimited(url: string): Promise<number | false> {
|
||||
const global = this.rateLimits.get('global')
|
||||
const rateLimited = this.rateLimits.get(url)
|
||||
const now = Date.now()
|
||||
|
||||
if (ratelimited !== undefined && now < ratelimited.resetTimestamp) {
|
||||
return ratelimited.resetTimestamp - now
|
||||
if (rateLimited !== undefined && now < rateLimited.resetAt) {
|
||||
return rateLimited.resetAt - now
|
||||
}
|
||||
if (global !== undefined && now < global.resetTimestamp) {
|
||||
return global.resetTimestamp - now
|
||||
if (global !== undefined && now < global.resetAt) {
|
||||
return global.resetAt - now
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async runMethod (
|
||||
processHeaders(url: string, headers: Headers): string | null | undefined {
|
||||
let rateLimited = false
|
||||
|
||||
const global = headers.get('x-ratelimit-global')
|
||||
const bucket = headers.get('x-ratelimit-bucket')
|
||||
const remaining = headers.get('x-ratelimit-remaining')
|
||||
const resetAt = headers.get('x-ratelimit-reset')
|
||||
const retryAfter = headers.get('retry-after')
|
||||
|
||||
if (remaining !== null && remaining === '0') {
|
||||
rateLimited = true
|
||||
|
||||
this.rateLimits.set(url, {
|
||||
url,
|
||||
resetAt: Number(resetAt) * 1000,
|
||||
bucket
|
||||
})
|
||||
|
||||
if (bucket !== null) {
|
||||
this.rateLimits.set(bucket, {
|
||||
url,
|
||||
resetAt: Number(resetAt) * 1000,
|
||||
bucket
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (global !== null) {
|
||||
const reset = Date.now() + Number(retryAfter)
|
||||
this.globalRateLimit = true
|
||||
rateLimited = true
|
||||
|
||||
this.rateLimits.set('global', {
|
||||
url: 'global',
|
||||
resetAt: reset,
|
||||
bucket
|
||||
})
|
||||
|
||||
if (bucket !== null) {
|
||||
this.rateLimits.set(bucket, {
|
||||
url: 'global',
|
||||
resetAt: reset,
|
||||
bucket
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return rateLimited ? bucket : undefined
|
||||
}
|
||||
|
||||
async handleStatusCode(
|
||||
response: Response
|
||||
): Promise<undefined> {
|
||||
const status = response.status
|
||||
|
||||
if ((status >= 200 && status < 400) || status === HttpResponseCode.TooManyRequests) return
|
||||
|
||||
const body = await response.json()
|
||||
const text = Deno.inspect(body.errors)
|
||||
|
||||
if (status === HttpResponseCode.Unauthorized)
|
||||
throw new Error(`Request was not successful (Unauthorized). Invalid Token.\n${text}`)
|
||||
|
||||
if ([
|
||||
HttpResponseCode.BadRequest,
|
||||
HttpResponseCode.NotFound,
|
||||
HttpResponseCode.Forbidden,
|
||||
HttpResponseCode.MethodNotAllowed
|
||||
].includes(status)) {
|
||||
throw new Error(`Request - Client Error. Code: ${status}\n${text}`)
|
||||
} else if (status === HttpResponseCode.GatewayUnavailable) {
|
||||
throw new Error(`Request - Server Error. Code: ${status}\n${text}`)
|
||||
} else throw new Error('Request - Unknown Error')
|
||||
}
|
||||
|
||||
async make(
|
||||
method: RequestMethods,
|
||||
url: string,
|
||||
body?: unknown,
|
||||
retryCount = 0,
|
||||
bucketID?: string | null
|
||||
maxRetries = 0,
|
||||
bucket?: string | null
|
||||
): Promise<any> {
|
||||
const errorStack = new Error('Location In Your Files:')
|
||||
Error.captureStackTrace(errorStack)
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
const callback = async (): Promise<undefined | any> => {
|
||||
const onComplete = async (): Promise<undefined | any> => {
|
||||
try {
|
||||
const rateLimitResetIn = await this.checkRatelimits(url)
|
||||
const rateLimitResetIn = await this.isRateLimited(url)
|
||||
if (rateLimitResetIn !== false) {
|
||||
return {
|
||||
rateLimited: rateLimitResetIn,
|
||||
beforeFetch: true,
|
||||
bucketID
|
||||
before: true,
|
||||
bucket
|
||||
}
|
||||
}
|
||||
|
||||
const query =
|
||||
method === 'get' && body !== undefined
|
||||
? Object.entries(body as any)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(
|
||||
value as any
|
||||
)}`
|
||||
)
|
||||
.join('&')
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(
|
||||
value as any
|
||||
)}`
|
||||
)
|
||||
.join('&')
|
||||
: ''
|
||||
let urlToUse =
|
||||
method === 'get' && query !== '' ? `${url}?${query}` : url
|
||||
|
@ -259,13 +315,13 @@ export class RESTManager {
|
|||
urlToUse = split[0] + '//canary.' + split[1]
|
||||
}
|
||||
|
||||
const requestData = this.createRequestBody(body, method)
|
||||
const requestData = this.prepare(body, method)
|
||||
|
||||
const response = await fetch(urlToUse, requestData)
|
||||
const bucketIDFromHeaders = this.processHeaders(url, response.headers)
|
||||
this.handleStatusCode(response, errorStack)
|
||||
const bucketFromHeaders = this.processHeaders(url, response.headers)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.handleStatusCode(response)
|
||||
|
||||
// Sometimes Discord returns an empty 204 response that can't be made to JSON.
|
||||
if (response.status === 204) return resolve(undefined)
|
||||
|
||||
const json = await response.json()
|
||||
|
@ -273,14 +329,14 @@ export class RESTManager {
|
|||
json.retry_after !== undefined ||
|
||||
json.message === 'You are being rate limited.'
|
||||
) {
|
||||
if (retryCount > 10) {
|
||||
if (maxRetries > 10) {
|
||||
throw new Error('Max RateLimit Retries hit')
|
||||
}
|
||||
|
||||
return {
|
||||
rateLimited: json.retry_after,
|
||||
beforeFetch: false,
|
||||
bucketID: bucketIDFromHeaders
|
||||
before: false,
|
||||
bucket: bucketFromHeaders
|
||||
}
|
||||
}
|
||||
return resolve(json)
|
||||
|
@ -289,132 +345,45 @@ export class RESTManager {
|
|||
}
|
||||
}
|
||||
|
||||
this.addToQueue({
|
||||
callback,
|
||||
bucketID,
|
||||
this.queue({
|
||||
onComplete,
|
||||
bucket,
|
||||
url
|
||||
})
|
||||
if (!this.queueInProcess) {
|
||||
this.queueInProcess = true
|
||||
if (!this.processing) {
|
||||
this.processing = true
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.processQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async logErrors (response: Response, errorStack?: unknown): Promise<void> {
|
||||
try {
|
||||
const error = await response.json()
|
||||
console.error(error)
|
||||
} catch {
|
||||
console.error(response)
|
||||
}
|
||||
async handleRateLimits(): Promise<void> {
|
||||
const now = Date.now()
|
||||
this.rateLimits.forEach((value, key) => {
|
||||
if (value.resetAt > now) return
|
||||
this.rateLimits.delete(key)
|
||||
if (key === 'global') this.globalRateLimit = false
|
||||
})
|
||||
}
|
||||
|
||||
handleStatusCode (
|
||||
response: Response,
|
||||
errorStack?: unknown
|
||||
): undefined | boolean {
|
||||
const status = response.status
|
||||
|
||||
if (
|
||||
(status >= 200 && status < 400) ||
|
||||
status === HttpResponseCode.TooManyRequests
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.logErrors(response, errorStack)
|
||||
|
||||
if (status === HttpResponseCode.Unauthorized)
|
||||
throw new Error('Request was not successful. Invalid Token.')
|
||||
|
||||
switch (status) {
|
||||
case HttpResponseCode.BadRequest:
|
||||
case HttpResponseCode.Unauthorized:
|
||||
case HttpResponseCode.Forbidden:
|
||||
case HttpResponseCode.NotFound:
|
||||
case HttpResponseCode.MethodNotAllowed:
|
||||
throw new Error('Request Client Error.')
|
||||
case HttpResponseCode.GatewayUnavailable:
|
||||
throw new Error('Request Server Error.')
|
||||
}
|
||||
|
||||
// left are all unknown
|
||||
throw new Error('Request Unknown Error')
|
||||
async get(url: string, body?: unknown): Promise<any> {
|
||||
return await this.make('get', url, body)
|
||||
}
|
||||
|
||||
processHeaders (url: string, headers: Headers): string | null | undefined {
|
||||
let ratelimited = false
|
||||
|
||||
// Get all useful headers
|
||||
const remaining = headers.get('x-ratelimit-remaining')
|
||||
const resetTimestamp = headers.get('x-ratelimit-reset')
|
||||
const retryAfter = headers.get('retry-after')
|
||||
const global = headers.get('x-ratelimit-global')
|
||||
const bucketID = headers.get('x-ratelimit-bucket')
|
||||
|
||||
// If there is no remaining rate limit for this endpoint, we save it in cache
|
||||
if (remaining !== null && remaining === '0') {
|
||||
ratelimited = true
|
||||
|
||||
this.ratelimitedPaths.set(url, {
|
||||
url,
|
||||
resetTimestamp: Number(resetTimestamp) * 1000,
|
||||
bucketID
|
||||
})
|
||||
|
||||
if (bucketID !== null) {
|
||||
this.ratelimitedPaths.set(bucketID, {
|
||||
url,
|
||||
resetTimestamp: Number(resetTimestamp) * 1000,
|
||||
bucketID
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no remaining global limit, we save it in cache
|
||||
if (global !== null) {
|
||||
const reset = Date.now() + Number(retryAfter)
|
||||
this.globallyRateLimited = true
|
||||
ratelimited = true
|
||||
|
||||
this.ratelimitedPaths.set('global', {
|
||||
url: 'global',
|
||||
resetTimestamp: reset,
|
||||
bucketID
|
||||
})
|
||||
|
||||
if (bucketID !== null) {
|
||||
this.ratelimitedPaths.set(bucketID, {
|
||||
url: 'global',
|
||||
resetTimestamp: reset,
|
||||
bucketID
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ratelimited ? bucketID : undefined
|
||||
async post(url: string, body?: unknown): Promise<any> {
|
||||
return await this.make('post', url, body)
|
||||
}
|
||||
|
||||
async get (url: string, body?: unknown): Promise<any> {
|
||||
return await this.runMethod('get', url, body)
|
||||
async delete(url: string, body?: unknown): Promise<any> {
|
||||
return await this.make('delete', url, body)
|
||||
}
|
||||
|
||||
async post (url: string, body?: unknown): Promise<any> {
|
||||
return await this.runMethod('post', url, body)
|
||||
async patch(url: string, body?: unknown): Promise<any> {
|
||||
return await this.make('patch', url, body)
|
||||
}
|
||||
|
||||
async delete (url: string, body?: unknown): Promise<any> {
|
||||
return await this.runMethod('delete', url, body)
|
||||
}
|
||||
|
||||
async patch (url: string, body?: unknown): Promise<any> {
|
||||
return await this.runMethod('patch', url, body)
|
||||
}
|
||||
|
||||
async put (url: string, body?: unknown): Promise<any> {
|
||||
return await this.runMethod('put', url, body)
|
||||
async put(url: string, body?: unknown): Promise<any> {
|
||||
return await this.make('put', url, body)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import * as cache from '../models/cache.ts'
|
||||
|
||||
interface IInit {
|
||||
useCache?: boolean
|
||||
|
@ -25,18 +24,13 @@ export class Base {
|
|||
this.useCache = useCache
|
||||
const cacheID = restURLfuncArgs.join(':')
|
||||
if (this.useCache !== undefined) {
|
||||
const cached = cache.get(this.cacheName ?? this.name, cacheID)
|
||||
const cached = await client.cache.get(this.cacheName ?? this.name, cacheID)
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
|
||||
const resp = await fetch(endpoint(...restURLfuncArgs), {
|
||||
headers: {
|
||||
Authorization: `Bot ${client.token}`
|
||||
}
|
||||
})
|
||||
const jsonParsed = await resp.json()
|
||||
const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs))
|
||||
|
||||
return new this(client, jsonParsed)
|
||||
}
|
||||
|
@ -47,12 +41,7 @@ export class Base {
|
|||
): Promise<this> {
|
||||
const oldOne = Object.assign(Object.create(this), this)
|
||||
|
||||
const resp = await fetch(endpoint(...restURLfuncArgs), {
|
||||
headers: {
|
||||
Authorization: `Bot ${client.token}`
|
||||
}
|
||||
})
|
||||
const jsonParsed = await resp.json()
|
||||
const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs))
|
||||
|
||||
this.readFromData(jsonParsed)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Client } from '../models/client.ts'
|
|||
import { EmojiPayload } from '../types/emoji.ts'
|
||||
import { USER } from '../types/endpoint.ts'
|
||||
import { Base } from './base.ts'
|
||||
import { Guild } from './guild.ts'
|
||||
import { User } from './user.ts'
|
||||
|
||||
export class Emoji extends Base {
|
||||
|
@ -9,6 +10,7 @@ export class Emoji extends Base {
|
|||
name: string
|
||||
roles?: string[]
|
||||
user?: User
|
||||
guild?: Guild
|
||||
requireColons?: boolean
|
||||
managed?: boolean
|
||||
animated?: boolean
|
||||
|
@ -20,17 +22,16 @@ export class Emoji extends Base {
|
|||
} else return `<a:${this.name}:${this.id}>`
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.getEmojiString
|
||||
}
|
||||
|
||||
constructor (client: Client, data: EmojiPayload) {
|
||||
super(client, data)
|
||||
this.id = data.id
|
||||
this.name = data.name
|
||||
if (data.user !== undefined) this.user = new User(this.client, data.user)
|
||||
this.roles = data.roles
|
||||
if (data.user !== undefined) {
|
||||
User.autoInit(this.client, {
|
||||
endpoint: USER,
|
||||
restURLfuncArgs: [data.user.id]
|
||||
}).then(user => (this.user = user))
|
||||
}
|
||||
this.requireColons = data.require_colons
|
||||
this.managed = data.managed
|
||||
this.animated = data.animated
|
||||
|
|
|
@ -6,7 +6,8 @@ import { VoiceState } from './voiceState.ts'
|
|||
import { RolesManager } from '../managers/roles.ts'
|
||||
import { GuildChannelsManager } from '../managers/guildChannels.ts'
|
||||
import { MembersManager } from '../managers/members.ts'
|
||||
import { EmojisManager } from '../managers/emojis.ts'
|
||||
import { Role } from './role.ts'
|
||||
import { GuildEmojisManager } from '../managers/guildEmojis.ts'
|
||||
|
||||
export class Guild extends Base {
|
||||
id: string
|
||||
|
@ -27,7 +28,7 @@ export class Guild extends Base {
|
|||
defaultMessageNotifications?: string
|
||||
explicitContentFilter?: string
|
||||
roles: RolesManager
|
||||
emojis: EmojisManager
|
||||
emojis: GuildEmojisManager
|
||||
features?: GuildFeatures[]
|
||||
mfaLevel?: string
|
||||
applicationID?: string
|
||||
|
@ -61,12 +62,12 @@ export class Guild extends Base {
|
|||
this.unavailable = data.unavailable
|
||||
this.members = new MembersManager(this.client, this)
|
||||
this.channels = new GuildChannelsManager(
|
||||
this.client,
|
||||
this.client.channels,
|
||||
this.client,
|
||||
this.client.channels,
|
||||
this
|
||||
)
|
||||
this.roles = new RolesManager(this.client, this)
|
||||
this.emojis = new EmojisManager(this.client, this)
|
||||
this.emojis = new GuildEmojisManager(this.client, this.client.emojis, this)
|
||||
|
||||
if (!this.unavailable) {
|
||||
this.name = data.name
|
||||
|
@ -160,6 +161,10 @@ export class Guild extends Base {
|
|||
// data.roles.map(
|
||||
// v => cache.get('role', v.id) ?? new Role(this.client, v)
|
||||
// ) ?? this.roles
|
||||
// this.emojis =
|
||||
// data.emojis.map(
|
||||
// v => cache.get('emoji', v.id) ?? new Emoji(this.client, v)
|
||||
// ) ?? this.emojis
|
||||
this.features = data.features ?? this.features
|
||||
this.mfaLevel = data.mfa_level ?? this.mfaLevel
|
||||
this.systemChannelID = data.system_channel_id ?? this.systemChannelID
|
||||
|
@ -205,4 +210,8 @@ export class Guild extends Base {
|
|||
data.approximate_presence_count ?? this.approximatePresenceCount
|
||||
}
|
||||
}
|
||||
|
||||
async getEveryoneRole (): Promise<Role> {
|
||||
return (await this.roles.array().then(arr => arr?.sort((b, a) => a.position - b.position)[0]) as any) as Role
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
GuildChannelCategoryPayload,
|
||||
Overwrite
|
||||
} from '../types/channel.ts'
|
||||
import { Guild } from "./guild.ts"
|
||||
import { Guild } from './guild.ts'
|
||||
|
||||
export class CategoryChannel extends Channel {
|
||||
guildID: string
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { GuildNewsChannelPayload, Overwrite } from '../types/channel.ts'
|
||||
import { Guild } from "./guild.ts"
|
||||
import { Guild } from './guild.ts'
|
||||
import { TextChannel } from './textChannel.ts'
|
||||
|
||||
export class NewsChannel extends TextChannel {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { GuildTextChannelPayload, Overwrite } from '../types/channel.ts'
|
||||
import { TextChannel } from './textChannel.ts'
|
||||
import { Guild } from "./guild.ts"
|
||||
import { Guild } from './guild.ts'
|
||||
|
||||
export class GuildTextChannel extends TextChannel {
|
||||
guildID: string
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts'
|
||||
import { Channel } from './channel.ts'
|
||||
import { Guild } from "./guild.ts"
|
||||
import { Guild } from './guild.ts'
|
||||
|
||||
export class VoiceChannel extends Channel {
|
||||
bitrate: string
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
import cache from '../models/cache.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import { MemberPayload } from '../types/guild.ts'
|
||||
import { Base } from './base.ts'
|
||||
import { Role } from './role.ts'
|
||||
import { User } from './user.ts'
|
||||
|
||||
export class Member extends Base {
|
||||
id: string
|
||||
user: User
|
||||
nick?: string
|
||||
roles: string[]
|
||||
roleIDs: string[]
|
||||
roles: Role[] = []
|
||||
joinedAt: string
|
||||
premiumSince?: string
|
||||
deaf: boolean
|
||||
mute: boolean
|
||||
|
||||
constructor (client: Client, data: MemberPayload) {
|
||||
constructor (client: Client, data: MemberPayload, user: User) {
|
||||
super(client)
|
||||
this.id = data.user.id
|
||||
this.user =
|
||||
cache.get('user', data.user.id) ?? new User(this.client, data.user)
|
||||
this.user = user
|
||||
// this.user =
|
||||
// cache.get('user', data.user.id) ?? new User(this.client, data.user)
|
||||
this.nick = data.nick
|
||||
this.roles = data.roles
|
||||
this.roleIDs = data.roles
|
||||
this.joinedAt = data.joined_at
|
||||
this.premiumSince = data.premium_since
|
||||
this.deaf = data.deaf
|
||||
|
@ -32,7 +34,7 @@ export class Member extends Base {
|
|||
protected readFromData (data: MemberPayload): void {
|
||||
super.readFromData(data.user)
|
||||
this.nick = data.nick ?? this.nick
|
||||
this.roles = data.roles ?? this.roles
|
||||
this.roleIDs = data.roles ?? this.roles.map(r => r.id)
|
||||
this.joinedAt = data.joined_at ?? this.joinedAt
|
||||
this.premiumSince = data.premium_since ?? this.premiumSince
|
||||
this.deaf = data.deaf ?? this.deaf
|
||||
|
|
|
@ -14,10 +14,10 @@ import { User } from './user.ts'
|
|||
import { Member } from './member.ts'
|
||||
import { Embed } from './embed.ts'
|
||||
import { CHANNEL_MESSAGE } from '../types/endpoint.ts'
|
||||
import { MessageMentions } from "./messageMentions.ts"
|
||||
import { TextChannel } from "./textChannel.ts"
|
||||
import { DMChannel } from "./dmChannel.ts"
|
||||
import { Guild } from "./guild.ts"
|
||||
import { MessageMentions } from './messageMentions.ts'
|
||||
import { TextChannel } from './textChannel.ts'
|
||||
import { DMChannel } from './dmChannel.ts'
|
||||
import { Guild } from './guild.ts'
|
||||
|
||||
export class Message extends Base {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-readonly
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Client } from '../models/client.ts'
|
|||
import { MessageOption, TextChannelPayload } from '../types/channel.ts'
|
||||
import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts'
|
||||
import { Channel } from './channel.ts'
|
||||
import { Embed } from "./embed.ts"
|
||||
import { Embed } from './embed.ts'
|
||||
import { Message } from './message.ts'
|
||||
import { MessageMentions } from './messageMentions.ts'
|
||||
|
||||
|
@ -27,14 +27,14 @@ export class TextChannel extends Channel {
|
|||
}
|
||||
|
||||
async send (text?: string | AllMessageOptions, option?: AllMessageOptions): Promise<Message> {
|
||||
if(typeof text === "object") {
|
||||
if (typeof text === "object") {
|
||||
option = text
|
||||
text = undefined
|
||||
}
|
||||
if (text === undefined && option === undefined) {
|
||||
throw new Error('Either text or option is necessary.')
|
||||
}
|
||||
if(option instanceof Embed) option = {
|
||||
if (option instanceof Embed) option = {
|
||||
embed: option
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { CommandClient, Intents } from '../../mod.ts';
|
||||
import PingCommand from "./cmds/ping.ts";
|
||||
import { CommandClient, Intents } from '../../mod.ts'
|
||||
import PingCommand from './cmds/ping.ts'
|
||||
import AddEmojiCommand from './cmds/addemoji.ts'
|
||||
import UserinfoCommand from './cmds/userinfo.ts'
|
||||
import { TOKEN } from './config.ts'
|
||||
|
||||
const client = new CommandClient({
|
||||
|
@ -13,6 +15,10 @@ client.on('ready', () => {
|
|||
console.log(`[Login] Logged in as ${client.user?.tag}!`)
|
||||
})
|
||||
|
||||
client.on("commandError", console.error)
|
||||
|
||||
client.commands.add(PingCommand)
|
||||
client.commands.add(UserinfoCommand)
|
||||
client.commands.add(AddEmojiCommand)
|
||||
|
||||
client.connect(TOKEN, Intents.All)
|
22
src/test/cmds/addemoji.ts
Normal file
22
src/test/cmds/addemoji.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Command } from '../../../mod.ts'
|
||||
import { CommandContext } from '../../models/command.ts'
|
||||
|
||||
export default class AddEmojiCommand extends Command {
|
||||
name = 'addemoji'
|
||||
aliases = [ 'ae', 'emojiadd' ]
|
||||
args = 2
|
||||
guildOnly = true
|
||||
|
||||
execute(ctx: CommandContext): any {
|
||||
const name = ctx.args[0]
|
||||
if (name === undefined) return ctx.message.reply('No name was given!')
|
||||
const url = ctx.argString.slice(name.length).trim()
|
||||
if (url === '') return ctx.message.reply('No URL was given!')
|
||||
ctx.message.guild?.emojis.create(name, url).then(emoji => {
|
||||
if (emoji === undefined) throw new Error('Unknown')
|
||||
ctx.message.reply(`Successfuly added emoji ${emoji.toString()} ${emoji.name}!`)
|
||||
}).catch(e => {
|
||||
ctx.message.reply(`Failed to add emoji. Reason: ${e.message}`)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { Command } from "../../../mod.ts";
|
||||
import { CommandContext } from "../../models/command.ts";
|
||||
import { Command } from '../../../mod.ts'
|
||||
import { CommandContext } from '../../models/command.ts'
|
||||
|
||||
export default class PingCommand extends Command {
|
||||
name = "ping"
|
||||
dmOnly = true
|
||||
|
||||
execute(ctx: CommandContext): void {
|
||||
ctx.message.reply(`pong! Latency: ${ctx.client.ping}ms`)
|
||||
ctx.message.reply(`Pong! Latency: ${ctx.client.ping}ms`)
|
||||
}
|
||||
}
|
16
src/test/cmds/userinfo.ts
Normal file
16
src/test/cmds/userinfo.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Command, Member, CommandContext, Embed } from '../../../mod.ts'
|
||||
|
||||
export default class UserinfoCommand extends Command {
|
||||
name = "userinfo"
|
||||
guildOnly = true
|
||||
|
||||
execute(ctx: CommandContext): void {
|
||||
const member: Member = ctx.message.member as any
|
||||
const embed = new Embed()
|
||||
.setTitle(`User Info`)
|
||||
.setAuthor({ name: member.user.tag })
|
||||
.addField("ID", member.id)
|
||||
.addField("Roles", member.roles.map(r => r.name).join(", "))
|
||||
ctx.channel.send(embed)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Client, Intents, GuildTextChannel, Message, ClientPresence, Member, Role, GuildChannel, Embed, Guild } from '../../mod.ts';
|
||||
import { Client, Intents, GuildTextChannel, Message, ClientPresence, Member, Role, GuildChannel, Embed, Guild } from '../../mod.ts'
|
||||
import { TOKEN } from './config.ts'
|
||||
|
||||
const client = new Client({
|
||||
|
|
|
@ -14,6 +14,8 @@ const GUILD_WIDGET = (guildID: string): string =>
|
|||
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/widget`
|
||||
const GUILD_EMOJI = (guildID: string, emojiID: string): string =>
|
||||
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}`
|
||||
const GUILD_EMOJIS = (guildID: string): string =>
|
||||
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis`
|
||||
const GUILD_ROLE = (guildID: string, roleID: string): string =>
|
||||
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/roles/${roleID}`
|
||||
const GUILD_ROLES = (guildID: string): string =>
|
||||
|
@ -172,8 +174,6 @@ const TEAM_ICON = (teamID: string, iconID: string): string =>
|
|||
// Emoji Endpoints
|
||||
const EMOJI = (guildID: string, emojiID: string): string =>
|
||||
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}`
|
||||
const EMOJIS = (guildID: string): string =>
|
||||
`${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis`
|
||||
|
||||
// Template Endpoint
|
||||
const TEMPLATE = (templateCODE: string): string =>
|
||||
|
@ -259,7 +259,7 @@ export default [
|
|||
ACHIEVEMENT_ICON,
|
||||
TEAM_ICON,
|
||||
EMOJI,
|
||||
EMOJIS,
|
||||
GUILD_EMOJIS,
|
||||
TEMPLATE,
|
||||
INVITE,
|
||||
VOICE_REGIONS
|
||||
|
@ -305,6 +305,7 @@ export {
|
|||
CHANNEL_PIN,
|
||||
CHANNEL_PINS,
|
||||
CHANNEL_PERMISSION,
|
||||
GUILD_EMOJIS,
|
||||
CHANNEL_TYPING,
|
||||
GROUP_RECIPIENT,
|
||||
CURRENT_USER,
|
||||
|
@ -333,7 +334,6 @@ export {
|
|||
ACHIEVEMENT_ICON,
|
||||
TEAM_ICON,
|
||||
EMOJI,
|
||||
EMOJIS,
|
||||
TEMPLATE,
|
||||
INVITE,
|
||||
VOICE_REGIONS
|
||||
|
|
|
@ -14,9 +14,9 @@ import { GroupDMChannel } from '../structures/groupChannel.ts'
|
|||
import { CategoryChannel } from '../structures/guildCategoryChannel.ts'
|
||||
import { NewsChannel } from '../structures/guildNewsChannel.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import { Guild } from "../structures/guild.ts"
|
||||
import { GuildTextChannel } from "../structures/guildTextChannel.ts"
|
||||
import { TextChannel } from "../structures/textChannel.ts"
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { GuildTextChannel } from '../structures/guildTextChannel.ts'
|
||||
import { TextChannel } from '../structures/textChannel.ts'
|
||||
|
||||
const getChannelByType = (
|
||||
client: Client,
|
||||
|
|
Loading…
Reference in a new issue