feat: implement audit log, core fetch functionality, guild management, message handling, and poll features

This commit is contained in:
Sosokker 2025-09-02 20:50:59 +07:00
parent 5b4832a2cb
commit 6272bb62ff
6 changed files with 631 additions and 0 deletions

53
src/discord/audit-log.ts Normal file
View File

@ -0,0 +1,53 @@
import { fetchDiscord } from "./core";
export interface AuditLog {
application_commands: any[];
audit_log_entries: AuditLogEntry[];
auto_moderation_rules: any[];
guild_scheduled_events: any[];
integrations: any[];
threads: any[];
users: any[];
webhooks: any[];
}
export interface AuditLogEntry {
target_id: string | null;
changes?: AuditLogChange[];
user_id: string | null;
id: string;
action_type: number;
options?: any;
reason?: string;
}
export interface AuditLogChange {
new_value?: any;
old_value?: any;
key: string;
}
export const getAuditLog = async (options: {
guildId: string;
userId?: string;
actionType?: number;
before?: string;
after?: string;
limit?: number;
}): Promise<AuditLog> => {
const { guildId, userId, actionType, before, after, limit } = options;
const query = new URLSearchParams();
if (userId) query.append("user_id", userId);
if (actionType) query.append("action_type", String(actionType));
if (before) query.append("before", before);
if (after) query.append("after", after);
if (limit) query.append("limit", String(limit));
const queryString = query.toString();
const path = `/guilds/${guildId}/audit-logs${
queryString ? `?${queryString}` : ""
}`;
const response = await fetchDiscord(path);
return response.json();
};

21
src/discord/core.ts Normal file
View File

@ -0,0 +1,21 @@
import { Config } from "../config";
export const fetchDiscord = async (
path: string,
init?: RequestInit,
): Promise<Response> => {
const response = await fetch(`${Config.DISCORD_API}${path}`, {
...init,
headers: {
Authorization: `Bot ${Config.BOT_TOKEN}`,
"Content-Type": "application/json",
...(init?.headers || {}),
},
});
if (response.status === 429) {
const data = await response.json().catch(() => ({}) as any);
const retry = (data?.retry_after ? Number(data.retry_after) : 1) * 1000;
await new Promise((r) => setTimeout(r, retry));
}
return response;
};

252
src/discord/guild.ts Normal file
View File

