Skip to content

Commit

Permalink
feat: guild member structure and references (#52)
Browse files Browse the repository at this point in the history
* feat: guild member structure and references

* add changeset

* chore: formatting

---------

Co-authored-by: Shadow <hi@shadowing.dev>
  • Loading branch information
Codeize and thewilloftheshadow committed Sep 2, 2024
1 parent 7e2549f commit 8438b8d
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-ads-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@buape/carbon": patch
---

Add GuildMember structure
7 changes: 7 additions & 0 deletions packages/carbon/src/abstracts/BaseInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Client } from "../classes/Client.js"
import type { Row } from "../classes/Row.js"
import { channelFactory } from "../factories/channelFactory.js"
import { Guild } from "../structures/Guild.js"
import { GuildMember } from "../structures/GuildMember.js"
import { Message } from "../structures/Message.js"
import { User } from "../structures/User.js"
import { Base } from "./Base.js"
Expand Down Expand Up @@ -109,6 +110,12 @@ export abstract class BaseInteraction<T extends APIInteraction> extends Base {
return channelFactory(this.client, this.rawData.channel as APIChannel)
}

get member() {
if (!this.rawData.member) return null
if (!this.guild) return null
return new GuildMember(this.client, this.rawData.member, this.guild)
}

/**
* Reply to an interaction.
* If the interaction is deferred, this will edit the original response.
Expand Down
2 changes: 1 addition & 1 deletion packages/carbon/src/classes/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
ApplicationCommandType
} from "discord-api-types/v10"
import { BaseCommand } from "../abstracts/BaseCommand.js"
import type { CommandInteraction } from "../internals/CommandInteraction.js"
import type { AutocompleteInteraction } from "../internals/AutocompleteInteraction.js"
import type { CommandInteraction } from "../internals/CommandInteraction.js"

export type CommandOptions = APIApplicationCommandBasicOption[]

Expand Down
10 changes: 5 additions & 5 deletions packages/carbon/src/internals/AutocompleteInteraction.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { BaseCommand } from "../abstracts/BaseCommand.js"
import { BaseInteraction } from "../abstracts/BaseInteraction.js"
import {
type APIApplicationCommandAutocompleteInteraction,
type APIApplicationCommandInteractionDataBasicOption,
ApplicationCommandOptionType,
ApplicationCommandType,
InteractionResponseType,
InteractionType,
type APIApplicationCommandAutocompleteInteraction,
Routes,
InteractionResponseType
Routes
} from "discord-api-types/v10"
import type { BaseCommand } from "../abstracts/BaseCommand.js"
import { BaseInteraction } from "../abstracts/BaseInteraction.js"
import type { Client } from "../classes/Client.js"
import { Command } from "../classes/Command.js"
import { OptionsHandler } from "./OptionsHandler.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/carbon/src/internals/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { Base } from "../abstracts/Base.js"
import { Command } from "../classes/Command.js"
import { CommandWithSubcommandGroups } from "../classes/CommandWithSubcommandGroups.js"
import { CommandWithSubcommands } from "../classes/CommandWithSubcommands.js"
import { CommandInteraction } from "./CommandInteraction.js"
import { AutocompleteInteraction } from "./AutocompleteInteraction.js"
import { CommandInteraction } from "./CommandInteraction.js"

export class CommandHandler extends Base {
private getCommand(
Expand Down
12 changes: 12 additions & 0 deletions packages/carbon/src/structures/Guild.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {
type APIChannel,
type APIGuild,
type APIGuildMember,
type APIRole,
type RESTPostAPIGuildRoleJSONBody,
Routes
} from "discord-api-types/v10"
import { Base } from "../abstracts/Base.js"
import type { Client } from "../classes/Client.js"
import { channelFactory } from "../factories/channelFactory.js"
import { GuildMember } from "./GuildMember.js"
import { Role } from "./Role.js"

export class Guild extends Base {
Expand Down Expand Up @@ -103,6 +105,16 @@ export class Guild extends Base {
return new Role(this.client, role)
}

/**
* Get a member in the guild by ID
*/
async fetchMember(memberId: string) {
const member = (await this.client.rest.get(
Routes.guildMember(this.id, memberId)
)) as APIGuildMember
return new GuildMember(this.client, member, this)
}

/**
* Get the URL of the guild's icon
*/
Expand Down
231 changes: 231 additions & 0 deletions packages/carbon/src/structures/GuildMember.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import type { APIGuildMember, GuildMemberFlags } from "discord-api-types/v10"
import { Base } from "../abstracts/Base.js"
import type { Client } from "../classes/Client.js"
import type { Guild } from "./Guild.js"
import { Role } from "./Role.js"
import { User } from "./User.js"

