new things

This commit is contained in:
DjDeveloperr 2021-02-12 17:07:38 +05:30
parent f4b4c640b3
commit a800f394ac
9 changed files with 219 additions and 130 deletions

View File

@ -1,7 +1,16 @@
import { Guild } from '../../structures/guild.ts'
import { Member } from '../../structures/member.ts' import { Member } from '../../structures/member.ts'
import { Interaction } from '../../structures/slash.ts' import { Role } from '../../structures/role.ts'
import {
Interaction,
InteractionApplicationCommandResolved,
InteractionChannel
} from '../../structures/slash.ts'
import { GuildTextChannel } from '../../structures/textChannel.ts' import { GuildTextChannel } from '../../structures/textChannel.ts'
import { User } from '../../structures/user.ts'
import { InteractionPayload } from '../../types/slash.ts' import { InteractionPayload } from '../../types/slash.ts'
import { UserPayload } from '../../types/user.ts'
import { Permissions } from '../../utils/permissions.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import { Gateway, GatewayEventHandler } from '../index.ts'
export const interactionCreate: GatewayEventHandler = async ( export const interactionCreate: GatewayEventHandler = async (
@ -18,13 +27,12 @@ export const interactionCreate: GatewayEventHandler = async (
d.guild_id === undefined d.guild_id === undefined
? undefined ? undefined
: await gateway.client.guilds.get(d.guild_id) : await gateway.client.guilds.get(d.guild_id)
if (guild === undefined) return
if (d.member !== undefined) if (d.member !== undefined)
await guild.members.set(d.member.user.id, d.member) await guild?.members.set(d.member.user.id, d.member)
const member = const member =
d.member !== undefined d.member !== undefined
? (((await guild.members.get(d.member.user.id)) as unknown) as Member) ? (((await guild?.members.get(d.member.user.id)) as unknown) as Member)
: undefined : undefined
if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user) if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user)
const dmUser = const dmUser =
@ -37,11 +45,66 @@ export const interactionCreate: GatewayEventHandler = async (
(await gateway.client.channels.get<GuildTextChannel>(d.channel_id)) ?? (await gateway.client.channels.get<GuildTextChannel>(d.channel_id)) ??
(await gateway.client.channels.fetch<GuildTextChannel>(d.channel_id)) (await gateway.client.channels.fetch<GuildTextChannel>(d.channel_id))
const resolved: InteractionApplicationCommandResolved = {
users: {},
channels: {},
members: {},
roles: {}
}
if (d.data?.resolved !== undefined) {
for (const [id, data] of Object.entries(d.data.resolved.users ?? {})) {
await gateway.client.users.set(id, data)
resolved.users[id] = ((await gateway.client.users.get(
id
)) as unknown) as User
if (resolved.members[id] !== undefined)
resolved.users[id].member = resolved.members[id]
}
for (const [id, data] of Object.entries(d.data.resolved.members ?? {})) {
const roles = await guild?.roles.array()
let permissions = new Permissions(Permissions.DEFAULT)
if (roles !== undefined) {
const mRoles = roles.filter(
(r) => (data?.roles?.includes(r.id) as boolean) || r.id === guild?.id
)
permissions = new Permissions(mRoles.map((r) => r.permissions))
}
data.user = (d.data.resolved.users?.[id] as unknown) as UserPayload
resolved.members[id] = new Member(
gateway.client,
data,
resolved.users[id],
guild as Guild,
permissions
)
}
for (const [id, data] of Object.entries(d.data.resolved.roles ?? {})) {
if (guild !== undefined) {
await guild.roles.set(id, data)
resolved.roles[id] = ((await guild.roles.get(id)) as unknown) as Role
} else {
resolved.roles[id] = new Role(
gateway.client,
data,
(guild as unknown) as Guild
)
}
}
for (const [id, data] of Object.entries(d.data.resolved.channels ?? {})) {
resolved.channels[id] = new InteractionChannel(gateway.client, data)
}
}
const interaction = new Interaction(gateway.client, d, { const interaction = new Interaction(gateway.client, d, {
member, member,
guild, guild,
channel, channel,
user user,
resolved
}) })
gateway.client.emit('interactionCreate', interaction) gateway.client.emit('interactionCreate', interaction)
} }

View File

