MASSIVE UPDATE NOW WITH EVENTS

Co-Authored-By: Y <8479056+yky4589@users.noreply.github.com>
Co-Authored-By: Aki <71239005+AkiaCode@users.noreply.github.com>
This commit is contained in:
Helloyunho 2020-10-30 23:51:40 +09:00
parent e899738b55
commit f319e0df91
42 changed files with 641 additions and 483 deletions

4
.gitignore vendored
View File

@ -109,6 +109,4 @@ yarn.lock
# PRIVACY XDDDD
src/test/config.ts
.vscode
src/test/
.vscode

View File

@ -0,0 +1,13 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import getChannelByType from '../../utils/getChannelByType.ts'
export const channelCreate: GatewayEventHandler = (
gateway: Gateway,
d: any
) => {
const channel = getChannelByType(gateway.client, d)
if (channel !== undefined) {
gateway.client.emit('channelCreate', channel)
}
}

View File

@ -0,0 +1,14 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { Channel } from '../../structures/channel.ts'
export const channelDelete: GatewayEventHandler = (
gateway: Gateway,
d: any
) => {
const channel: Channel = cache.get('channel', d.id)
if (channel !== undefined) {
cache.del('channel', d.id)
gateway.client.emit('channelDelete', channel)
}
}

View File

@ -0,0 +1,16 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { TextChannel } from '../../structures/textChannel.ts'
export const channelPinsUpdate: GatewayEventHandler = (
gateway: Gateway,
d: any
) => {
const after: TextChannel = cache.get('textchannel', d.channel_id)
if (after !== undefined) {
const before = after.refreshFromData({
last_pin_timestamp: d.last_pin_timestamp
})
gateway.client.emit('channelPinsUpdate', before, after)
}
}

View File

@ -0,0 +1,21 @@
import cache from '../../models/cache.ts'
import { Channel } from '../../structures/channel.ts'
import getChannelByType from '../../utils/getChannelByType.ts'
import { Gateway, GatewayEventHandler } from '../index.ts'
export const channelUpdate: GatewayEventHandler = (
gateway: Gateway,
d: any
) => {
const oldChannel: Channel = cache.get('channel', d.id)
if (oldChannel !== undefined) {
if (oldChannel.type !== d.type) {
const channel: Channel = getChannelByType(gateway.client, d) ?? oldChannel
gateway.client.emit('channelUpdate', oldChannel, channel)
} else {
const before = oldChannel.refreshFromData(d)
gateway.client.emit('channelUpdate', before, oldChannel)
}
}
}

View File

@ -0,0 +1,14 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts'
export const guildBanAdd: GatewayEventHandler = (gateway: Gateway, d: any) => {
const guild: Guild = cache.get('guild', d.guild_id)
const user: User =
cache.get('user', d.user.id) ?? new User(gateway.client, d.user)
if (guild !== undefined) {
gateway.client.emit('guildBanAdd', guild, user)
}
}

View File

@ -0,0 +1,17 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts'
export const guildBanRemove: GatewayEventHandler = (
gateway: Gateway,
d: any
) => {
const guild: Guild = cache.get('guild', d.guild_id)
const user: User =
cache.get('user', d.user.id) ?? new User(gateway.client, d.user)
if (guild !== undefined) {
gateway.client.emit('guildBanRemove', guild, user)
}
}

View File

@ -0,0 +1,14 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts'
export const guildCreate: GatewayEventHandler = (gateway: Gateway, d: any) => {
let guild: Guild = cache.get('guild', d.id)
if (guild !== undefined) {
guild.refreshFromData(d)
} else {
guild = new Guild(gateway.client, d)
}
gateway.client.emit('guildCreate', guild)
}

View File

@ -0,0 +1,13 @@
import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts'
import { Gateway, GatewayEventHandler } from '../index.ts'
export const guildDelte: GatewayEventHandler = (gateway: Gateway, d: any) => {
const guild: Guild = cache.get('guild', d.id)
if (guild !== undefined) {
guild.refreshFromData(d)
cache.del('guild', d.id)
gateway.client.emit('guildDelete', guild)
}
}

View File

@ -0,0 +1,11 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import cache from '../../models/cache.ts'
import { Guild } from '../../structures/guild.ts'
export const guildUpdate: GatewayEventHandler = (gateway: Gateway, d: any) => {
const after: Guild = cache.get('guild', d.id)
if (after !== undefined) {
const before: Guild = after.refreshFromData(d)
gateway.client.emit('guildUpdate', before, after)
}
}

View File

@ -0,0 +1,53 @@
import { GatewayEventHandler } from '../index.ts'
import { GatewayEvents } from '../../types/gatewayTypes.ts'
import { channelCreate } from './channelCreate.ts'
import { channelDelete } from './channelDelete.ts'
import { channelUpdate } from './channelUpdate.ts'
import { channelPinsUpdate } from './channelPinsUpdate.ts'
import { guildCreate } from './guildCreate.ts'
import { guildDelte as guildDelete } from './guildDelete.ts'
import { guildUpdate } from './guildUpdate.ts'
import { guildBanAdd } from './guildBanAdd.ts'
import { ready } from './ready.ts'
import { guildBanRemove } from './guildBanRemove.ts'
export const gatewayHandlers: {
[eventCode in GatewayEvents]: GatewayEventHandler | undefined
} = {
READY: ready,
RECONNECT: undefined,
RESUMED: undefined,
CHANNEL_CREATE: channelCreate,
CHANNEL_DELETE: channelDelete,
CHANNEL_UPDATE: channelUpdate,
CHANNEL_PINS_UPDATE: channelPinsUpdate,
GUILD_CREATE: guildCreate,
GUILD_DELETE: guildDelete,
GUILD_UPDATE: guildUpdate,
GUILD_BAN_ADD: guildBanAdd,
GUILD_BAN_REMOVE: guildBanRemove,
GUILD_EMOJIS_UPDATE: undefined,
GUILD_INTEGRATIONS_UPDATE: undefined,
GUILD_MEMBER_ADD: undefined,
GUILD_MEMBER_REMOVE: undefined,
GUILD_MEMBER_UPDATE: undefined,
GUILD_MEMBERS_CHUNK: undefined,
GUILD_ROLE_CREATE: undefined,
GUILD_ROLE_UPDATE: undefined,
GUILD_ROLE_DELETE: undefined,
INVITE_CREATE: undefined,
INVITE_DELETE: undefined,
MESSAGE_CREATE: undefined,
MESSAGE_UPDATE: undefined,
MESSAGE_DELETE: undefined,
MESSAGE_DELETE_BULK: undefined,
MESSAGE_REACTION_ADD: undefined,
MESSAGE_REACTION_REMOVE: undefined,
MESSAGE_REACTION_REMOVE_ALL: undefined,
MESSAGE_REACTION_REMOVE_EMOJI: undefined,
PRESENCE_UPDATE: undefined,
TYPING_START: undefined,
USER_UPDATE: undefined,
VOICE_SERVER_UPDATE: undefined,
WEBHOOKS_UPDATE: undefined
}