@ -0,0 +1,252 @@
import { fetchDiscord } from "./core";
/**
* Represents a Discord guild.
* This is a partial interface based on the Discord API documentation.
*/
export interface Guild {
id: string;
name: string;
icon: string | null;
owner_id: string;
verification_level: number;
roles: any[]; // Array of role objects
emojis: any[]; // Array of emoji objects
features: string[];
}
/**
* Represents a Guild Member.
* This is a partial interface.
*/
export interface GuildMember {
user?: any; // User object
nick?: string | null;
roles: string[];
joined_at: string; // ISO8601 timestamp
deaf: boolean;
mute: boolean;
}
/**
* Represents a Role.
*/
export interface Role {
id: string;
name: string;
color: number;
hoist: boolean;
position: number;
permissions: string;
managed: boolean;
mentionable: boolean;
}
/**
* Represents a Ban.
*/
export interface Ban {
reason: string | null;
user: any; // User object
}
/**
* Get a guild by ID.
* @param guildId The ID of the guild.
* @param withCounts Whether to include approximate member and presence counts.
* @returns A guild object.
*/
export const getGuild = async (
guildId: string,
withCounts?: boolean,
): Promise<Guild> => {
const path = `/guilds/${guildId}${withCounts ? "?with_counts=true" : ""}`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Modify a guild's settings.
* @param guildId The ID of the guild.
* @param data The new guild data.
* @returns The updated guild object.
*/
export const modifyGuild = async (
guildId: string,
data: any,
): Promise<Guild> => {
const path = `/guilds/${guildId}`;
const res = await fetchDiscord(path, {
method: "PATCH",
body: JSON.stringify(data),
});
return res.json();
};
/**
* Get a list of guild channel objects.
* @param guildId The ID of the guild.
* @returns A list of channel objects.
*/
export const getGuildChannels = async (guildId: string): Promise<any[]> => {
const path = `/guilds/${guildId}/channels`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Create a new channel object for the guild.
* @param guildId The ID of the guild.
* @param data The channel data.
* @returns The new channel object.
*/
export const createGuildChannel = async (
guildId: string,
data: any,
): Promise<any> => {
const path = `/guilds/${guildId}/channels`;
const res = await fetchDiscord(path, {
method: "POST",
body: JSON.stringify(data),
});
return res.json();
};
/**
* Returns a list of guild member objects that are members of the guild.
* @param guildId The ID of the guild.
* @param options Options for listing members.
* @returns A list of guild member objects.
*/
export const listGuildMembers = async (
guildId: string,
options?: { limit?: number; after?: string },
): Promise<GuildMember[]> => {
const query = new URLSearchParams();
if (options?.limit) query.append("limit", String(options.limit));
if (options?.after) query.append("after", options.after);
const queryString = query.toString();
const path = `/guilds/${guildId}/members${queryString ? `?${queryString}` : ""}`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Returns a guild member object for the specified user.
* @param guildId The ID of the guild.
* @param userId The ID of the user.
* @returns A guild member object.
*/
export const getGuildMember = async (
guildId: string,
userId: string,
): Promise<GuildMember> => {
const path = `/guilds/${guildId}/members/${userId}`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Modify attributes of a guild member.
* @param guildId The ID of the guild.
* @param userId The ID of the user.
* @param data The data to update.
* @returns The updated guild member.
*/
export const modifyGuildMember = async (
guildId: string,
userId: string,
data: any,
): Promise<GuildMember> => {
const path = `/guilds/${guildId}/members/${userId}`;
const res = await fetchDiscord(path, {
method: "PATCH",
body: JSON.stringify(data),
});
return res.json();
};
/**
* Remove a member from a guild.
* @param guildId The ID of the guild.
* @param userId The ID of the user to remove.
*/
export const removeGuildMember = async (
guildId: string,
userId: string,
): Promise<void> => {
const path = `/guilds/${guildId}/members/${userId}`;
await fetchDiscord(path, {
method: "DELETE",
});
};
/**
* Returns a list of role objects for the guild.
* @param guildId The ID of the guild.
* @returns A list of role objects.
*/
export const getGuildRoles = async (guildId: string): Promise<Role[]> => {
const path = `/guilds/${guildId}/roles`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Returns a list of ban objects for the users banned from this guild.
* @param guildId The ID of the guild.
* @returns A list of ban objects.
*/
export const getGuildBans = async (guildId: string): Promise<Ban[]> => {
const path = `/guilds/${guildId}/bans`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Create a guild ban, and optionally delete previous messages sent by the banned user.
* @param guildId The ID of the guild.
* @param userId The ID of the user to ban.
* @param options Options for the ban.
*/
export const createGuildBan = async (
guildId: string,
userId: string,
options?: { delete_message_seconds?: number },
): Promise<void> => {
const path = `/guilds/${guildId}/bans/${userId}`;
await fetchDiscord(path, {
method: "PUT",
body: JSON.stringify(options),
});
};
/**
* Remove the ban for a user.
* @param guildId The ID of the guild.
* @param userId The ID of the user to unban.
*/
export const removeGuildBan = async (
guildId: string,
userId: string,
): Promise<void> => {
const path = `/guilds/${guildId}/bans/${userId}`;
await fetchDiscord(path, {
method: "DELETE",
});
};
/**
* Removes a role from a guild member.
* @param guildId The ID of the guild.
* @param userId The ID of the user.
* @param roleId The ID of the role to remove.
*/
export const removeGuildMemberRole = async (
guildId: string,
userId: string,
roleId: string,
): Promise<void> => {
const path = `/guilds/${guildId}/members/${userId}/roles/${roleId}`;
await fetchDiscord(path, { method: "DELETE" });
};

5
src/discord/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from "./audit-log";
export * from "./core";
export * from "./guild";
export * from "./message";
export * from "./poll";

219
src/discord/message.ts Normal file
View File

@ -0,0 +1,219 @@
import { fetchDiscord } from "./core";
/**
* Represents a message sent in a channel within Discord.
* This is a partial interface based on the Discord API documentation.
*/
export interface Message {
id: string;
channel_id: string;
author: any; // User object
content: string;
timestamp: string; // ISO8601 timestamp
edited_timestamp: string | null; // ISO8601 timestamp
tts: boolean;
mention_everyone: boolean;
mentions: any[]; // array of user objects
mention_roles: string[]; // array of role object ids
mention_channels?: any[]; // array of channel mention objects
attachments: any[]; // array of attachment objects
embeds: any[]; // array of embed objects
reactions?: any[]; // array of reaction objects
nonce?: number | string;
pinned: boolean;
webhook_id?: string;
type: number;
activity?: any; // message activity object
application?: any; // partial application object
application_id?: string;
flags?: number;
message_reference?: any; // message reference object
referenced_message?: Message | null;
interaction?: any; // message interaction object
thread?: any; // channel object
components?: any[]; // array of message components
sticker_items?: any[]; // array of message sticker item objects
stickers?: any[]; // array of sticker objects
position?: number;
role_subscription_data?: any; // role subscription data object
resolved?: any;
}
/**
* Retrieves the messages in a channel.
* @param channelId The ID of the channel.
* @param options Options for fetching messages.
* @returns An array of message objects.
*/
export const getChannelMessages = async (
channelId: string,
options?: {
around?: string;
before?: string;
after?: string;
limit?: number;
},
): Promise<Message[]> => {
const query = new URLSearchParams();
if (options?.around) query.append("around", options.around);
if (options?.before) query.append("before", options.before);
if (options?.after) query.append("after", options.after);
if (options?.limit) query.append("limit", String(options.limit));
const queryString = query.toString();
const path = `/channels/${channelId}/messages${queryString ? `?${queryString}` : ""}`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Retrieves a specific message in the channel.
* @param channelId The ID of the channel.
* @param messageId The ID of the message.
* @returns A message object.
*/
export const getChannelMessage = async (
channelId: string,
messageId: string,
): Promise<Message> => {
const path = `/channels/${channelId}/messages/${messageId}`;
const res = await fetchDiscord(path);
return res.json();
};
export interface CreateMessageParams {
content?: string;
tts?: boolean;
embeds?: any[];
allowed_mentions?: any;
message_reference?: any;
components?: any[];
sticker_ids?: string[];
files?: any[];
payload_json?: string;
attachments?: any[];
flags?: number;
}
/**
* Post a message to a guild text or DM channel.
* @param channelId The ID of the channel.
* @param data The message data.
* @returns The created message object.
*/
export const createMessage = async (
channelId: string,
data: CreateMessageParams,
): Promise<Message> => {
const path = `/channels/${channelId}/messages`;
const res = await fetchDiscord(path, {
method: "POST",
body: JSON.stringify(data),
});
return res.json();
};
export interface EditMessageParams {
content?: string;
embeds?: any[];
flags?: number;
allowed_mentions?: any;
components?: any[];
files?: any[];
payload_json?: string;
attachments?: any[];
}
/**
* Edit a previously sent message.
* @param channelId The ID of the channel.
* @param messageId The ID of the message to edit.
* @param data The new message data.
* @returns The updated message object.
*/
export const editMessage = async (
channelId: string,
messageId: string,
data: EditMessageParams,
): Promise<Message> => {
const path = `/channels/${channelId}/messages/${messageId}`;
const res = await fetchDiscord(path, {
method: "PATCH",
body: JSON.stringify(data),
});
return res.json();
};
/**
* Delete a message.
* @param channelId The ID of the channel.
* @param messageId The ID of the message to delete.
*/
export const deleteMessage = async (
channelId: string,
messageId: string,
): Promise<void> => {
const path = `/channels/${channelId}/messages/${messageId}`;
await fetchDiscord(path, {
method: "DELETE",
});
};
/**
* Delete multiple messages in a single request.
* @param channelId The ID of the channel.
* @param messageIds An array of message IDs to delete.
*/
export const bulkDeleteMessages = async (
channelId: string,
messageIds: string[],
): Promise<void> => {
const path = `/channels/${channelId}/messages/bulk-delete`;
await fetchDiscord(path, {
method: "POST",
body: JSON.stringify({ messages: messageIds }),
});
};
/**
* Create a reaction for the message.
* @param channelId The ID of the channel.
* @param messageId The ID of the message.
* @param emoji The emoji to react with.
*/
export const addReaction = async (
channelId: string,
messageId: string,
emoji: string,
): Promise<void> => {
const path = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(
emoji,
)}/@me`;
await fetchDiscord(path, { method: "PUT" });
};
/**
* Get a list of users that reacted with this emoji.
* @param channelId The ID of the channel.
* @param messageId The ID of the message.
* @param emoji The emoji.
* @param options Options for fetching reactions.
* @returns A list of user objects.
*/
export const getReactions = async (
channelId: string,
messageId: string,
emoji: string,
options?: { after?: string; limit?: number },
): Promise<any[]> => {
const query = new URLSearchParams();
if (options?.after) query.append("after", options.after);
if (options?.limit) query.append("limit", String(options.limit ?? 100));
const queryString = query.toString();
const path = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(
emoji,
)}${queryString ? `?${queryString}` : ""}`;
const res = await fetchDiscord(path);
return res.json();
};

81
src/discord/poll.ts Normal file
View File

@ -0,0 +1,81 @@
import { fetchDiscord } from "./core";
import { Message } from "./message";
export interface PollMedia {
text?: string;
emoji?: any; // Partial Emoji
}
export interface PollAnswer {
answer_id: number;
poll_media: PollMedia;
}
export interface PollAnswerCount {
id: number;
count: number;
me_voted: boolean;
}
export interface PollResults {
is_finalized: boolean;
answer_counts: PollAnswerCount[];
}
export interface Poll {
question: PollMedia;
answers: PollAnswer[];
expiry: string | null; // ISO8601 timestamp
allow_multiselect: boolean;
layout_type: number;
results?: PollResults;
}
export interface PollCreateRequest {
question: PollMedia;
answers: PollAnswer[];
duration?: number;
allow_multiselect?: boolean;
layout_type?: number;
}
/**
* Get a list of users that voted for a specific answer.
* @param channelId The ID of the channel.
* @param messageId The ID of the message with the poll.
* @param answerId The ID of the answer.
* @param options Options for fetching voters.
* @returns A list of user objects.
*/
export const getAnswerVoters = async (
channelId: string,
messageId: string,
answerId: number,
options?: { after?: string; limit?: number },
): Promise<{ users: any[] }> => {
const query = new URLSearchParams();
if (options?.after) query.append("after", options.after);
if (options?.limit) query.append("limit", String(options.limit));
const queryString = query.toString();
const path = `/channels/${channelId}/polls/${messageId}/answers/${answerId}${queryString ? `?${queryString}` : ""}`;
const res = await fetchDiscord(path);
return res.json();
};
/**
* Immediately ends a poll.
* @param channelId The ID of the channel.
* @param messageId The ID of the message with the poll.
* @returns The updated message object.
*/
export const endPoll = async (
channelId: string,
messageId: string,
): Promise<Message> => {
const path = `/channels/${channelId}/polls/${messageId}/expire`;
const res = await fetchDiscord(path, {
method: "POST",
});
return res.json();
};