@ -7,7 +7,6 @@ import {
import { GatewayResponse } from '../types/gatewayResponse.ts' import { GatewayResponse } from '../types/gatewayResponse.ts'
import { import {
GatewayOpcodes, GatewayOpcodes,
GatewayIntents,
GatewayCloseCodes, GatewayCloseCodes,
IdentityPayload, IdentityPayload,
StatusUpdatePayload, StatusUpdatePayload,
@ -57,8 +56,6 @@ export type GatewayTypedEvents = {
*/ */
export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
websocket?: WebSocket websocket?: WebSocket
token?: string
intents?: GatewayIntents[]
connected = false connected = false
initialized = false initialized = false
heartbeatInterval = 0 heartbeatInterval = 0
@ -269,8 +266,9 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
} }
private async sendIdentify(forceNewSession?: boolean): Promise<void> { private async sendIdentify(forceNewSession?: boolean): Promise<void> {
if (typeof this.token !== 'string') throw new Error('Token not specified') if (typeof this.client.token !== 'string')
if (typeof this.intents !== 'object') throw new Error('Token not specified')
if (typeof this.client.intents !== 'object')
throw new Error('Intents not specified') throw new Error('Intents not specified')
if (this.client.fetchGatewayInfo === true) { if (this.client.fetchGatewayInfo === true) {
@ -300,7 +298,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
} }
const payload: IdentityPayload = { const payload: IdentityPayload = {
token: this.token, token: this.client.token,
properties: { properties: {
$os: this.client.clientProperties.os ?? Deno.build.os, $os: this.client.clientProperties.os ?? Deno.build.os,
$browser: this.client.clientProperties.browser ?? 'harmony', $browser: this.client.clientProperties.browser ?? 'harmony',
@ -311,7 +309,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
this.shards === undefined this.shards === undefined
? [0, 1] ? [0, 1]
: [this.shards[0] ?? 0, this.shards[1] ?? 1], : [this.shards[0] ?? 0, this.shards[1] ?? 1],
intents: this.intents.reduce( intents: this.client.intents.reduce(
(previous, current) => previous | current, (previous, current) => previous | current,
0 0
), ),
@ -327,9 +325,8 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
} }
private async sendResume(): Promise<void> { private async sendResume(): Promise<void> {
if (typeof this.token !== 'string') throw new Error('Token not specified') if (typeof this.client.token !== 'string')
if (typeof this.intents !== 'object') throw new Error('Token not specified')
throw new Error('Intents not specified')
if (this.sessionID === undefined) { if (this.sessionID === undefined) {
this.sessionID = await this.cache.get( this.sessionID = await this.cache.get(
@ -348,7 +345,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
const resumePayload = { const resumePayload = {
op: GatewayOpcodes.RESUME, op: GatewayOpcodes.RESUME,
d: { d: {
token: this.token, token: this.client.token,
session_id: this.sessionID, session_id: this.sessionID,
seq: this.sequenceID ?? null seq: this.sequenceID ?? null
} }

View File

@ -79,8 +79,6 @@ export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> {
const shardCount = await this.getShardCount() const shardCount = await this.getShardCount()
const gw = new Gateway(this.client, [Number(id), shardCount]) const gw = new Gateway(this.client, [Number(id), shardCount])
gw.token = this.client.token
gw.intents = this.client.intents
this.list.set(id.toString(), gw) this.list.set(id.toString(), gw)
gw.initWebsocket() gw.initWebsocket()

View File

@ -2,10 +2,10 @@ import { Client } from '../models/client.ts'
import { Snowflake } from '../utils/snowflake.ts' import { Snowflake } from '../utils/snowflake.ts'
export class Base { export class Base {
client: Client client!: Client
constructor(client: Client, _data?: any) { constructor(client: Client, _data?: any) {
this.client = client Object.defineProperty(this, 'client', { value: client, enumerable: false })
} }
} }

View File

@ -1,24 +1,31 @@
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { import {
AllowedMentionsPayload, AllowedMentionsPayload,
ChannelTypes,
EmbedPayload, EmbedPayload,
MessageOptions MessageOptions
} from '../types/channel.ts' } from '../types/channel.ts'
import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts' import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts'
import { import {
Dict,
InteractionApplicationCommandData, InteractionApplicationCommandData,
InteractionApplicationCommandOption, InteractionApplicationCommandOption,
InteractionChannelPayload,
InteractionPayload, InteractionPayload,
InteractionResponseFlags, InteractionResponseFlags,
InteractionResponsePayload, InteractionResponsePayload,
InteractionResponseType, InteractionResponseType,
InteractionType InteractionType,
SlashCommandOptionType
} from '../types/slash.ts' } from '../types/slash.ts'
import { Permissions } from '../utils/permissions.ts'
import { SnowflakeBase } from './base.ts' import { SnowflakeBase } from './base.ts'
import { Channel } from './channel.ts'
import { Embed } from './embed.ts' import { Embed } from './embed.ts'
import { Guild } from './guild.ts' import { Guild } from './guild.ts'
import { Member } from './member.ts' import { Member } from './member.ts'
import { Message } from './message.ts' import { Message } from './message.ts'
import { Role } from './role.ts'
import { GuildTextChannel, TextChannel } from './textChannel.ts' import { GuildTextChannel, TextChannel } from './textChannel.ts'
import { User } from './user.ts' import { User } from './user.ts'
@ -54,8 +61,37 @@ export interface InteractionResponse extends InteractionMessageOptions {
ephemeral?: boolean ephemeral?: boolean
} }
/** Represents a Channel Object for an Option in Slash Command */
export class InteractionChannel extends SnowflakeBase {
name: string
type: ChannelTypes
permissions: Permissions
constructor(client: Client, data: InteractionChannelPayload) {
super(client)
this.name = data.name
this.type = data.type
this.permissions = new Permissions(data.permissions)
}
/** Resolve to actual Channel object if present in Cache */
async resolve<T = Channel>(): Promise<T | undefined> {
return this.client.channels.get<T>(this.id)
}
}
export interface InteractionApplicationCommandResolved {
users: Dict<InteractionUser>
members: Dict<Member>
channels: Dict<InteractionChannel>
roles: Dict<Role>
}
export class InteractionUser extends User {
member?: Member
}
export class Interaction extends SnowflakeBase { export class Interaction extends SnowflakeBase {
client: Client
/** Type of Interaction */ /** Type of Interaction */
type: InteractionType type: InteractionType
/** Interaction Token */ /** Interaction Token */
@ -72,6 +108,7 @@ export class Interaction extends SnowflakeBase {
/** User object of who invoked Interaction */ /** User object of who invoked Interaction */
user: User user: User
responded: boolean = false responded: boolean = false
resolved: InteractionApplicationCommandResolved
constructor( constructor(
client: Client, client: Client,
@ -81,10 +118,10 @@ export class Interaction extends SnowflakeBase {
guild?: Guild guild?: Guild
member?: Member member?: Member
user: User user: User
resolved: InteractionApplicationCommandResolved
} }
) { ) {
super(client) super(client)
this.client = client
this.type = data.type this.type = data.type
this.token = data.token this.token = data.token
this.member = others.member this.member = others.member
@ -93,6 +130,7 @@ export class Interaction extends SnowflakeBase {
this.data = data.data this.data = data.data
this.guild = others.guild this.guild = others.guild
this.channel = others.channel this.channel = others.channel
this.resolved = others.resolved
} }
/** Name of the Command Used (may change with future additions to Interactions!) */ /** Name of the Command Used (may change with future additions to Interactions!) */
@ -105,8 +143,16 @@ export class Interaction extends SnowflakeBase {
} }
/** Get an option by name */ /** Get an option by name */
option<T = any>(name: string): T { option<T>(name: string): T {
return this.options.find((e) => e.name === name)?.value const op = this.options.find((e) => e.name === name)
if (op === undefined || op.value === undefined) return undefined as any
if (op.type === SlashCommandOptionType.USER)
return this.resolved.users[op.value] as any
else if (op.type === SlashCommandOptionType.ROLE)
return this.resolved.roles[op.value] as any
else if (op.type === SlashCommandOptionType.CHANNEL)
return this.resolved.channels[op.value] as any
else return op.value
} }
/** Respond to an Interaction */ /** Respond to an Interaction */

View File

@ -1,100 +1,56 @@
import { Client, Intents, event, slash } from '../../mod.ts' import {
import { Embed } from '../structures/embed.ts' Client,
Intents,
event,
slash,
SlashCommandOptionType as Type
} from '../../mod.ts'
import { Interaction } from '../structures/slash.ts' import { Interaction } from '../structures/slash.ts'
import { TOKEN } from './config.ts' import { TOKEN } from './config.ts'
export class MyClient extends Client { export class MyClient extends Client {
@event() @event() ready(): void {
ready(): void {
console.log(`Logged in as ${this.user?.tag}!`) console.log(`Logged in as ${this.user?.tag}!`)
this.slash.commands.bulkEdit([{ name: 'send', description: 'idk' }]) this.slash.commands.bulkEdit(
} [
{
@event('debug') name: 'test',
debugEvt(txt: string): void { description: 'Test command.',
console.log(txt) options: [
} {
name: 'user',
@slash() type: Type.USER,
send(d: Interaction): void { description: 'User'
d.respond({ },
content: d.data.options?.find((e) => e.name === 'content')?.value {
}) name: 'role',
} type: Type.ROLE,
description: 'Role'
@slash() },
async eval(d: Interaction): Promise<void> { {
if ( name: 'channel',
d.user.id !== '422957901716652033' && type: Type.CHANNEL,
d.user.id !== '682849186227552266' description: 'Channel'
) { },
d.respond({ {
content: 'This command can only be used by owner!' name: 'string',
}) type: Type.STRING,
} else { description: 'String'
const code = d.data.options?.find((e) => e.name === 'code') }
?.value as string ]
try {
// eslint-disable-next-line no-eval
let evaled = eval(code)
if (evaled instanceof Promise) evaled = await evaled
if (typeof evaled === 'object') evaled = Deno.inspect(evaled)
let res = `${evaled}`.substring(0, 1990)
while (client.token !== undefined && res.includes(client.token)) {
res = res.replace(client.token, '[REMOVED]')
} }
d.respond({ ],
content: '```js\n' + `${res}` + '\n```' '807935370556866560'
}).catch(() => {}) )
} catch (e) { this.slash.commands.bulkEdit([])
d.respond({
content: '```js\n' + `${e.stack}` + '\n```'
})
}
}
} }
@slash() @slash() test(d: Interaction): void {
async hug(d: Interaction): Promise<void> { console.log(d.resolved)
const id = d.data.options?.find((e) => e.name === 'user')?.value as string
const user = (await client.users.get(id)) ?? (await client.users.fetch(id))
const url = await fetch('https://nekos.life/api/v2/img/hug')
.then((r) => r.json())
.then((e) => e.url)
d.respond({
embeds: [
new Embed()
.setTitle(`${d.user.username} hugged ${user?.username}!`)
.setImage({ url })
.setColor(0x2f3136)
]
})
} }
@slash() @event() raw(evt: string, d: any): void {
async kiss(d: Interaction): Promise<void> { if (evt === 'INTERACTION_CREATE') console.log(evt, d?.data?.resolved)
const id = d.data.options?.find((e) => e.name === 'user')?.value as string
const user = (await client.users.get(id)) ?? (await client.users.fetch(id))
const url = await fetch('https://nekos.life/api/v2/img/kiss')
.then((r) => r.json())
.then((e) => e.url)
d.respond({
embeds: [
new Embed()
.setTitle(`${d.user.username} kissed ${user?.username}!`)
.setImage({ url })
.setColor(0x2f3136)
]
})
}
@slash('ping')
pingCmd(d: Interaction): void {
d.respond({
content: `Pong!`
})
} }
} }

View File

@ -1,5 +1,10 @@
import { AllowedMentionsPayload, EmbedPayload } from './channel.ts' import {
AllowedMentionsPayload,
ChannelTypes,
EmbedPayload
} from './channel.ts'
import { MemberPayload } from './guild.ts' import { MemberPayload } from './guild.ts'
import { RolePayload } from './role.ts'
import { UserPayload } from './user.ts' import { UserPayload } from './user.ts'
export interface InteractionApplicationCommandOption { export interface InteractionApplicationCommandOption {
@ -13,6 +18,24 @@ export interface InteractionApplicationCommandOption {
options?: InteractionApplicationCommandOption[] options?: InteractionApplicationCommandOption[]
} }
export interface InteractionChannelPayload {
id: string
name: string
permissions: string
type: ChannelTypes
}
export interface Dict<T> {
[name: string]: T
}
export interface InteractionApplicationCommandResolvedPayload {
users?: Dict<UserPayload>
members?: Dict<MemberPayload>
channels?: Dict<InteractionChannelPayload>
roles?: Dict<RolePayload>
}
export interface InteractionApplicationCommandData { export interface InteractionApplicationCommandData {
/** Name of the Slash Command */ /** Name of the Slash Command */
name: string name: string
@ -20,6 +43,8 @@ export interface InteractionApplicationCommandData {
id: string id: string
/** Options (arguments) sent with Interaction */ /** Options (arguments) sent with Interaction */
options: InteractionApplicationCommandOption[] options: InteractionApplicationCommandOption[]
/** Resolved data for options in Slash Command */
resolved?: InteractionApplicationCommandResolvedPayload
} }
export enum InteractionType { export enum InteractionType {

View File

@ -9,31 +9,31 @@ export type BitFieldResolvable =
/** Bit Field utility to work with Bits and Flags */ /** Bit Field utility to work with Bits and Flags */
export class BitField { export class BitField {
flags: { [name: string]: number } = {} #flags: { [name: string]: number } = {}
bitfield: number bitfield: number
constructor(flags: { [name: string]: number }, bits: any) { constructor(flags: { [name: string]: number }, bits: any) {
this.flags = flags this.#flags = flags
this.bitfield = BitField.resolve(this.flags, bits) this.bitfield = BitField.resolve(this.#flags, bits)
} }
any(bit: BitFieldResolvable): boolean { any(bit: BitFieldResolvable): boolean {
return (this.bitfield & BitField.resolve(this.flags, bit)) !== 0 return (this.bitfield & BitField.resolve(this.#flags, bit)) !== 0
} }
equals(bit: BitFieldResolvable): boolean { equals(bit: BitFieldResolvable): boolean {
return this.bitfield === BitField.resolve(this.flags, bit) return this.bitfield === BitField.resolve(this.#flags, bit)
} }
has(bit: BitFieldResolvable, ...args: any[]): boolean { has(bit: BitFieldResolvable, ...args: any[]): boolean {
if (Array.isArray(bit)) return (bit.every as any)((p: any) => this.has(p)) if (Array.isArray(bit)) return (bit.every as any)((p: any) => this.has(p))
bit = BitField.resolve(this.flags, bit) bit = BitField.resolve(this.#flags, bit)
return (this.bitfield & bit) === bit return (this.bitfield & bit) === bit
} }
missing(bits: any, ...hasParams: any[]): string[] { missing(bits: any, ...hasParams: any[]): string[] {
if (!Array.isArray(bits)) if (!Array.isArray(bits))
bits = new BitField(this.flags, bits).toArray(false) bits = new BitField(this.#flags, bits).toArray(false)
return bits.filter((p: any) => !this.has(p, ...hasParams)) return bits.filter((p: any) => !this.has(p, ...hasParams))
} }
@ -44,10 +44,10 @@ export class BitField {
add(...bits: BitFieldResolvable[]): BitField { add(...bits: BitFieldResolvable[]): BitField {
let total = 0 let total = 0
for (const bit of bits) { for (const bit of bits) {
total |= BitField.resolve(this.flags, bit) total |= BitField.resolve(this.#flags, bit)
} }
if (Object.isFrozen(this)) if (Object.isFrozen(this))
return new BitField(this.flags, this.bitfield | total) return new BitField(this.#flags, this.bitfield | total)
this.bitfield |= total this.bitfield |= total
return this return this
} }
@ -55,27 +55,31 @@ export class BitField {
remove(...bits: BitFieldResolvable[]): BitField { remove(...bits: BitFieldResolvable[]): BitField {
let total = 0 let total = 0
for (const bit of bits) { for (const bit of bits) {
total |= BitField.resolve(this.flags, bit) total |= BitField.resolve(this.#flags, bit)
} }
if (Object.isFrozen(this)) if (Object.isFrozen(this))
return new BitField(this.flags, this.bitfield & ~total) return new BitField(this.#flags, this.bitfield & ~total)
this.bitfield &= ~total this.bitfield &= ~total
return this return this
} }
flags(): { [name: string]: number } {
return this.#flags
}
serialize(...hasParams: any[]): { [key: string]: any } { serialize(...hasParams: any[]): { [key: string]: any } {
const serialized: { [key: string]: any } = {} const serialized: { [key: string]: any } = {}
for (const [flag, bit] of Object.entries(this.flags)) for (const [flag, bit] of Object.entries(this.#flags))
serialized[flag] = this.has( serialized[flag] = this.has(
BitField.resolve(this.flags, bit), BitField.resolve(this.#flags, bit),
...hasParams ...hasParams
) )
return serialized return serialized
} }
toArray(...hasParams: any[]): string[] { toArray(...hasParams: any[]): string[] {
return Object.keys(this.flags).filter((bit) => return Object.keys(this.#flags).filter((bit) =>
this.has(BitField.resolve(this.flags, bit), ...hasParams) this.has(BitField.resolve(this.#flags, bit), ...hasParams)
) )
} }

View File

@ -21,14 +21,14 @@ export class Permissions extends BitField {
any(permission: PermissionResolvable, checkAdmin = true): boolean { any(permission: PermissionResolvable, checkAdmin = true): boolean {
return ( return (
(checkAdmin && super.has(this.flags.ADMINISTRATOR)) || (checkAdmin && super.has(this.flags().ADMINISTRATOR)) ||
super.any(permission as any) super.any(permission as any)
) )
} }
has(permission: PermissionResolvable, checkAdmin = true): boolean { has(permission: PermissionResolvable, checkAdmin = true): boolean {
return ( return (
(checkAdmin && super.has(this.flags.ADMINISTRATOR)) || (checkAdmin && super.has(this.flags().ADMINISTRATOR)) ||
super.has(permission as any) super.has(permission as any)
) )
} }