View File

@ -0,0 +1,11 @@
import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts'
import { GuildPayload } from '../../types/guildTypes.ts'
import { Gateway, GatewayEventHandler } from '../index.ts'
export const ready: GatewayEventHandler = (gateway: Gateway, d: any) => {
gateway.client.user = new User(gateway.client, d.user)
gateway.sessionID = d.session_id
d.guilds.forEach((guild: GuildPayload) => new Guild(gateway.client, guild))
gateway.client.emit('ready')
}

199
src/gateway/index.ts Normal file
View File

@ -0,0 +1,199 @@
import { unzlib } from 'https://deno.land/x/denoflate/mod.ts'
import { Client } from '../models/client.ts'
import {
DISCORD_GATEWAY_URL,
DISCORD_API_VERSION
} from '../consts/urlsAndVersions.ts'
import { GatewayResponse } from '../types/gatewayResponse.ts'
import { GatewayOpcodes, GatewayIntents } from '../types/gatewayTypes.ts'
import { gatewayHandlers } from './handlers/index.ts'
/**
* Handles Discord gateway connection.
* You should not use this and rather use Client class.
*
* @beta
*/
class Gateway {
websocket: WebSocket
token: string
intents: GatewayIntents[]
connected = false
initialized = false
heartbeatInterval = 0
heartbeatIntervalID?: number
sequenceID?: number
sessionID?: string
lastPingTimestemp = 0
private heartbeatServerResponded = false
client: Client
constructor (client: Client, token: string, intents: GatewayIntents[]) {
this.token = token
this.intents = intents
this.client = client
this.websocket = new WebSocket(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`,
[]
)
this.websocket.binaryType = 'arraybuffer'
this.websocket.onopen = this.onopen.bind(this)
this.websocket.onmessage = this.onmessage.bind(this)
this.websocket.onclose = this.onclose.bind(this)
this.websocket.onerror = this.onerror.bind(this)
}
private onopen (): void {
this.connected = true
}
private onmessage (event: MessageEvent): void {
let data = event.data
if (data instanceof ArrayBuffer) {
data = new Uint8Array(data)
}
if (data instanceof Uint8Array) {
data = unzlib(data)
data = new TextDecoder('utf-8').decode(data)
}
const { op, d, s, t }: GatewayResponse = JSON.parse(data)
switch (op) {
case GatewayOpcodes.HELLO:
this.heartbeatInterval = d.heartbeat_interval
this.heartbeatIntervalID = setInterval(() => {
if (this.heartbeatServerResponded) {
this.heartbeatServerResponded = false
} else {
clearInterval(this.heartbeatIntervalID)
this.websocket.close()
this.initWebsocket()
return
}
this.websocket.send(
JSON.stringify({
op: GatewayOpcodes.HEARTBEAT,
d: this.sequenceID ?? null
})
)
this.lastPingTimestemp = Date.now()
}, this.heartbeatInterval)
if (!this.initialized) {
this.sendIdentify()
this.initialized = true
} else {
this.sendResume()
}
break
case GatewayOpcodes.HEARTBEAT_ACK:
this.heartbeatServerResponded = true
this.client.ping = Date.now() - this.lastPingTimestemp
break
case GatewayOpcodes.INVALID_SESSION:
// Because we know this gonna be bool
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!d) {
setTimeout(this.sendResume, 3000)
} else {
setTimeout(this.sendIdentify, 3000)
}
break
case GatewayOpcodes.DISPATCH: {
this.heartbeatServerResponded = true
if (s !== null) {
this.sequenceID = s
}
if (t !== null && t !== undefined) {
const handler = gatewayHandlers[t]
if (handler !== undefined) {
handler(this, d)
}
}
break
}
default:
break
}
}
private onclose (event: CloseEvent): void {
console.log(event.code)
// TODO: Handle close event codes.
}
private onerror (event: Event | ErrorEvent): void {
const eventError = event as ErrorEvent
console.log(eventError)
}
private sendIdentify (): void {
this.websocket.send(
JSON.stringify({
op: GatewayOpcodes.IDENTIFY,
d: {
token: this.token,
properties: {
$os: Deno.build.os,
$browser: 'discord.deno',
$device: 'discord.deno'
},
compress: true,
shard: [0, 1], // TODO: Make sharding possible
intents: this.intents.reduce(
(previous, current) => previous | current,
0
),
presence: {
// TODO: User should can customize this
status: 'online',
since: null,
afk: false
}
}
})
)
}
private sendResume (): void {
this.websocket.send(
JSON.stringify({
op: GatewayOpcodes.RESUME,
d: {
token: this.token,
session_id: this.sessionID,
seq: this.sequenceID
}
})
)
}
initWebsocket (): void {
this.websocket = new WebSocket(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`,
[]
)
this.websocket.binaryType = 'arraybuffer'
this.websocket.onopen = this.onopen.bind(this)
this.websocket.onmessage = this.onmessage.bind(this)
this.websocket.onclose = this.onclose.bind(this)
this.websocket.onerror = this.onerror.bind(this)
}
close (): void {
this.websocket.close(1000)
}
}
export type GatewayEventHandler = (gateway: Gateway, d: any) => void
export { Gateway }