export class GuildMember extends Base {
/**
* The guild-specific nickname of the member.
*/
nickname?: string | null
/**
* The guild-specific avatar hash of the member.
* You can use {@link GuildMember.avatarUrl} to get the URL of the avatar.
*/
avatar?: string | null
/**
* Is this member muted in Voice Channels?
*/
mute?: boolean | null
/**
* Is this member deafened in Voice Channels?
*/
deaf?: boolean | null
/**
* The date since this member boosted the guild, if applicable.
*/
premiumSince?: string | null
/**
* The flags of the member.
* @see https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-flags
*/
flags?: GuildMemberFlags | null
/**
* The roles of the member
*/
roles?: Role[] | null
/**
* The joined date of the member
*/
joinedAt?: string | null
/**
* The date when the member's communication privileges (timeout) will be reinstated
*/
communicationDisabledUntil?: string | null
/**
* Is this member yet to pass the guild's Membership Screening requirements?
*/
pending?: boolean | null
/**
* The guild object of the member
*/
guild: Guild
/**
* The user object of the member
*/
user?: User | null

private rawData: APIGuildMember | null = null

constructor(client: Client, rawData: APIGuildMember, guild: Guild) {
super(client)
this.rawData = rawData
this.guild = guild
this.setData(rawData)
}

private setData(data: typeof this.rawData) {
if (!data) throw new Error("Cannot set data without having data... smh")
this.rawData = data
this.nickname = data.nick
this.avatar = data.avatar
this.mute = data.mute
this.deaf = data.deaf
this.premiumSince = data.premium_since
this.flags = data.flags
this.roles = data.roles?.map((roleId) => new Role(this.client, roleId))
this.joinedAt = data.joined_at
this.communicationDisabledUntil = data.communication_disabled_until
this.pending = data.pending
this.user = data.user ? new User(this.client, data.user) : null
}

/**
* Set the nickname of the member
*/
async setNickname(nickname: string | null): Promise<void> {
await this.client.rest.patch(
`/guilds/${this.guild?.id}/members/${this.user?.id}`,
{
body: {
nick: nickname
}
}
)
this.nickname = nickname
}

/**
* Add a role to the member
*/
async addRole(roleId: string): Promise<void> {
await this.client.rest.put(
`/guilds/${this.guild?.id}/members/${this.user?.id}/roles/${roleId}`,
{}
)
this.roles?.push(new Role(this.client, roleId))
}

/**
* Remove a role from the member
*/
async removeRole(roleId: string): Promise<void> {
await this.client.rest.delete(
`/guilds/${this.guild?.id}/members/${this.user?.id}/roles/${roleId}`
)
this.roles = this.roles?.filter((role) => role.id !== roleId)
}

/**
* Kick the member from the guild
*/
async kick(): Promise<void> {
await this.client.rest.delete(
`/guilds/${this.guild?.id}/members/${this.user?.id}`
)
}

/**
* Ban the member from the guild
*/
async ban(
options: { reason?: string; deleteMessageDays?: number } = {}
): Promise<void> {
await this.client.rest.put(
`/guilds/${this.guild?.id}/bans/${this.user?.id}`,
{
body: {
reason: options.reason,
delete_message_days: options.deleteMessageDays
}
}
)
}

/**
* Mute a member in voice channels
*/
async muteMember(): Promise<void> {
await this.client.rest.patch(
`/guilds/${this.guild?.id}/members/${this.user?.id}`,
{
body: {
mute: true
}
}
)
this.mute = true
}

/**
* Unmute a member in voice channels
*/
async unmuteMember(): Promise<void> {
await this.client.rest.patch(
`/guilds/${this.guild?.id}/members/${this.user?.id}`,
{
body: {
mute: false
}
}
)
this.mute = false
}

/**
* Deafen a member in voice channels
*/
async deafenMember(): Promise<void> {
await this.client.rest.patch(
`/guilds/${this.guild?.id}/members/${this.user?.id}`,
{
body: {
deaf: true
}
}
)
this.deaf = true
}

/**
* Undeafen a member in voice channels
*/
async undeafenMember(): Promise<void> {
await this.client.rest.patch(
`/guilds/${this.guild?.id}/members/${this.user?.id}`,
{
body: {
deaf: false
}
}
)
this.deaf = false
}

/**
* Set or remove a timeout for a member in the guild
*/
async timeoutMember(communicationDisabledUntil: string): Promise<void> {
await this.client.rest.patch(
`/guilds/${this.guild?.id}/members/${this.user?.id}`,
{
body: {
communication_disabled_until: communicationDisabledUntil
}
}
)
this.communicationDisabledUntil = communicationDisabledUntil
}

/**
* Get the URL of the member's guild-specific avatar
*/
get avatarUrl(): string | null {
if (!this.user) return null
return this.avatar
? `https://cdn.discordapp.com/guilds/${this.guild.id}/users/${this.user?.id}/${this.avatar}.png`
: null
}
}
1 change: 0 additions & 1 deletion website/app/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export default async function Page({

return (
<DocsLayout

tree={loader.pageTree}
githubUrl="https://github.com/buape/carbon"
nav={{
Expand Down

0 comments on commit 8438b8d

Please sign in to comment.