diff --git a/.eslintrc.js b/.eslintrc.js index 200257b..d9d64f0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { }, plugins: ['@typescript-eslint'], rules: { - '@typescript-eslint/restrict-template-expressions': 'off' + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/no-non-null-assertion': 'off' } } diff --git a/deps.ts b/deps.ts index 4b47aa2..43fc90c 100644 --- a/deps.ts +++ b/deps.ts @@ -1,7 +1,6 @@ export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts' export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts' export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts' -export { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts' export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts' export { join } from 'https://deno.land/std@0.86.0/path/mod.ts' export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0' diff --git a/mod.ts b/mod.ts index b99a120..70faacb 100644 --- a/mod.ts +++ b/mod.ts @@ -52,7 +52,8 @@ export { UsersManager } from './src/managers/users.ts' export { InviteManager } from './src/managers/invites.ts' export { Application } from './src/structures/application.ts' // export { ImageURL } from './src/structures/cdn.ts' -export { Channel } from './src/structures/channel.ts' +export { Channel, GuildChannel } from './src/structures/channel.ts' +export type { EditOverwriteOptions } from './src/structures/channel.ts' export { DMChannel } from './src/structures/dmChannel.ts' export { Embed } from './src/structures/embed.ts' export { Emoji } from './src/structures/emoji.ts' @@ -167,3 +168,5 @@ export type { WebhookPayload } from './src/types/webhook.ts' export * from './src/models/collectors.ts' export type { Dict } from './src/utils/dict.ts' export * from './src/models/redisCache.ts' +export { ColorUtil } from './src/utils/colorutil.ts' +export type { Colors } from './src/utils/colorutil.ts' diff --git a/src/managers/guildChannelVoiceStates.ts b/src/managers/guildChannelVoiceStates.ts new file mode 100644 index 0000000..2dc9ac5 --- /dev/null +++ b/src/managers/guildChannelVoiceStates.ts @@ -0,0 +1,39 @@ +import { Client } from '../models/client.ts' +import { BaseChildManager } from './baseChild.ts' +import { VoiceStatePayload } from '../types/voice.ts' +import { VoiceState } from '../structures/voiceState.ts' +import { GuildVoiceStatesManager } from './guildVoiceStates.ts' +import { VoiceChannel } from '../structures/guildVoiceChannel.ts' + +export class GuildChannelVoiceStatesManager extends BaseChildManager< + VoiceStatePayload, + VoiceState +> { + channel: VoiceChannel + + constructor( + client: Client, + parent: GuildVoiceStatesManager, + channel: VoiceChannel + ) { + super(client, parent as any) + this.channel = channel + } + + async get(id: string): Promise { + const res = await this.parent.get(id) + if (res !== undefined && res.channel?.id === this.channel.id) return res + else return undefined + } + + async array(): Promise { + const arr = (await this.parent.array()) as VoiceState[] + return arr.filter((c: any) => c.channel?.id === this.channel.id) as any + } + + async fromPayload(d: VoiceStatePayload[]): Promise { + for (const data of d) { + await this.set(data.user_id, data) + } + } +} diff --git a/src/managers/guildVoiceStates.ts b/src/managers/guildVoiceStates.ts index 4b1c5bd..baefcf7 100644 --- a/src/managers/guildVoiceStates.ts +++ b/src/managers/guildVoiceStates.ts @@ -30,7 +30,7 @@ export class GuildVoiceStatesManager extends BaseManager< const guild = raw.guild_id === undefined - ? undefined + ? this.guild : await this.client.guilds.get(raw.guild_id) return new VoiceState(this.client, raw, { @@ -57,7 +57,7 @@ export class GuildVoiceStatesManager extends BaseManager< arr.map(async (raw) => { const guild = raw.guild_id === undefined - ? undefined + ? this.guild : await this.client.guilds.get(raw.guild_id) return new VoiceState(this.client, raw, { diff --git a/src/models/command.ts b/src/models/command.ts index 4040449..ee24145 100644 --- a/src/models/command.ts +++ b/src/models/command.ts @@ -5,7 +5,7 @@ import { User } from '../structures/user.ts' import { Collection } from '../utils/collection.ts' import { CommandClient } from './commandClient.ts' import { Extension } from './extensions.ts' -import { join, parse, walk } from '../../deps.ts' +import { join, walk } from '../../deps.ts' export interface CommandContext { /** The Client object */ @@ -599,7 +599,7 @@ export const parseCommand = ( ): ParsedCommand | undefined => { let content = msg.content.slice(prefix.length) if (client.spacesAfterPrefix === true) content = content.trim() - const args = parse(content)._.map((e) => e.toString()) + const args = content.split(' ') const name = args.shift() if (name === undefined) return diff --git a/src/structures/channel.ts b/src/structures/channel.ts index 65caf4d..9aad184 100644 --- a/src/structures/channel.ts +++ b/src/structures/channel.ts @@ -38,6 +38,13 @@ export class Channel extends SnowflakeBase { } } +export interface EditOverwriteOptions { + /** Allow Override Type */ + allow?: OverrideType + /** Deny Override Type */ + deny?: OverrideType +} + export class GuildChannel extends Channel { guildID: string name: string @@ -74,7 +81,7 @@ export class GuildChannel extends Channel { const stringToObject = typeof target === 'string' ? (await this.guild.members.get(target)) ?? - (await this.guild.roles.get(target)) + (await this.guild.roles.get(target)) : target if (stringToObject === undefined) { @@ -121,7 +128,7 @@ export class GuildChannel extends Channel { const stringToObject = typeof target === 'string' ? (await this.guild.members.get(target)) ?? - (await this.guild.roles.get(target)) + (await this.guild.roles.get(target)) : target if (stringToObject === undefined) { @@ -193,8 +200,8 @@ export class GuildChannel extends Channel { overwrite.id instanceof Role ? 0 : overwrite.id instanceof Member - ? 1 - : overwrite.type + ? 1 + : overwrite.type if (type === undefined) { throw new Error('Overwrite type is undefined.') } @@ -226,8 +233,8 @@ export class GuildChannel extends Channel { overwrite.id instanceof Role ? 0 : overwrite.id instanceof Member - ? 1 - : overwrite.type + ? 1 + : overwrite.type if (type === undefined) { throw new Error('Overwrite type is undefined.') } @@ -257,12 +264,9 @@ export class GuildChannel extends Channel { async editOverwrite( overwrite: OverwriteAsArg, { - overriteAllow = OverrideType.ADD, - overriteDeny = OverrideType.ADD - }: { - overriteAllow?: OverrideType - overriteDeny?: OverrideType - } + allow: overwriteAllow = OverrideType.ADD, + deny: overwriteDeny = OverrideType.ADD + }: EditOverwriteOptions ): Promise { const id = typeof overwrite.id === 'string' ? overwrite.id : overwrite.id.id const index = this.permissionOverwrites.findIndex((e) => e.id === id) @@ -274,9 +278,9 @@ export class GuildChannel extends Channel { if ( overwrite.allow !== undefined && - overriteAllow !== OverrideType.REPLACE + overwriteAllow !== OverrideType.REPLACE ) { - switch (overriteAllow) { + switch (overwriteAllow) { case OverrideType.ADD: { const originalAllow = new Permissions(overwrites[index].allow) const newAllow = new Permissions(overwrite.allow) @@ -299,8 +303,8 @@ export class GuildChannel extends Channel { : overwrite.allow?.toJSON() ?? overwrites[index].allow } - if (overwrite.deny !== undefined && overriteDeny !== OverrideType.REPLACE) { - switch (overriteDeny) { + if (overwrite.deny !== undefined && overwriteDeny !== OverrideType.REPLACE) { + switch (overwriteDeny) { case OverrideType.ADD: { const originalDeny = new Permissions(overwrites[index].deny) const newDeny = new Permissions(overwrite.deny) @@ -327,8 +331,8 @@ export class GuildChannel extends Channel { overwrite.id instanceof Role ? 0 : overwrite.id instanceof Member - ? 1 - : overwrite.type + ? 1 + : overwrite.type if (type === undefined) { throw new Error('Overwrite type is undefined.') } diff --git a/src/structures/guildVoiceChannel.ts b/src/structures/guildVoiceChannel.ts index d1dc287..759fb71 100644 --- a/src/structures/guildVoiceChannel.ts +++ b/src/structures/guildVoiceChannel.ts @@ -10,6 +10,9 @@ import { CHANNEL } from '../types/endpoint.ts' import { GuildChannel } from './channel.ts' import { Guild } from './guild.ts' import { VoiceState } from './voiceState.ts' +import { GuildChannelVoiceStatesManager } from '../managers/guildChannelVoiceStates.ts' +import { User } from './user.ts' +import { Member } from './member.ts' export interface VoiceServerData extends VoiceServerUpdateData { sessionID: string @@ -18,6 +21,11 @@ export interface VoiceServerData extends VoiceServerUpdateData { export class VoiceChannel extends GuildChannel { bitrate: string userLimit: number + voiceStates = new GuildChannelVoiceStatesManager( + this.client, + this.guild.voiceStates, + this + ) constructor(client: Client, data: GuildVoiceChannelPayload, guild: Guild) { super(client, data, guild) @@ -107,4 +115,25 @@ export class VoiceChannel extends GuildChannel { return new VoiceChannel(this.client, resp, this.guild) } + + async disconnectMember( + member: User | Member | string + ): Promise { + const memberID = typeof member === 'string' ? member : member.id + const memberVoiceState = await this.voiceStates.get(memberID) + + return memberVoiceState?.disconnect() + } + + async disconnectAll(): Promise { + const members: Member[] = [] + for await (const memberVoiceState of this.voiceStates) { + const member = await memberVoiceState.disconnect() + if (member !== undefined) { + members.push(member) + } + } + + return members + } } diff --git a/src/structures/member.ts b/src/structures/member.ts index fe491d4..eb2593a 100644 --- a/src/structures/member.ts +++ b/src/structures/member.ts @@ -5,6 +5,7 @@ import { MemberPayload } from '../types/guild.ts' import { Permissions } from '../utils/permissions.ts' import { SnowflakeBase } from './base.ts' import { Guild } from './guild.ts' +import { VoiceChannel } from './guildVoiceChannel.ts' import { Role } from './role.ts' import { User } from './user.ts' @@ -13,6 +14,7 @@ export interface MemberData { roles?: Array deaf?: boolean mute?: boolean + channel?: string | VoiceChannel | null } export class Member extends SnowflakeBase { @@ -84,8 +86,11 @@ export class Member extends SnowflakeBase { nick: data.nick, roles: data.roles?.map((e) => (typeof e === 'string' ? e : e.id)), deaf: data.deaf, - mute: data.mute + mute: data.mute, + channel_id: + data.channel instanceof VoiceChannel ? data.channel.id : data.channel } + const res = await this.client.rest.patch( GUILD_MEMBER(this.guild.id, this.id), payload, @@ -125,7 +130,7 @@ export class Member extends SnowflakeBase { */ async setMute(mute?: boolean): Promise { return await this.edit({ - mute: mute === undefined ? false : mute + mute: mute ?? false }) } @@ -135,7 +140,28 @@ export class Member extends SnowflakeBase { */ async setDeaf(deaf?: boolean): Promise { return await this.edit({ - deaf: deaf === undefined ? false : deaf + deaf: deaf ?? false + }) + } + + /** + * Moves a Member to another VC + * @param channel Channel to move(null or undefined to disconnect) + */ + async moveVoiceChannel( + channel?: string | VoiceChannel | null + ): Promise { + return await this.edit({ + channel: channel ?? null + }) + } + + /** + * Disconnects a Member from connected VC + */ + async disconnectVoice(): Promise { + return await this.edit({ + channel: null }) } @@ -146,6 +172,13 @@ export class Member extends SnowflakeBase { return await this.setMute(false) } + /** + * Undeafs the Member from VC. + */ + async undeaf(): Promise { + return await this.setDeaf(false) + } + /** * Kicks the member. */ diff --git a/src/structures/voiceState.ts b/src/structures/voiceState.ts index cd3cc7f..45c2a4b 100644 --- a/src/structures/voiceState.ts +++ b/src/structures/voiceState.ts @@ -1,4 +1,5 @@ import { Client } from '../models/client.ts' +import { ChannelTypes } from '../types/channel.ts' import { VoiceStatePayload } from '../types/voice.ts' import { Base } from './base.ts' import { Guild } from './guild.ts' @@ -52,12 +53,81 @@ export class VoiceState extends Base { this.deaf = data.deaf ?? this.deaf this.channelID = data.channel_id ?? this.channelID this.mute = data.mute ?? this.mute - this.deaf = data.self_deaf ?? this.deaf - this.mute = data.self_mute ?? this.mute this.selfDeaf = data.self_deaf ?? this.selfDeaf this.selfMute = data.self_mute ?? this.selfMute this.stream = data.self_stream ?? this.stream this.video = data.self_video ?? this.video this.suppress = data.suppress ?? this.suppress } + + /** + * Disconnects a Member from connected VC + */ + async disconnect(): Promise { + const result = this.member?.disconnectVoice() + if (result !== undefined) { + this.channelID = null + this.channel = null + } + return result + } + + /** + * Moves a Member to another VC + * @param channel Channel to move(null or undefined to disconnect) + */ + async moveChannel( + channel?: string | VoiceChannel | null + ): Promise { + const result = this.member?.moveVoiceChannel(channel) + if (result !== undefined) { + let channelFetched: VoiceChannel | null + let channelID: string | null + if (typeof channel === 'string') { + channelID = channel + const channelCached = await this.guild?.channels.fetch(channel) + if (channelCached?.type === ChannelTypes.GUILD_VOICE) { + channelFetched = channelCached as VoiceChannel + } else { + throw new Error(`Channel ${channel} is not a VoiceChannel.`) + } + } else { + channelID = channel?.id ?? null + channelFetched = channel ?? null + } + this.channelID = channelID + this.channel = channelFetched + } + return result + } + + /** + * Sets a Member mute in VC + * @param mute Value to set + */ + async setMute(mute?: boolean): Promise { + return this.member?.setMute(mute) + } + + /** + * Sets a Member deaf in VC + * @param deaf Value to set + */ + async setDeaf(deaf?: boolean): Promise { + return this.member?.setDeaf(deaf) + } + + /** + * Unmutes the Member from VC. + */ + async unmute(): Promise { + return await this.setMute(false) + } + + /** + * Undeafs the Member from VC. + */ + async undeaf(): Promise { + return await this.setDeaf(false) + } } diff --git a/src/test/cmd.ts b/src/test/cmd.ts index 5063198..4798c36 100644 --- a/src/test/cmd.ts +++ b/src/test/cmd.ts @@ -139,7 +139,7 @@ client.on('messageReactionRemoveAll', (message) => { // client.on('raw', (evt: string) => console.log(`EVENT: ${evt}`)) -const files = Deno.readDirSync('./src/test/cmds') +const files = Deno.readDirSync('cmds') for (const file of files) { const module = await import(`./cmds/${file.name}`) diff --git a/src/test/cmds/kickFromSpecificVoice.ts b/src/test/cmds/kickFromSpecificVoice.ts new file mode 100644 index 0000000..f60089b --- /dev/null +++ b/src/test/cmds/kickFromSpecificVoice.ts @@ -0,0 +1,23 @@ +import { Command, VoiceChannel } from '../../../mod.ts' +import { CommandContext } from '../../models/command.ts' +import { ChannelTypes } from '../../types/channel.ts' + +export default class KickFromSpecificVoiceCommand extends Command { + name = 'kickFromSpecificVoice' + + async execute(ctx: CommandContext): Promise { + if (ctx.guild !== undefined) { + const channel = await ctx.guild.channels.get('YOUR VOICE CHANNEL ID') + if (channel === undefined || channel.type !== ChannelTypes.GUILD_VOICE) { + ctx.channel.send('The channel is either not a voice or not available.') + return + } + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const members = await (channel as VoiceChannel).disconnectAll() + members.forEach((member) => { + ctx.channel.send(`Kicked member ${member.id}`) + }) + } + } +} diff --git a/src/test/cmds/kickFromVoice.ts b/src/test/cmds/kickFromVoice.ts new file mode 100644 index 0000000..5730703 --- /dev/null +++ b/src/test/cmds/kickFromVoice.ts @@ -0,0 +1,20 @@ +import { Command } from '../../../mod.ts' +import { CommandContext } from '../../models/command.ts' + +export default class KickFromVoiceCommand extends Command { + name = 'kickFromVoice' + + async execute(ctx: CommandContext): Promise { + if (ctx.guild !== undefined) { + const voiceStates = await ctx.guild.voiceStates.array() + if (voiceStates !== undefined) { + voiceStates.forEach(async (voiceState) => { + const member = await voiceState.disconnect() + if (member !== undefined) { + ctx.channel.send(`Kicked member ${member.id}`) + } + }) + } + } + } +} diff --git a/src/test/cmds/ping.ts b/src/test/cmds/ping.ts index 61981ae..3f0c381 100644 --- a/src/test/cmds/ping.ts +++ b/src/test/cmds/ping.ts @@ -5,6 +5,7 @@ export default class PingCommand extends Command { name = 'ping' execute(ctx: CommandContext): void { + console.log(ctx.args, ctx.argString) ctx.message.reply(`Pong! Latency: ${ctx.client.ping}ms`) } } diff --git a/src/test/index.ts b/src/test/index.ts index d60429f..379c695 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -74,12 +74,12 @@ client.on('messageCreate', async (msg: Message) => { const guilds = await msg.client.guilds.collection() msg.channel.send( 'Guild List:\n' + - (guilds - .array() - .map((c: Guild, i: number) => { - return `${i + 1}. ${c.name} - ${c.memberCount} members` - }) - .join('\n') as string) + (guilds + .array() + .map((c: Guild, i: number) => { + return `${i + 1}. ${c.name} - ${c.memberCount} members` + }) + .join('\n') as string) ) } else if (msg.content === '!roles') { const col = await msg.guild?.roles.collection() @@ -236,7 +236,7 @@ client.on('messageCreate', async (msg: Message) => { allow: Permissions.DEFAULT.toString() }, { - overriteAllow: OverrideType.REMOVE + allow: OverrideType.REMOVE } ) msg.channel.send(`Done!`) diff --git a/src/types/channel.ts b/src/types/channel.ts index 40f4748..2787830 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -29,7 +29,7 @@ export interface GuildChannelPayload extends ChannelPayload { export interface GuildTextBasedChannelPayload extends TextChannelPayload, - GuildChannelPayload { + GuildChannelPayload { topic?: string } @@ -37,7 +37,7 @@ export interface GuildTextChannelPayload extends GuildTextBasedChannelPayload { rate_limit_per_user: number } -export interface GuildNewsChannelPayload extends GuildTextBasedChannelPayload {} +export interface GuildNewsChannelPayload extends GuildTextBasedChannelPayload { } export interface GuildVoiceChannelPayload extends GuildChannelPayload { bitrate: string @@ -56,7 +56,7 @@ export interface GroupDMChannelPayload extends DMChannelPayload { export interface GuildCategoryChannelPayload extends ChannelPayload, - GuildChannelPayload {} + GuildChannelPayload { } export interface ModifyChannelPayload { name?: string @@ -67,7 +67,7 @@ export interface ModifyChannelPayload { } export interface ModifyGuildCategoryChannelPayload - extends ModifyChannelPayload {} + extends ModifyChannelPayload { } export interface ModifyGuildTextBasedChannelPayload extends ModifyChannelPayload { @@ -81,7 +81,7 @@ export interface ModifyGuildTextChannelPayload } export interface ModifyGuildNewsChannelPayload - extends ModifyGuildTextBasedChannelPayload {} + extends ModifyGuildTextBasedChannelPayload { } export interface ModifyVoiceChannelPayload extends ModifyChannelPayload { bitrate?: number | null @@ -96,7 +96,7 @@ export interface ModifyChannelOption { nsfw?: boolean | null } -export interface ModifyGuildCategoryChannelOption extends ModifyChannelOption {} +export interface ModifyGuildCategoryChannelOption extends ModifyChannelOption { } export interface ModifyGuildTextBasedChannelOption extends ModifyChannelOption { type?: number @@ -109,7 +109,7 @@ export interface ModifyGuildTextChannelOption } export interface ModifyGuildNewsChannelOption - extends ModifyGuildTextBasedChannelOption {} + extends ModifyGuildTextBasedChannelOption { } export interface ModifyVoiceChannelOption extends ModifyChannelOption { bitrate?: number | null