diff --git a/packages/biscuit/Actions.ts b/packages/biscuit/Actions.ts index 7c109c1..0b7a170 100644 --- a/packages/biscuit/Actions.ts +++ b/packages/biscuit/Actions.ts @@ -1,4 +1,6 @@ import type { + DiscordAutoModerationActionExecution, + DiscordAutoModerationRule, DiscordChannel, DiscordChannelPinsUpdate, DiscordEmoji, @@ -29,14 +31,17 @@ import type { DiscordTypingStart, DiscordUser, DiscordWebhookUpdate, + DiscordInviteCreate, + DiscordInviteDelete } from "../discordeno/mod.ts"; import type { Snowflake } from "./Snowflake.ts"; import type { Session } from "./Session.ts"; -import type { Channel } from "./structures/channels.ts"; import type { Interaction } from "./structures/interactions/InteractionFactory.ts"; -import { ChannelFactory, GuildChannel, ThreadChannel } from "./structures/channels.ts"; +import { AutoModerationRule } from "./structures/AutoModerationRule.ts"; +import { AutoModerationExecution } from "./structures/AutoModerationExecution.ts"; +import { type Channel, ChannelFactory, GuildChannel, ThreadChannel } from "./structures/channels.ts"; import ThreadMember from "./structures/ThreadMember.ts"; import Member from "./structures/Member.ts"; @@ -45,6 +50,7 @@ import User from "./structures/User.ts"; import Integration from "./structures/Integration.ts"; import Guild from "./structures/guilds/Guild.ts"; import InteractionFactory from "./structures/interactions/InteractionFactory.ts"; +import { NewInviteCreate, InviteCreate } from "./structures/Invite.ts"; export type RawHandler = (...args: [Session, number, T]) => void; export type Handler = (...args: T) => unknown; @@ -227,6 +233,26 @@ export const INTEGRATION_DELETE: RawHandler = (session }); }; +export const AUTO_MODERATION_RULE_CREATE: RawHandler = (session, _shardId, payload) => { + session.emit("autoModerationRuleCreate", new AutoModerationRule(session, payload)); +}; + +export const AUTO_MODERATION_RULE_UPDATE: RawHandler = (session, _shardId, payload) => { + session.emit("autoModerationRuleUpdate", new AutoModerationRule(session, payload)); +}; + +export const AUTO_MODERATION_RULE_DELETE: RawHandler = (session, _shardId, payload) => { + session.emit("autoModerationRuleDelete", new AutoModerationRule(session, payload)); +}; + +export const AUTO_MODERATION_ACTION_EXECUTE: RawHandler = ( + session, + _shardId, + payload, +) => { + session.emit("autoModerationActionExecution", new AutoModerationExecution(session, payload)); +}; + export const MESSAGE_REACTION_ADD: RawHandler = (session, _shardId, reaction) => { session.emit("messageReactionAdd", null); }; @@ -251,6 +277,14 @@ export const MESSAGE_REACTION_REMOVE_EMOJI: RawHandler = (session, _shardId, invite) => { + session.emit("inviteCreate", NewInviteCreate(session, invite)); +} + +export const INVITE_DELETE: RawHandler = (session, _shardId, data) => { + session.emit("inviteDelete", { channelId: data.channel_id, guildId: data.guild_id, code: data.code }); +} + export const raw: RawHandler = (session, shardId, data) => { session.emit("raw", data, shardId); }; @@ -296,6 +330,12 @@ export interface Events { "integrationCreate": Handler<[Integration]>; "integrationUpdate": Handler<[Integration]>; "integrationDelete": Handler<[{ id: Snowflake, guildId?: Snowflake, applicationId?: Snowflake }]>; + "inviteCreate": Handler<[InviteCreate]>; + "inviteDelete": Handler<[{ channelId: string, guildId?: string, code: string }]>; + "autoModerationRuleCreate": Handler<[AutoModerationRule]>; + "autoModerationRuleUpdate": Handler<[AutoModerationRule]>; + "autoModerationRuleDelete": Handler<[AutoModerationRule]>; + "autoModerationActionExecution":Handler<[AutoModerationExecution]> "raw": Handler<[unknown, number]>; "webhooksUpdate": Handler<[{ guildId: Snowflake, channelId: Snowflake }]>; "userUpdate": Handler<[User]>; diff --git a/packages/biscuit/Routes.ts b/packages/biscuit/Routes.ts index e928d63..393efed 100644 --- a/packages/biscuit/Routes.ts +++ b/packages/biscuit/Routes.ts @@ -124,6 +124,13 @@ export function GUILDS() { return `/guilds`; } +export function AUTO_MODERATION_RULES(guildId: Snowflake, ruleId?: Snowflake) { + if (ruleId) { + return `/guilds/${guildId}/auto-moderation/rules/${ruleId}`; + } + return `/guilds/${guildId}/auto-moderation/rules`; +} + export function INVITE(inviteCode: string, options?: GetInvite) { let url = `/invites/${inviteCode}?`; diff --git a/packages/biscuit/structures/Application.ts b/packages/biscuit/structures/Application.ts new file mode 100644 index 0000000..0fed63b --- /dev/null +++ b/packages/biscuit/structures/Application.ts @@ -0,0 +1,107 @@ +import { Model } from "./Base.ts"; +import type { Snowflake } from "../Snowflake.ts"; +import type { Session } from "../Session.ts"; +import { + DiscordApplication, + TeamMembershipStates, + DiscordInstallParams, + DiscordUser, + DiscordTeam +} from "../../discordeno/mod.ts"; +import User from "./User.ts"; + +type SummaryDeprecated = "" + +export interface Team { + /** a hash of the image of the team's icon */ + icon?: string; + /** the unique id of the team */ + id: string; + /** the members of the team */ + members: TeamMember[]; + /** user id of the current team owner */ + ownerUserId: string; + /** team name */ + name: string; +} + +export interface TeamMember { + /** the user's membership state on the team */ + membershipState: TeamMembershipStates; + permissions: "*"[]; + + teamId: string; + + user: Partial & Pick +} + +// NewTeam create a new Team object for discord applications +export function NewTeam(session: Session, data: DiscordTeam): Team { + return { + icon: data.icon ? data.icon : undefined, + id: data.id, + members: data.members.map(member => { + return { + membershipState: member.membership_state, + permissions: member.permissions, + teamId: member.team_id, + user: new User(session, member.user) + } + }), + ownerUserId: data.owner_user_id, + name: data.name, + } +} +/** + * @link https://discord.com/developers/docs/resources/application#application-object + */ +export class Application implements Model { + + constructor(session: Session, data: DiscordApplication) { + this.id = data.id; + this.session = session; + + this.name = data.name; + this.icon = data.icon || undefined; + this.description = data.description; + this.rpcOrigins = data.rpc_origins; + this.botPublic = data.bot_public; + this.botRequireCodeGrant = data.bot_require_code_grant; + this.termsOfServiceURL = data.terms_of_service_url; + this.privacyPolicyURL = data.privacy_policy_url; + this.owner = data.owner ? new User(session, data.owner as DiscordUser) : undefined; + this.summary = ""; + this.verifyKey = data.verify_key; + this.team = data.team ? NewTeam(session, data.team) : undefined; + this.guildId = data.guild_id; + this.coverImage = data.cover_image; + this.tags = data.tags; + this.installParams = data.install_params; + this.customInstallURL = data.custom_install_url; + } + + readonly session: Session; + id: Snowflake; + name: string; + icon?: string; + description: string; + rpcOrigins?: string[]; + botPublic: boolean; + botRequireCodeGrant: boolean; + termsOfServiceURL?: string; + privacyPolicyURL?: string; + owner?: Partial; + summary: SummaryDeprecated; + verifyKey: string; + team?: Team; + guildId?: Snowflake; + primarySkuId?: Snowflake; + slug?: string; + coverImage?: string; + flags?: number; + tags?: string[]; + installParams?: DiscordInstallParams; + customInstallURL?: string; +} + +export default Application; \ No newline at end of file diff --git a/packages/biscuit/structures/AutoModerationExecution.ts b/packages/biscuit/structures/AutoModerationExecution.ts new file mode 100644 index 0000000..6e2c7ab --- /dev/null +++ b/packages/biscuit/structures/AutoModerationExecution.ts @@ -0,0 +1,51 @@ +import { AutoModerationTriggerTypes, DiscordAutoModerationActionExecution } from "../../discordeno/mod.ts"; +import type { Session } from "../Session.ts"; +import type { Snowflake } from "../Snowflake.ts"; +import { AutoModerationAction } from "./AutoModerationRule.ts"; + +export class AutoModerationExecution { + constructor(session: Session, data: DiscordAutoModerationActionExecution) { + this.session = session; + this.guildId = data.guild_id; + this.action = Object.create({ + type: data.action.type, + metadata: { + channelId: data.action.metadata.channel_id, + durationSeconds: data.action.metadata.duration_seconds, + }, + }); + this.ruleId = data.rule_id; + this.ruleTriggerType = data.rule_trigger_type; + this.userId = data.user_id; + this.content = data.content; + if (data.channel_id) { + this.channelId = data.channel_id; + } + if (data.message_id) { + this.messageId = data.message_id; + } + if (data.alert_system_message_id) { + this.alertSystemMessageId = data.alert_system_message_id; + } + + if (data.matched_keyword) { + this.matchedKeyword = data.matched_keyword; + } + + if (data.matched_content) { + this.matched_content = data.matched_content; + } + } + session: Session; + guildId: Snowflake; + action: AutoModerationAction; + ruleId: Snowflake; + ruleTriggerType: AutoModerationTriggerTypes; + userId: Snowflake; + channelId?: Snowflake; + messageId?: Snowflake; + alertSystemMessageId?: Snowflake; + content?: string; + matchedKeyword?: string; + matched_content?: string; +} diff --git a/packages/biscuit/structures/AutoModerationRule.ts b/packages/biscuit/structures/AutoModerationRule.ts new file mode 100644 index 0000000..a221d2a --- /dev/null +++ b/packages/biscuit/structures/AutoModerationRule.ts @@ -0,0 +1,65 @@ +import { + AutoModerationActionType, + AutoModerationEventTypes, + AutoModerationTriggerTypes, + DiscordAutoModerationRule, + DiscordAutoModerationRuleTriggerMetadataPresets, +} from "../../discordeno/mod.ts"; +import { Model } from "./Base.ts"; +import type { Session } from "../Session.ts"; +import type { Snowflake } from "../Snowflake.ts"; + +export interface AutoModerationRuleTriggerMetadata { + keywordFilter?: string[]; + presets?: DiscordAutoModerationRuleTriggerMetadataPresets[]; +} + +export interface ActionMetadata { + channelId: Snowflake; + durationSeconds: number; +} + +export interface AutoModerationAction { + type: AutoModerationActionType; + metadata: ActionMetadata; +} + +export class AutoModerationRule implements Model { + constructor(session: Session, data: DiscordAutoModerationRule) { + this.session = session; + this.id = data.id; + this.guildId = data.guild_id; + this.name = data.name; + this.creatorId = data.creator_id; + this.eventType = data.event_type; + this.triggerType = data.trigger_type; + this.triggerMetadata = { + keywordFilter: data.trigger_metadata.keyword_filter, + presets: data.trigger_metadata.presets, + }; + this.actions = data.actions.map((action) => + Object.create({ + type: action.type, + metadata: { + channelId: action.metadata.channel_id, + durationSeconds: action.metadata.duration_seconds, + }, + }) + ); + this.enabled = !!data.enabled; + this.exemptRoles = data.exempt_roles; + this.exemptChannels = data.exempt_channels; + } + session: Session; + id: Snowflake; + guildId: Snowflake; + name: string; + creatorId: Snowflake; + eventType: AutoModerationEventTypes; + triggerType: AutoModerationTriggerTypes; + triggerMetadata: AutoModerationRuleTriggerMetadata; + actions: AutoModerationAction[]; + enabled: boolean; + exemptRoles: Snowflake[]; + exemptChannels: Snowflake[]; +} diff --git a/packages/biscuit/structures/Integration.ts b/packages/biscuit/structures/Integration.ts index 0fcca51..f7bf481 100644 --- a/packages/biscuit/structures/Integration.ts +++ b/packages/biscuit/structures/Integration.ts @@ -53,8 +53,8 @@ export class Integration implements Model { } } + readonly session: Session; id: Snowflake; - session: Session; guildId?: Snowflake; name: string; diff --git a/packages/biscuit/structures/Invite.ts b/packages/biscuit/structures/Invite.ts index 5e79787..1aa857d 100644 --- a/packages/biscuit/structures/Invite.ts +++ b/packages/biscuit/structures/Invite.ts @@ -8,6 +8,8 @@ import type { ScheduledEventEntityType, ScheduledEventPrivacyLevel, ScheduledEventStatus, + DiscordApplication, + DiscordInviteCreate } from "../../discordeno/mod.ts"; import { TargetTypes } from "../../discordeno/mod.ts"; import { GuildChannel } from "./channels.ts"; @@ -15,6 +17,7 @@ import { Member } from "./Member.ts"; import InviteGuild from "./guilds/InviteGuild.ts"; import User from "./User.ts"; import Guild from "./guilds/Guild.ts"; +import Application from "./Application.ts"; export interface InviteStageInstance { /** The members speaking in the Stage */ @@ -46,6 +49,38 @@ export interface InviteScheduledEvent { image?: string; } +export interface InviteCreate { + channelId: string; + code: string; + createdAt: string; + guildId?: string; + inviter?: User; + maxAge: number; + maxUses: number; + targetType: TargetTypes; + targetUser?: User; + targetApplication?: Partial; + temporary: boolean; + uses: number; +} + +export function NewInviteCreate(session: Session, invite: DiscordInviteCreate): InviteCreate { + return { + channelId: invite.channel_id, + code: invite.code, + createdAt: invite.created_at, + guildId: invite.guild_id, + inviter: invite.inviter ? new User(session, invite.inviter) : undefined, + maxAge: invite.max_age, + maxUses: invite.max_uses, + targetType: invite.target_type, + targetUser: invite.target_user ? new User(session, invite.target_user) : undefined, + targetApplication: invite.target_application ? new Application(session, invite.target_application as DiscordApplication) : undefined, + temporary: invite.temporary, + uses: invite.uses + } +} + /** * @link https://discord.com/developers/docs/resources/invite#invite-object */ @@ -53,29 +88,21 @@ export class Invite { constructor(session: Session, data: DiscordInvite) { this.session = session; - if (data.guild) { - this.guild = new InviteGuild(session, data.guild); - } - - if (data.approximate_member_count) { - this.approximateMemberCount = data.approximate_member_count; - } - - if (data.approximate_presence_count) { - this.approximatePresenceCount = data.approximate_presence_count; - } - + this.guild = data.guild ? new InviteGuild(session, data.guild) : undefined; + this.approximateMemberCount = data.approximate_member_count ? data.approximate_member_count : undefined; + this.approximatePresenceCount = data.approximate_presence_count ? data.approximate_presence_count : undefined; + this.code = data.code; + this.expiresAt = data.expires_at ? Number.parseInt(data.expires_at) : undefined; + this.inviter = data.inviter ? new User(session, data.inviter) : undefined; + this.targetUser = data.target_user ? new User(session, data.target_user) : undefined; + this.targetApplication = data.target_application ? new Application(session, data.target_application as DiscordApplication) : undefined; + this.targetType = data.target_type; + if (data.channel) { const guildId = (data.guild && data.guild?.id) ? data.guild.id : ""; this.channel = new GuildChannel(session, data.channel as DiscordChannel, guildId); } - this.code = data.code; - - if (data.expires_at) { - this.expiresAt = Number.parseInt(data.expires_at); - } - if (data.guild_scheduled_event) { this.guildScheduledEvent = { id: data.guild_scheduled_event.id, @@ -105,14 +132,6 @@ export class Invite { }; } - if (data.inviter) { - this.inviter = new User(session, data.inviter); - } - - if (data.target_user) { - this.targetUser = new User(session, data.target_user); - } - if (data.stage_instance) { const guildId = (data.guild && data.guild?.id) ? data.guild.id : ""; this.stageInstance = { @@ -124,13 +143,6 @@ export class Invite { topic: data.stage_instance.topic, }; } - - // TODO: create Application structure - // this.targetApplication = data.target_application - - if (data.target_type) { - this.targetType = data.target_type; - } } readonly session: Session; @@ -145,8 +157,7 @@ export class Invite { channel?: Partial; stageInstance?: InviteStageInstance; guildScheduledEvent?: InviteScheduledEvent; - // TODO: create Application structure - // targetApplication?: Partial + targetApplication?: Partial async delete(): Promise { await Guild.prototype.deleteInvite.call(this.guild, this.code);