View File

@ -1,8 +1,9 @@
import { User } from '../structures/user.ts'
import { GatewayIntents } from '../types/gatewayTypes.ts'
import { Gateway } from './gateway.ts'
import { Gateway } from '../gateway/index.ts'
import { Rest } from './rest.ts'
import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts'
/**
* Discord Client.
*/

View File

@ -1,309 +0,0 @@
import { unzlib } from 'https://deno.land/x/denoflate/mod.ts'
import { Client } from './client.ts'
import {
DISCORD_GATEWAY_URL,
DISCORD_API_VERSION
} from '../consts/urlsAndVersions.ts'
import { GatewayResponse } from '../types/gatewayResponse.ts'
import {
GatewayOpcodes,
GatewayIntents,
GatewayEvents
} from '../types/gatewayTypes.ts'
import { GuildPayload } from '../types/guildTypes.ts'
import { User } from '../structures/user.ts'
import * as cache from './cache.ts'
import { Guild } from '../structures/guild.ts'
import { Channel } from '../structures/channel.ts'
import { ChannelTypes } from '../types/channelTypes.ts'
import { DMChannel } from '../structures/dmChannel.ts'
import { GroupDMChannel } from '../structures/groupChannel.ts'
import { GuildTextChannel } from '../structures/guildTextChannel.ts'
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
import { CategoryChannel } from '../structures/guildCategoryChannel.ts'
import { NewsChannel } from '../structures/guildNewsChannel.ts'
/**
* Handles Discord gateway connection.
* You should not use this and rather use Client class.
*
* @beta
*/
class Gateway {
websocket: WebSocket
token: string
intents: GatewayIntents[]
connected = false
initialized = false
private heartbeatInterval = 0
private heartbeatIntervalID?: number
private sequenceID?: number
private sessionID?: string
lastPingTimestemp = 0
private heartbeatServerResponded = false
client: Client
constructor (client: Client, token: string, intents: GatewayIntents[]) {
this.token = token
this.intents = intents
this.client = client
this.websocket = new WebSocket(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`,
[]
)
this.websocket.binaryType = 'arraybuffer'
this.websocket.onopen = this.onopen.bind(this)
this.websocket.onmessage = this.onmessage.bind(this)
this.websocket.onclose = this.onclose.bind(this)
this.websocket.onerror = this.onerror.bind(this)
}
private onopen (): void {
this.connected = true
}
private onmessage (event: MessageEvent): void {
let data = event.data
if (data instanceof ArrayBuffer) {
data = new Uint8Array(data)
}
if (data instanceof Uint8Array) {
data = unzlib(data)
data = new TextDecoder('utf-8').decode(data)
}
const { op, d, s, t }: GatewayResponse = JSON.parse(data)
switch (op) {
case GatewayOpcodes.HELLO:
this.heartbeatInterval = d.heartbeat_interval
this.heartbeatIntervalID = setInterval(() => {
if (this.heartbeatServerResponded) {
this.heartbeatServerResponded = false
} else {
clearInterval(this.heartbeatIntervalID)
this.websocket.close()
this.initWebsocket()
return
}
this.websocket.send(
JSON.stringify({
op: GatewayOpcodes.HEARTBEAT,
d: this.sequenceID ?? null
})
)
this.lastPingTimestemp = Date.now()
}, this.heartbeatInterval)
if (!this.initialized) {
this.sendIdentify()
this.initialized = true
} else {
this.sendResume()
}
break
case GatewayOpcodes.HEARTBEAT_ACK:
this.heartbeatServerResponded = true
this.client.ping = Date.now() - this.lastPingTimestemp
break
case GatewayOpcodes.INVALID_SESSION:
// Because we know this gonna be bool
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!d) {
setTimeout(this.sendResume, 3000)
} else {
setTimeout(this.sendIdentify, 3000)
}
break
case GatewayOpcodes.DISPATCH:
this.heartbeatServerResponded = true
if (s !== null) {
this.sequenceID = s
}
switch (t) {
case GatewayEvents.Ready:
this.client.user = new User(this.client, d.user)
this.sessionID = d.session_id
d.guilds.forEach((guild: GuildPayload) => {
cache.set('guild', guild.id, new Guild(this.client, guild))
})
this.client.emit('ready')
break
case GatewayEvents.Channel_Create: {
let channel: Channel | undefined
switch (d.type) {
case ChannelTypes.DM:
channel = new DMChannel(this.client, d)
break
case ChannelTypes.GROUP_DM:
channel = new GroupDMChannel(this.client, d)
break
case ChannelTypes.GUILD_TEXT:
channel = new GuildTextChannel(this.client, d)
break
case ChannelTypes.GUILD_VOICE:
channel = new VoiceChannel(this.client, d)
break
case ChannelTypes.GUILD_CATEGORY:
channel = new CategoryChannel(this.client, d)
break
case ChannelTypes.GUILD_NEWS:
channel = new NewsChannel(this.client, d)
break
default:
break
}
if (channel !== undefined) {
cache.set('channel', channel.id, channel)
this.client.emit('channelCreate', channel)
}
break
}
case GatewayEvents.Channel_Update: {
const oldChannel: Channel = cache.get('channel', d.id)
if (oldChannel !== undefined) {
if (oldChannel.type !== d.type) {
let channel: Channel = oldChannel
switch (d.type) {
case ChannelTypes.DM:
channel = new DMChannel(this.client, d)
break
case ChannelTypes.GROUP_DM:
channel = new GroupDMChannel(this.client, d)
break
case ChannelTypes.GUILD_TEXT:
channel = new GuildTextChannel(this.client, d)
break
case ChannelTypes.GUILD_VOICE:
channel = new VoiceChannel(this.client, d)
break
case ChannelTypes.GUILD_CATEGORY:
channel = new CategoryChannel(this.client, d)
break
case ChannelTypes.GUILD_NEWS:
channel = new NewsChannel(this.client, d)
break
default:
break
}
cache.set('channel', channel.id, channel)
this.client.emit('channelUpdate', oldChannel, channel)
} else {
const before = oldChannel.refreshFromData(d)
this.client.emit('channelUpdate', before, oldChannel)
}
}
break
}
case GatewayEvents.Channel_Delete: {
const channel: Channel = cache.get('channel', d.id)
if (channel !== undefined) {
cache.del('channel', d.id)
this.client.emit('channelDelete', channel)
}
break
}
case GatewayEvents.Channel_Pins_Update: {
const channel: Channel = cache.get('channel', d.channel_id)
if (channel !== undefined && d.last_pin_timestamp !== null) {
channel.refreshFromData({
last_pin_timestamp: d.last_pin_timestamp
})
this.client.emit('channelPinsUpdate', channel)
}
break
}
case GatewayEvents.Guild_Create: {
const guild: Guild = cache.get('guild', d.id)
if (guild !== undefined) {
guild.refreshFromData(guild)
}
break
}
default:
break
}
break
default:
break
}
}
private onclose (event: CloseEvent): void {
console.log(event.code)
// TODO: Handle close event codes.
}
private onerror (event: Event | ErrorEvent): void {
const eventError = event as ErrorEvent
console.log(eventError)
}
private sendIdentify (): void {
this.websocket.send(
JSON.stringify({
op: GatewayOpcodes.IDENTIFY,
d: {
token: this.token,
properties: {
$os: Deno.build.os,
$browser: 'discord.deno',
$device: 'discord.deno'
},
compress: true,
shard: [0, 1], // TODO: Make sharding possible
intents: this.intents.reduce(
(previous, current) => previous | current,
0
),
presence: {
// TODO: User should can customize this
status: 'online',
since: null,
afk: false
}
}
})
)
}
private sendResume (): void {
this.websocket.send(
JSON.stringify({
op: GatewayOpcodes.RESUME,
d: {
token: this.token,
session_id: this.sessionID,
seq: this.sequenceID
}
})
)
}
initWebsocket (): void {
this.websocket = new WebSocket(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`,
[]
)
this.websocket.binaryType = 'arraybuffer'
this.websocket.onopen = this.onopen.bind(this)
this.websocket.onmessage = this.onmessage.bind(this)
this.websocket.onclose = this.onclose.bind(this)
this.websocket.onerror = this.onerror.bind(this)
}
close (): void {
this.websocket.close(1000)
}
}
export { Gateway }

View File

@ -65,7 +65,7 @@ export class Base {
return oldOne
}
readFromData (data: { [k: string]: any }): void {}
protected readFromData (data: { [k: string]: any }): void {}
// toJSON() {}
}

View File

@ -18,7 +18,7 @@ export class Channel extends Base {
cache.set('channel', this.id, this)
}
readFromData (data: ChannelPayload): void {
protected readFromData (data: ChannelPayload): void {
super.readFromData(data)
this.type = data.type ?? this.type
this.id = data.id ?? this.id

View File

@ -13,7 +13,7 @@ export class DMChannel extends TextChannel {
cache.set('dmchannel', this.id, this)
}
readFromData (data: DMChannelPayload): void {
protected readFromData (data: DMChannelPayload): void {
super.readFromData(data)
this.recipients = data.recipients ?? this.recipients
}

View File

@ -37,7 +37,7 @@ export class Emoji extends Base {
this.available = data.available
}
readFromData (data: EmojiPayload): void {
protected readFromData (data: EmojiPayload): void {
super.readFromData(data)
this.id = data.id ?? this.id
this.name = data.name ?? this.name

View File

@ -17,7 +17,7 @@ export class GroupDMChannel extends Channel {
cache.set('groupchannel', this.id, this)
}
readFromData (data: GroupDMChannelPayload): void {
protected readFromData (data: GroupDMChannelPayload): void {
super.readFromData(data)
this.name = data.name ?? this.name
this.icon = data.icon ?? this.icon

View File

@ -8,6 +8,7 @@ import { Member } from './member.ts'
import { Role } from './role.ts'
import { VoiceState } from './voiceState.ts'
import cache from '../models/cache.ts'
import getChannelByType from '../utils/getChannelByType.ts'
export class Guild extends Base {
id: string
@ -103,7 +104,7 @@ export class Guild extends Base {
new Member(client, v)
)
this.channels = data.channels?.map(
v => cache.get('channel', v.id) ?? new Channel(client, v)
v => cache.get('channel', v.id) ?? getChannelByType(this.client, v)
)
this.presences = data.presences
this.maxPresences = data.max_presences
@ -122,7 +123,7 @@ export class Guild extends Base {
cache.set('guild', this.id, this)
}
readFromData (data: GuildPayload): void {
protected readFromData (data: GuildPayload): void {
super.readFromData(data)
this.id = data.id ?? this.id
this.unavailable = data.unavailable ?? this.unavailable
@ -177,7 +178,7 @@ export class Guild extends Base {
) ?? this.members
this.channels =
data.channels?.map(
v => cache.get('channel', v.id) ?? new Channel(this.client, v)
v => cache.get('channel', v.id) ?? getChannelByType(this.client, v)
) ?? this.members
this.presences = data.presences ?? this.presences
this.maxPresences = data.max_presences ?? this.maxPresences

View File

@ -25,7 +25,7 @@ export class CategoryChannel extends Channel {
cache.set('guildcategorychannel', this.id, this)
}
readFromData (data: GuildChannelCategoryPayload): void {
protected readFromData (data: GuildChannelCategoryPayload): void {
super.readFromData(data)
this.guildID = data.guild_id ?? this.guildID
this.name = data.name ?? this.name

View File

@ -1,35 +0,0 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts'
import { GuildChannelPayload, Overwrite } from '../types/channelTypes.ts'
import { Channel } from './channel.ts'
export class GuildChannel extends Channel {
guildID: string
name: string
position: number
permissionOverwrites: Overwrite[]
nsfw: boolean
parentID?: string
constructor (client: Client, data: GuildChannelPayload) {
super(client, data)
this.guildID = data.guild_id
this.name = data.name
this.position = data.position
this.permissionOverwrites = data.permission_overwrites
this.nsfw = data.nsfw
this.parentID = data.parent_id
cache.set('guildchannel', this.id, this)
}
readFromData (data: GuildChannelPayload): void {
super.readFromData(data)
this.guildID = data.guild_id ?? this.guildID
this.name = data.name ?? this.name
this.position = data.position ?? this.position
this.permissionOverwrites =
data.permission_overwrites ?? this.permissionOverwrites
this.nsfw = data.nsfw ?? this.nsfw
this.parentID = data.parent_id ?? this.parentID
}
}

View File

@ -1,9 +1,15 @@
import { Client } from '../models/client.ts'
import { GuildChannel } from './guildChannel.ts'
import { GuildTextChannelPayload } from '../types/channelTypes.ts'
import { GuildTextChannelPayload, Overwrite } from '../types/channelTypes.ts'
import cache from '../models/cache.ts'
import { TextChannel } from './textChannel.ts'
export class GuildTextChannel extends GuildChannel {
export class GuildTextChannel extends TextChannel {
guildID: string
name: string
position: number
permissionOverwrites: Overwrite[]
nsfw: boolean
parentID?: string
rateLimit: number
topic?: string
@ -13,13 +19,26 @@ export class GuildTextChannel extends GuildChannel {
constructor (client: Client, data: GuildTextChannelPayload) {
super(client, data)
this.guildID = data.guild_id
this.name = data.name
this.position = data.position
this.permissionOverwrites = data.permission_overwrites
this.nsfw = data.nsfw
this.parentID = data.parent_id
this.topic = data.topic
this.rateLimit = data.rate_limit_per_user
cache.set('guildtextchannel', this.id, this)
}
readFromData (data: GuildTextChannelPayload): void {
protected readFromData (data: GuildTextChannelPayload): void {
super.readFromData(data)
this.guildID = data.guild_id ?? this.guildID
this.name = data.name ?? this.name
this.position = data.position ?? this.position
this.permissionOverwrites =
data.permission_overwrites ?? this.permissionOverwrites
this.nsfw = data.nsfw ?? this.nsfw
this.parentID = data.parent_id ?? this.parentID
this.topic = data.topic ?? this.topic
this.rateLimit = data.rate_limit_per_user ?? this.rateLimit
}

View File

@ -26,7 +26,7 @@ export class VoiceChannel extends Channel {
cache.set('guildvoicechannel', this.id, this)
}
readFromData (data: GuildVoiceChannelPayload): void {
protected readFromData (data: GuildVoiceChannelPayload): void {
super.readFromData(data)
this.bitrate = data.bitrate ?? this.bitrate
this.userLimit = data.user_limit ?? this.userLimit

View File

@ -1,10 +1,36 @@
import { Client } from '../models/client.ts'
import { Channel } from './channel.ts'
import { GuildNewsChannelPayload } from '../types/channelTypes.ts'
import { GuildNewsChannelPayload, Overwrite } from '../types/channelTypes.ts'
import { TextChannel } from './textChannel.ts'
export class NewsChannel extends TextChannel {
guildID: string
name: string
position: number
permissionOverwrites: Overwrite[]
nsfw: boolean
parentID?: string
topic?: string
export class NewsChannel extends Channel {
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor (client: Client, data: GuildNewsChannelPayload) {
super(client, data)
this.guildID = data.guild_id
this.name = data.name
this.position = data.position
this.permissionOverwrites = data.permission_overwrites
this.nsfw = data.nsfw
this.parentID = data.parent_id
this.topic = data.topic
}
protected readFromData (data: GuildNewsChannelPayload): void {
super.readFromData(data)
this.guildID = data.guild_id ?? this.guildID
this.name = data.name ?? this.name
this.position = data.position ?? this.position
this.permissionOverwrites =
data.permission_overwrites ?? this.permissionOverwrites
this.nsfw = data.nsfw ?? this.nsfw
this.parentID = data.parent_id ?? this.parentID
this.topic = data.topic ?? this.topic
}
}

View File

@ -31,7 +31,7 @@ export class Invite extends Base {
this.approximatePresenceCount = data.approximate_presence_count
}
readFromData (data: InvitePayload): void {
protected readFromData (data: InvitePayload): void {
super.readFromData(data)
this.code = data.code ?? this.code
this.guild = data.guild ?? this.guild

View File

@ -1,9 +1,12 @@
import cache from '../models/cache.ts'
import { Client } from '../models/client.ts'
import { MemberPayload } from '../types/guildTypes.ts'
import { Base } from './base.ts'
import { User } from './user.ts'
export class Member extends User {
export class Member extends Base {
id: string
user: User
nick?: string
roles: string[]
joinedAt: string
@ -12,7 +15,10 @@ export class Member extends User {
mute: boolean
constructor (client: Client, data: MemberPayload) {
super(client, data.user)
super(client)
this.id = data.user.id
this.user =
cache.get('user', data.user.id) ?? new User(this.client, data.user)
this.nick = data.nick
this.roles = data.roles
this.joinedAt = data.joined_at
@ -22,8 +28,8 @@ export class Member extends User {
cache.set('member', this.id, this)
}
readFromData (data: MemberPayload): void {
super.readFromData(data)
protected readFromData (data: MemberPayload): void {
super.readFromData(data.user)
this.nick = data.nick ?? this.nick
this.roles = data.roles ?? this.roles
this.joinedAt = data.joined_at ?? this.joinedAt

View File

@ -76,14 +76,14 @@ export class Message extends Base {
cache.set('message', this.id, this)
}
readFromData (data: MessagePayload): void {
protected readFromData (data: MessagePayload): void {
super.readFromData(data)
this.channelID = data.channel_id ?? this.channelID
this.guildID = data.guild_id ?? this.guildID
this.author =
cache.get('user', data.author.id) ??
new User(this.client, data.author) ??
this.author
this.author ??
new User(this.client, data.author)
this.content = data.content ?? this.content
this.timestamp = data.timestamp ?? this.timestamp
this.editedTimestamp = data.edited_timestamp ?? this.editedTimestamp

View File

@ -30,7 +30,7 @@ export class Role extends Base {
cache.set('role', this.id, this)
}
readFromData (data: RolePayload): void {
protected readFromData (data: RolePayload): void {
super.readFromData(data)
this.name = data.name ?? this.name
this.color = data.color ?? this.color

View File

@ -16,7 +16,7 @@ export class TextChannel extends Channel {
cache.set('textchannel', this.id, this)
}
readFromData (data: TextChannelPayload): void {
protected readFromData (data: TextChannelPayload): void {
super.readFromData(data)
this.lastMessageID = data.last_message_id ?? this.lastMessageID
this.lastPinTimestamp = data.last_pin_timestamp ?? this.lastPinTimestamp

View File

@ -44,7 +44,7 @@ export class User extends Base {
cache.set('user', this.id, this)
}
readFromData (data: UserPayload): void {
protected readFromData (data: UserPayload): void {
super.readFromData(data)
this.username = data.username ?? this.username
this.discriminator = data.discriminator ?? this.discriminator

View File

@ -33,7 +33,7 @@ export class VoiceState extends Base {
cache.set('voiceState', `${this.guildID}:${this.userID}`, this)
}
readFromData (data: VoiceStatePayload): void {
protected readFromData (data: VoiceStatePayload): void {
super.readFromData(data)
this.channelID = data.channel_id ?? this.channelID
this.sessionID = data.session_id ?? this.sessionID

View File

@ -1,10 +1,11 @@
import { Client } from '../models/client.ts'
import { GatewayIntents } from '../types/gatewayTypes.ts'
import { TOKEN } from './config.ts'
import * as cache from '../models/cache.ts'
import { Member } from '../structures/member.ts'
import { Channel } from '../structures/channel.ts'
import { GuildTextChannel } from '../structures/guildTextChannel.ts'
import { TextChannel } from '../structures/textChannel.ts'
import { Guild } from '../structures/guild.ts'
import { User } from '../structures/user.ts'
const bot = new Client()
@ -12,18 +13,66 @@ bot.on('ready', () => {
console.log('READY!')
})
bot.on('channelDelete', (channel: Channel) => {
console.log('channelDelete', channel.id)
})
bot.on('channelUpdate', (before: Channel, after: Channel) => {
if (before instanceof GuildTextChannel && after instanceof GuildTextChannel) {
console.log(before.name)
console.log(after.name)
console.log('channelUpdate', before.name)
console.log('channelUpdate', after.name)
} else {
console.log(before)
console.log(after)
console.log('channelUpdate', before.id)
console.log('channelUpdate', after.id)
}
})
bot.on('channelCreate', (channel: Channel) => {
console.log('channelCreate', channel.id)
})
bot.on('channelPinsUpdate', (before: TextChannel, after: TextChannel) => {
console.log(
'channelPinsUpdate',
before.lastPinTimestamp,
after.lastPinTimestamp
)
})
bot.on('guildBanAdd', (guild: Guild, user: User) => {
console.log('guildBanAdd', guild.id, user.id)
})
bot.on('guildBanRemove', (guild: Guild, user: User) => {
console.log('guildBanRemove', guild.id, user.id)
})
bot.on('guildCreate', (guild: Guild) => {
console.log('guildCreate', guild.id)
})
bot.on('guildDelete', (guild: Guild) => {
console.log('guildDelete', guild.id)
})
bot.on('guildUpdate', (before: Guild, after: Guild) => {
console.log('guildUpdate', before.name, after.name)
})
bot.connect(TOKEN, [
GatewayIntents.GUILD_MEMBERS,
GatewayIntents.GUILD_PRESENCES,
GatewayIntents.GUILD_MESSAGES
GatewayIntents.GUILD_MESSAGES,
GatewayIntents.DIRECT_MESSAGES,
GatewayIntents.DIRECT_MESSAGE_REACTIONS,
GatewayIntents.DIRECT_MESSAGE_TYPING,
GatewayIntents.GUILDS,
GatewayIntents.GUILD_BANS,
GatewayIntents.GUILD_EMOJIS,
GatewayIntents.GUILD_INTEGRATIONS,
GatewayIntents.GUILD_INVITES,
GatewayIntents.GUILD_MESSAGE_REACTIONS,
GatewayIntents.GUILD_MESSAGE_TYPING,
GatewayIntents.GUILD_VOICE_STATES,
GatewayIntents.GUILD_WEBHOOKS
])

View File

@ -3,17 +3,17 @@ import { EmojiPayload } from './emojiTypes.ts'
import { MemberPayload } from './guildTypes.ts'
import { UserPayload } from './userTypes.ts'
interface ChannelPayload {
export interface ChannelPayload {
id: string
type: ChannelTypes
}
interface TextChannelPayload extends ChannelPayload {
export interface TextChannelPayload extends ChannelPayload {
last_message_id?: string
last_pin_timestamp?: string
}
interface GuildChannelPayload extends ChannelPayload {
export interface GuildChannelPayload extends ChannelPayload {
guild_id: string
name: string
position: number
@ -22,46 +22,46 @@ interface GuildChannelPayload extends ChannelPayload {
parent_id?: string
}
interface GuildTextChannelPayload
export interface GuildTextChannelPayload
extends TextChannelPayload,
GuildChannelPayload {
rate_limit_per_user: number
topic?: string
}
interface GuildNewsChannelPayload
export interface GuildNewsChannelPayload
extends TextChannelPayload,
GuildChannelPayload {
topic?: string
}
interface GuildVoiceChannelPayload extends GuildChannelPayload {
export interface GuildVoiceChannelPayload extends GuildChannelPayload {
bitrate: string
user_limit: number
}
interface DMChannelPayload extends TextChannelPayload {
export interface DMChannelPayload extends TextChannelPayload {
recipients: UserPayload[]
}
interface GroupDMChannelPayload extends DMChannelPayload {
export interface GroupDMChannelPayload extends DMChannelPayload {
name: string
icon?: string
owner_id: string
}
interface GuildChannelCategoryPayload
export interface GuildChannelCategoryPayload
extends ChannelPayload,
GuildChannelPayload {}
interface Overwrite {
export interface Overwrite {
id: string
type: number
allow: string
deny: string
}
enum ChannelTypes {
export enum ChannelTypes {
GUILD_TEXT = 0,
DM = 1,
GUILD_VOICE = 2,
@ -71,7 +71,7 @@ enum ChannelTypes {
GUILD_STORE = 6
}
interface MessagePayload {
export interface MessagePayload {
id: string
channel_id: string
guild_id?: string
@ -98,7 +98,7 @@ interface MessagePayload {
flags?: number
}
interface MessageOption {
export interface MessageOption {
tts: boolean
embed: Embed
file: Attachment
@ -109,14 +109,14 @@ interface MessageOption {
}
}
interface ChannelMention {
export interface ChannelMention {
id: string
guild_id: string
type: ChannelTypes
name: string
}
interface Attachment {
export interface Attachment {
id: string
filename: string
size: number
@ -126,7 +126,7 @@ interface Attachment {
width: number | undefined
}
interface EmbedPayload {
export interface EmbedPayload {
title?: string
type?: EmbedTypes
description?: string
@ -142,64 +142,70 @@ interface EmbedPayload {
fields?: EmbedField[]
}
type EmbedTypes = 'rich' | 'image' | 'video' | 'gifv' | 'article' | 'link'
export type EmbedTypes =
| 'rich'
| 'image'
| 'video'
| 'gifv'
| 'article'
| 'link'
interface EmbedField {
export interface EmbedField {
name: string
value: string
inline?: boolean
}
interface EmbedAuthor {
export interface EmbedAuthor {
name?: string
url?: string
icon_url?: string
proxy_icon_url?: string
}
interface EmbedFooter {
export interface EmbedFooter {
text: string
icon_url?: string
proxy_icon_url?: string
}
interface EmbedImage {
export interface EmbedImage {
url?: string
proxy_url?: string
height?: number
width?: number
}
interface EmbedProvider {
export interface EmbedProvider {
name?: string
url?: string
}
interface EmbedVideo {
export interface EmbedVideo {
url?: string
height?: number
width?: number
}
interface EmbedThumbnail {
export interface EmbedThumbnail {
url?: string
proxy_url?: string
height?: number
width?: number
}
interface Reaction {
export interface Reaction {
count: number
me: boolean
emoji: EmojiPayload
}
interface MessageActivity {
export interface MessageActivity {
type: MessageTypes
party_id?: string
}
interface MessageApplication {
export interface MessageApplication {
id: string
cover_image?: string
desription: string
@ -207,13 +213,13 @@ interface MessageApplication {
name: string
}
interface MessageReference {
export interface MessageReference {
message_id?: string
channel_id?: string
guild_id?: string
}
enum MessageTypes {
export enum MessageTypes {
DEFAULT = 0,
RECIPIENT_ADD = 1,
RECIPIENT_REMOVE = 2,
@ -231,14 +237,14 @@ enum MessageTypes {
GUILD_DISCOVERY_REQUALIFIED = 15
}
enum MessageActivityTypes {
export enum MessageActivityTypes {
JOIN = 1,
SPECTATE = 2,
LISTEN = 3,
JOIN_REQUEST = 4
}
enum MessageFlags {
export enum MessageFlags {
CROSSPOSTED = 1 << 0,
IS_CROSSPOST = 1 << 1,
SUPPRESS_EMBEDS = 1 << 2,
@ -246,54 +252,7 @@ enum MessageFlags {
URGENT = 1 << 4
}
interface FollowedChannel {
export interface FollowedChannel {
channel_id: string
webhook_id: string
}
interface Overwrite {
id: string
type: number
allow: string
deny: string
}
interface ChannelMention {
id: string
guild_id: string
type: ChannelTypes
name: string
}
export {
ChannelPayload,
TextChannelPayload,
GuildChannelPayload,
GuildNewsChannelPayload,
GuildTextChannelPayload,
GuildVoiceChannelPayload,
GuildChannelCategoryPayload,
DMChannelPayload,
GroupDMChannelPayload,
Overwrite,
ChannelTypes,
ChannelMention,
Attachment,
Reaction,
MessageActivity,
MessageActivityTypes,
MessageFlags,
FollowedChannel,
MessageApplication,
MessageReference,
MessagePayload,
MessageOption,
EmbedPayload,
EmbedTypes,
EmbedFooter,
EmbedImage,
EmbedThumbnail,
EmbedVideo,
EmbedProvider,
EmbedAuthor,
EmbedField
}

View File

@ -4,11 +4,9 @@ import { GatewayOpcodes, GatewayEvents } from '../types/gatewayTypes.ts'
* Gateway response from Discord.
*
*/
interface GatewayResponse {
export interface GatewayResponse {
op: GatewayOpcodes
d: any
s?: number
t?: GatewayEvents
}
export { GatewayResponse }

View File

@ -62,11 +62,9 @@ enum GatewayIntents {
}
enum GatewayEvents {
Hello = 'HELLO',
Ready = 'READY',
Resumed = 'RESUMED',
Reconnect = 'RECONNECT',
Invalid_Session = 'INVALID_SESSION',
Channel_Create = 'CHANNEL_CREATE',
Channel_Update = 'CHANNEL_UPDATE',
Channel_Delete = 'CHANNEL_DELETE',
@ -89,7 +87,7 @@ enum GatewayEvents {
Invite_Delete = 'INVITE_DELETE',
Message_Create = 'MESSAGE_CREATE',
Message_Update = 'MESSAGE_UPDATE',
Message_Delete = 'MESSAG_DELETE',
Message_Delete = 'MESSAGE_DELETE',
Message_Delete_Bulk = 'MESSAGE_DELETE_BULK',
Message_Reaction_Add = 'MESSAGE_REACTION_ADD',
Message_Reaction_Remove = 'MESSAGE_REACTION_REMOVE',
@ -98,7 +96,6 @@ enum GatewayEvents {
Presence_Update = 'PRESENCE_UPDATE',
Typing_Start = 'TYPING_START',
User_Update = 'USER_UPDATE',
Voice_State_Update = 'VOICE_STATE_UPDATE',
Voice_Server_Update = 'VOICE_SERVER_UPDATE',
Webhooks_Update = 'WEBHOOKS_UPDATE'
}
@ -395,5 +392,5 @@ interface WebhooksUpdate {
channel_id: string
}
//https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields
// https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields
export { GatewayCloseCodes, GatewayOpcodes, GatewayIntents, GatewayEvents }

View File

@ -5,7 +5,7 @@ import { RolePayload } from './roleTypes.ts'
import { UserPayload } from './userTypes.ts'
import { VoiceStatePayload } from './voiceTypes.ts'
interface GuildPayload {
export interface GuildPayload {
id: string
name: string
icon?: string
@ -53,7 +53,7 @@ interface GuildPayload {
approximate_presence_count?: number
}
interface MemberPayload {
export interface MemberPayload {
user: UserPayload
nick?: string
roles: string[]
@ -99,7 +99,7 @@ enum SystemChannelFlags {
SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1
}
type GuildFeatures =
export type GuildFeatures =
| 'INVITE_SPLASH'
| 'VIP_REGIONS'
| 'VANITY_URL'
@ -112,5 +112,3 @@ type GuildFeatures =
| 'FEATURABLE'
| 'ANIMATED_ICON'
| 'BANNER'
export { MemberPayload, GuildPayload, GuildFeatures }

View File

@ -1,6 +1,6 @@
import { UserPayload } from './userTypes.ts'
interface PresenceUpdatePayload {
export interface PresenceUpdatePayload {
user: UserPayload
guild_id: string
status: string
@ -14,7 +14,7 @@ interface ClientStatus {
web?: string
}
interface ActivityPayload {
export interface ActivityPayload {
name: string
type: 0 | 1 | 2 | 3 | 4 | 5
url?: string | undefined
@ -68,5 +68,3 @@ enum ActivityFlags {
SYNC = 1 << 4,
PLAY = 1 << 5
}
export { ActivityPayload, PresenceUpdatePayload }

View File

@ -0,0 +1,53 @@
import { Client } from '../models/client.ts'
import {
ChannelPayload,
ChannelTypes,
DMChannelPayload,
GroupDMChannelPayload,
GuildChannelCategoryPayload,
GuildNewsChannelPayload,
GuildTextChannelPayload,
GuildVoiceChannelPayload
} from '../types/channelTypes.ts'
import { DMChannel } from '../structures/dmChannel.ts'
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 { TextChannel } from '../structures/textChannel.ts'
const getChannelByType = (
client: Client,
data:
| GuildChannelCategoryPayload
| GuildNewsChannelPayload
| GuildTextChannelPayload
| GuildVoiceChannelPayload
| DMChannelPayload
| GroupDMChannelPayload
| ChannelPayload
):
| CategoryChannel
| NewsChannel
| TextChannel
| VoiceChannel
| DMChannel
| GroupDMChannel
| undefined => {
switch (data.type) {
case ChannelTypes.GUILD_CATEGORY:
return new CategoryChannel(client, data as GuildChannelCategoryPayload)
case ChannelTypes.GUILD_NEWS:
return new NewsChannel(client, data as GuildNewsChannelPayload)
case ChannelTypes.GUILD_TEXT:
return new TextChannel(client, data as GuildTextChannelPayload)
case ChannelTypes.GUILD_VOICE:
return new VoiceChannel(client, data as GuildVoiceChannelPayload)
case ChannelTypes.DM:
return new DMChannel(client, data as DMChannelPayload)
case ChannelTypes.GROUP_DM:
return new GroupDMChannel(client, data as GroupDMChannelPayload)
}
}
export default getChannelByType

3
src/utils/index.ts Normal file
View File

@ -0,0 +1,3 @@
import getChannelByType from './getChannelByType.ts'
export default { getChannelByType }

View File

@ -25,7 +25,7 @@
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
"isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
/* Strict Type-Checking Options */
"strict": true,
/* Enable all strict type-checking options. */
@ -61,7 +61,7 @@
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"emitDecoratorMetadata": false /* Enables experimental support for emitting type metadata for decorators. */,
/* Advanced Options */
"skipLibCheck": true,
/* Skip type checking of declaration files. */