Merge pull request #1118 from TriForMine/fp-attempt-9001

Functional Rewrite - Structures -> Transfomers
This commit is contained in:
Skillz4Killz
2021-10-09 14:08:51 -04:00
committed by GitHub
6 changed files with 215 additions and 1081 deletions

View File

@@ -1,144 +1,48 @@
import { eventHandlers } from "../bot.ts";
import { cache } from "../cache.ts";
import { channelOverwriteHasPermission } from "../helpers/channels/channel_overwrite_has_permission.ts";
import { cloneChannel } from "../helpers/channels/clone_channel.ts";
import { deleteChannel } from "../helpers/channels/delete_channel.ts";
import { deleteChannelOverwrite } from "../helpers/channels/delete_channel_overwrite.ts";
import { editChannel } from "../helpers/channels/edit_channel.ts";
import { editChannelOverwrite } from "../helpers/channels/edit_channel_overwrite.ts";
import { sendMessage } from "../helpers/messages/send_message.ts";
import { disconnectMember } from "../helpers/mod.ts";
import type { Channel } from "../types/channels/channel.ts";
import { DiscordChannelTypes } from "../types/channels/channel_types.ts";
import type { ModifyChannel } from "../types/channels/modify_channel.ts";
import type { DiscordOverwrite, Overwrite } from "../types/channels/overwrite.ts";
import type { CreateMessage } from "../types/messages/create_message.ts";
import type { PermissionStrings } from "../types/permissions/permission_strings.ts";
import { snowflakeToBigint } from "../util/bigint.ts";
import { Collection } from "../util/collection.ts";
import { createNewProp } from "../util/utils.ts";
import { DiscordenoGuild } from "./guild.ts";
import { DiscordenoMember } from "./member.ts";
import { DiscordenoMessage } from "./message.ts";
import { DiscordenoVoiceState } from "./voice_state.ts";
import { Channel } from "../types/channels/channel.ts";
import { Bot } from "../bot.ts";
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
import { DiscordOverwrite } from "../types/channels/overwrite.ts";
const CHANNEL_SNOWFLAKES = ["id", "guildId", "lastMessageId", "ownerId", "applicationId", "parentId"];
export function transformChannel(
bot: Bot,
payload: { channel: SnakeCasedPropertiesDeep<Channel> } & { guildId?: bigint }
) {
return {
// UNTRANSFORMED STUFF HERE
type: payload.channel.type,
position: payload.channel.position,
name: payload.channel.name,
topic: payload.channel.topic,
nsfw: payload.channel.nsfw,
bitrate: payload.channel.bitrate,
userLimit: payload.channel.user_limit,
rateLimitPerUser: payload.channel.rate_limit_per_user,
recipients: payload.channel.recipients,
icon: payload.channel.icon,
rtcRegion: payload.channel.rtc_region,
videoQualityMode: payload.channel.video_quality_mode,
guildId: payload.guildId || (payload.channel.guild_id ? bot.transformers.snowflake(payload.channel.guild_id) : 0n),
lastPinTimestamp: payload.channel.last_pin_timestamp,
permissionOverwrites: payload.channel.permission_overwrites
? payload.channel.permission_overwrites.map((o) => ({
type: o.type,
id: bot.transformers.snowflake(o.id),
allow: bot.transformers.snowflake(o.allow),
deny: bot.transformers.snowflake(o.deny),
}))
: [],
const baseChannel: Partial<DiscordenoChannel> = {
toJSON() {
return {
id: this.id?.toString(),
type: this.type,
guildId: this.guildId?.toString(),
position: this.position,
permissionOverwrites: this.permissionOverwrites?.map((o) => ({
...o,
id: o.id.toString(),
allow: o.allow.toString(),
deny: o.deny.toString(),
})),
name: this.name,
topic: this.topic,
nsfw: this.nsfw,
lastMessageId: this.lastMessageId?.toString(),
bitrate: this.bitrate,
userLimit: this.userLimit,
rateLimitPerUser: this.rateLimitPerUser,
recipients: this.recipients,
icon: this.icon,
ownerId: this.ownerId,
applicationId: this.applicationId,
parentId: this.parentId,
lastPinTimestamp: this.lastPinTimestamp ? new Date(this.lastPinTimestamp).toISOString() : undefined,
rtcRegion: this.rtcRegion,
videoQualityMode: this.videoQualityMode,
} as Channel;
},
get guild() {
return cache.guilds.get(this.guildId!);
},
get messages() {
return cache.messages.filter((m) => m.channelId === this.id!);
},
get mention() {
return `<#${this.id!}>`;
},
get voiceStates() {
return this.guild?.voiceStates.filter((voiceState) => voiceState.channelId === this.id!);
},
get connectedMembers() {
const voiceStates = this.voiceStates;
if (!voiceStates) return undefined;
return new Collection(voiceStates.map((vs) => [vs.userId, cache.members.get(vs.userId)]));
},
get isNewsChannel() {
return this.type === DiscordChannelTypes.GuildNews;
},
get isGuildTextBasedChannel() {
return [DiscordChannelTypes.GuildNews, DiscordChannelTypes.GuildText].includes(this.type!);
},
send(content) {
return sendMessage(this.id!, content);
},
disconnect(memberId) {
return disconnectMember(this.guildId!, memberId);
},
delete(reason) {
return deleteChannel(this.id!, reason);
},
editOverwrite(id, options) {
return editChannelOverwrite(this.guildId!, this.id!, id, options);
},
deleteOverwrite(id) {
return deleteChannelOverwrite(this.guildId!, this.id!, id);
},
hasPermission(overwrites, permissions) {
return channelOverwriteHasPermission(this.guildId!, this.id!, overwrites, permissions);
},
edit(options, reason) {
return editChannel(this.id!, options, reason);
},
clone(reason) {
return cloneChannel(this.id!, reason);
},
};
/** Create a structure object */
// deno-lint-ignore require-await
export async function createDiscordenoChannel(data: Channel, guildId?: bigint) {
const { lastPinTimestamp, permissionOverwrites = [], ...rest } = data;
const requiredPropsSize = cache.requiredStructureProperties.channels.size;
const props: Record<string, PropertyDescriptor> = {};
for (const key of Object.keys(rest) as (keyof typeof rest)[]) {
eventHandlers.debug?.("loop", `Running forEach loop in createDiscordenoChannel function.`);
// If empty then support all, otherwise we only allow the ones user added
if (requiredPropsSize && !cache.requiredStructureProperties.channels.has(key)) continue;
props[key] = createNewProp(
CHANNEL_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key]
);
}
// Set the guildId seperately because sometimes guildId is not included
if (!requiredPropsSize || cache.requiredStructureProperties.channels.has("guildId"))
props.guildId = createNewProp(snowflakeToBigint(guildId?.toString() || data.guildId || ""));
if (!requiredPropsSize || cache.requiredStructureProperties.channels.has("lastPinTimestamp"))
props.lastPinTimestamp = createNewProp(lastPinTimestamp ? Date.parse(lastPinTimestamp) : undefined);
if (!requiredPropsSize || cache.requiredStructureProperties.channels.has("permissionOverwrites"))
props.permissionOverwrites = createNewProp(
permissionOverwrites.map((o) => ({
...o,
id: snowflakeToBigint(o.id),
allow: snowflakeToBigint(o.allow),
deny: snowflakeToBigint(o.deny),
}))
);
return Object.create(baseChannel, props) as DiscordenoChannel;
// TRANSFORMED STUFF BELOW
id: bot.transformers.snowflake(payload.channel.id),
lastMessageId: payload.channel.last_message_id
? bot.transformers.snowflake(payload.channel.last_message_id)
: undefined,
ownerId: payload.channel.owner_id ? bot.transformers.snowflake(payload.channel.owner_id) : undefined,
applicationId: payload.channel.application_id
? bot.transformers.snowflake(payload.channel.application_id)
: undefined,
parentId: payload.channel.parent_id ? bot.transformers.snowflake(payload.channel.parent_id) : undefined,
};
}
export interface DiscordenoChannel
@@ -173,64 +77,4 @@ export interface DiscordenoChannel
applicationId?: bigint;
/** Id of the parent category for a channel (each parent category can contain up to 50 channels) */
parentId?: bigint;
// GETTERS
/**
* Gets the guild object for this channel.
*
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
*/
guild?: DiscordenoGuild;
/**
* Gets the messages from cache that were sent in this channel
*
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
*/
messages: Collection<bigint, DiscordenoMessage>;
/** The mention of the channel */
mention: string;
/**
* Gets the voice states for this channel
*
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
*/
voiceStates?: Collection<bigint, DiscordenoVoiceState>;
/**
* Gets the connected members for this channel undefined if member is not cached
*
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
*/
connectedMembers?: Collection<bigint, DiscordenoMember | undefined>;
/** Whether the channel is a news channel. */
isNewsChannel: boolean;
/** Whether the channel is a news or text channel in a guild. */
isGuildTextBasedChannel: boolean;
// METHODS
/** Send a message to the channel. Requires SEND_MESSAGES permission. */
send(content: string | CreateMessage): ReturnType<typeof sendMessage>;
/** Disconnect a member from a voice channel. Requires MOVE_MEMBERS permission. */
disconnect(memberId: bigint): ReturnType<typeof disconnectMember>;
/** Delete the channel */
delete(reason?: string): ReturnType<typeof deleteChannel>;
/** Edit a channel Overwrite */
editOverwrite(overwriteId: bigint, options: Omit<Overwrite, "id">): ReturnType<typeof editChannelOverwrite>;
/** Delete a channel Overwrite */
deleteOverwrite(overwriteId: bigint): ReturnType<typeof deleteChannelOverwrite>;
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */
hasPermission(
overwrites: (Omit<DiscordOverwrite, "id" | "allow" | "deny"> & {
id: bigint;
allow: bigint;
deny: bigint;
})[],
permissions: PermissionStrings[]
): ReturnType<typeof channelOverwriteHasPermission>;
/** Edit the channel */
edit(options: ModifyChannel, reason?: string): ReturnType<typeof editChannel>;
/** Create a new channel with the same properties */
clone(reason?: string): ReturnType<typeof cloneChannel>;
/** Returns the Channel object json value */
toJSON(): Channel;
}

View File

@@ -1,360 +1,93 @@
import { botId, eventHandlers } from "../bot.ts";
import { cache, cacheHandlers } from "../cache.ts";
import { deleteGuild } from "../helpers/guilds/delete_guild.ts";
import { editGuild } from "../helpers/guilds/edit_guild.ts";
import { getAuditLogs } from "../helpers/guilds/get_audit_logs.ts";
import { getBan } from "../helpers/guilds/get_ban.ts";
import { getBans } from "../helpers/guilds/get_bans.ts";
import { guildBannerURL } from "../helpers/guilds/guild_banner_url.ts";
import { guildIconURL } from "../helpers/guilds/guild_icon_url.ts";
import { guildSplashURL } from "../helpers/guilds/guild_splash_url.ts";
import { leaveGuild } from "../helpers/guilds/leave_guild.ts";
import { getInvites } from "../helpers/invites/get_invites.ts";
import { banMember } from "../helpers/members/ban_member.ts";
import { unbanMember } from "../helpers/members/unban_member.ts";
import { Bot } from "../bot.ts";
import type { PresenceUpdate } from "../types/activity/presence_update.ts";
import { GetGuildAuditLog } from "../types/audit_log/get_guild_audit_log.ts";
import type { Emoji } from "../types/emojis/emoji.ts";
import type { CreateGuildBan } from "../types/guilds/create_guild_ban.ts";
import type { Guild } from "../types/guilds/guild.ts";
import { DiscordGuildFeatures } from "../types/guilds/guild_features.ts";
import type { ModifyGuild } from "../types/guilds/modify_guild.ts";
import type { GuildMember, GuildMemberWithUser } from "../types/members/guild_member.ts";
import type { DiscordImageFormat } from "../types/misc/image_format.ts";
import type { DiscordImageSize } from "../types/misc/image_size.ts";
import { snowflakeToBigint } from "../util/bigint.ts";
import { cacheMembers } from "../util/cache_members.ts";
import { Collection } from "../util/collection.ts";
import { iconHashToBigInt } from "../util/hash.ts";
import { channelToThread } from "../util/transformers/channel_to_thread.ts";
import { createNewProp } from "../util/utils.ts";
import { DiscordenoChannel } from "./channel.ts";
import { DiscordenoMember } from "./member.ts";
import { structures } from "./mod.ts";
import { DiscordenoRole } from "./role.ts";
import { DiscordenoVoiceState } from "./voice_state.ts";
import { transformChannel } from "./channel.ts";
import { DiscordenoRole, transformRole } from "./role.ts";
import { DiscordenoVoiceState, transformVoiceState } from "./voice_state.ts";
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
const GUILD_SNOWFLAKES = [
"id",
"ownerId",
"permissions",
"afkChannelId",
"widgetChannelId",
"applicationId",
"systemChannelId",
"rulesChannelId",
"publicUpdatesChannelId",
];
export function transformGuild(bot: Bot, payload: { guild: SnakeCasedPropertiesDeep<Guild> } & { shardId: number }) {
return {
afkTimeout: payload.guild.afk_timeout,
approximateMemberCount: payload.guild.approximate_member_count,
approximatePresenceCount: payload.guild.approximate_presence_count,
defaultMessageNotifications: payload.guild.default_message_notifications,
description: payload.guild.description,
explicitContentFilter: payload.guild.explicit_content_filter,
features: payload.guild.features,
maxMembers: payload.guild.max_members,
maxPresences: payload.guild.max_presences,
maxVideoChannelUsers: payload.guild.max_video_channel_users,
mfaLevel: payload.guild.mfa_level,
name: payload.guild.name,
nsfwLevel: payload.guild.nsfw_level,
preferredLocale: payload.guild.preferred_locale,
premiumSubscriptionCount: payload.guild.premium_subscription_count,
premiumTier: payload.guild.premium_tier,
stageInstances: payload.guild.stage_instances,
systemChannelFlags: payload.guild.system_channel_flags,
vanityUrlCode: payload.guild.vanity_url_code,
verificationLevel: payload.guild.verification_level,
welcomeScreen: payload.guild.welcome_screen,
discoverySplash: payload.guild.discovery_splash,
export const guildToggles = {
/** Whether this user is owner of this guild */
owner: 1n,
/** Whether the guild widget is enabled */
widgetEnabled: 2n,
/** Whether this is a large guild */
large: 4n,
/** Whether this guild is unavailable due to an outage */
unavailable: 8n,
/** Whether this server's icon is animated */
animatedIcon: 16n,
/** Whether this server's banner is animated. */
animatedBanner: 32n,
/** Whether this server's splash is animated. */
animatedSplash: 64n,
};
bitfield:
(payload.guild.owner ? 1n : 0n) |
(payload.guild.widget_enabled ? 2n : 0n) |
(payload.guild.large ? 4n : 0n) |
(payload.guild.unavailable ? 8n : 0n),
const baseGuild: Partial<DiscordenoGuild> = {
toJSON() {
return {
shardId: this.shardId!,
id: this.id?.toString(),
name: this.name,
icon: this.icon,
iconHash: undefined,
splash: this.splash,
discoverySplash: this.discoverySplash,
owner: this.owner,
ownerId: this.ownerId?.toString(),
permissions: this.permissions,
afkChannelId: this.afkChannelId?.toString(),
afkTimeout: this.afkTimeout,
widgetEnabled: this.widgetEnabled,
widgetChannelId: this.widgetChannelId?.toString(),
verificationLevel: this.verificationLevel,
defaultMessageNotifications: this.defaultMessageNotifications,
explicitContentFilter: this.explicitContentFilter,
roles: this.roles?.map((r) => r.toJSON()) || [],
emojis: this.emojis?.array() || [],
features: this.features,
mfaLevel: this.mfaLevel,
applicationId: this.applicationId?.toString(),
systemChannelId: this.systemChannelId?.toString(),
systemChannelFlags: this.systemChannelFlags,
rulesChannelId: this.rulesChannelId?.toString(),
joinedAt: this.joinedAt ? new Date(this.joinedAt).toISOString() : undefined,
large: this.large,
unavailable: this.unavailable,
memberCount: this.memberCount,
voiceStates: this.voiceStates,
members: this.members,
channels: this.channels,
threads: this.threads,
presences: this.presences,
maxPresences: this.maxPresences,
maxMembers: this.maxMembers,
vanityUrlCode: this.vanityUrlCode,
description: this.description,
banner: this.banner,
premiumTier: this.premiumTier,
premiumSubscriptionCount: this.premiumSubscriptionCount,
preferredLocale: this.preferredLocale,
publicUpdatesChannelId: this.publicUpdatesChannelId?.toString(),
maxVideoChannelUsers: this.maxVideoChannelUsers,
approximateMemberCount: this.approximateMemberCount,
approximatePresenceCount: this.approximatePresenceCount,
welcomeScreen: this.welcomeScreen,
nsfwLevel: this.nsfwLevel,
stageInstances: this.stageInstances,
} as Guild & { shardId: number };
},
get members() {
return cache.members.filter((member) => member.guilds.has(this.id!));
},
get channels() {
return cache.channels.filter((channel) => channel.guildId === this.id);
},
get afkChannel() {
return cache.channels.get(this.afkChannelId!);
},
get publicUpdatesChannel() {
return cache.channels.get(this.publicUpdatesChannelId!);
},
get rulesChannel() {
return cache.channels.get(this.rulesChannelId!);
},
get systemChannel() {
return cache.channels.get(this.systemChannelId!);
},
get bot() {
return cache.members.get(botId);
},
get botMember() {
return this.bot?.guilds.get(this.id!);
},
get botVoice() {
return this.voiceStates?.get(botId);
},
get owner() {
return cache.members.get(this.ownerId!);
},
get partnered() {
return Boolean(this.features?.includes(DiscordGuildFeatures.Partnered));
},
get verified() {
return Boolean(this.features?.includes(DiscordGuildFeatures.Verified));
},
bannerURL(size, format) {
return guildBannerURL(this.id!, {
banner: this.banner!,
size,
format,
animated: this.animatedBanner!,
});
},
splashURL(size, format) {
return guildSplashURL(this.id!, {
splash: this.splash!,
size,
format,
animated: this.animatedSplash,
});
},
delete() {
return deleteGuild(this.id!);
},
edit(options) {
return editGuild(this.id!, options);
},
auditLogs(options) {
return getAuditLogs(this.id!, options);
},
getBan(memberId) {
return getBan(this.id!, memberId);
},
bans() {
return getBans(this.id!);
},
ban(memberId, options) {
return banMember(this.id!, memberId, options);
},
unban(memberId) {
return unbanMember(this.id!, memberId);
},
invites() {
return getInvites(this.id!);
},
iconURL(size, format) {
return guildIconURL(this.id!, {
icon: this.icon!,
size,
format,
animated: this.animatedIcon!,
});
},
leave() {
return leaveGuild(this.id!);
},
get isOwner() {
return Boolean(this.bitfield! & guildToggles.owner);
},
get widgetEnabled() {
return Boolean(this.bitfield! & guildToggles.widgetEnabled);
},
get large() {
return Boolean(this.bitfield! & guildToggles.large);
},
get unavailable() {
return Boolean(this.bitfield! & guildToggles.unavailable);
},
get animatedIcon() {
return Boolean(this.bitfield! & guildToggles.animatedIcon);
},
get animatedBanner() {
return Boolean(this.bitfield! & guildToggles.animatedBanner);
},
get animatedSplash() {
return Boolean(this.bitfield! & guildToggles.animatedSplash);
},
};
joinedAt: payload.guild.joined_at ? Date.parse(payload.guild.joined_at) : undefined,
memberCount: payload.guild.member_count ?? 0,
shardId: payload.shardId,
icon: payload.guild.icon ? iconHashToBigInt(payload.guild.icon) : undefined,
banner: payload.guild.banner ? iconHashToBigInt(payload.guild.banner) : undefined,
splash: payload.guild.icon ? iconHashToBigInt(payload.guild.splash) : undefined,
export async function createDiscordenoGuild(data: Guild, shardId: number) {
const {
memberCount = 0,
voiceStates = [],
channels = [],
threads = [],
presences = [],
joinedAt = "",
emojis = [],
members = [],
icon,
splash,
banner,
...rest
} = data;
// TRANSFORMED STUFF BELOW
// TODO: Handle channels/threads in a better way?
channels: (payload.guild.channels || []).map((channel) =>
transformChannel(bot, { channel, guildId: bot.transformers.snowflake(payload.guild.id) })
),
threads: (payload.guild.threads || []).map((channel) => channelToThread(channel)),
let bitfield = 0n;
const guildId = snowflakeToBigint(rest.id);
roles: new Collection(
(payload.guild.roles || [])
.map((role) => transformRole(bot, { role, guildId: bot.transformers.snowflake(payload.guild.id) }))
.map((role) => [role.id, role])
),
presences: new Collection((payload.guild.presences || []).map((p) => [bot.transformers.snowflake(p.user!.id), p])),
emojis: new Collection((payload.guild.emojis || []).map((emoji) => [bot.transformers.snowflake(emoji.id!), emoji])),
voiceStates: new Collection(
(payload.guild.voice_states || [])
.map((vs) =>
transformVoiceState(bot, { voiceState: vs, guildId: bot.transformers.snowflake(payload.guild.id) })
)
.map((vs) => [vs.userId, vs])
),
const promises = [];
for (const channel of channels) {
promises.push(async () => {
const discordenoChannel = await structures.createDiscordenoChannel(channel, guildId);
return cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel);
});
}
for (const thread of threads) {
promises.push(() => {
const discordenoThread = channelToThread(thread);
return cacheHandlers.set("threads", discordenoThread.id, discordenoThread);
});
}
await Promise.all(
promises.map(async (promise) => {
return await promise();
})
);
const roles = await Promise.all(
(data.roles || []).map((role) =>
structures.createDiscordenoRole({
role,
guildId,
})
)
);
const voiceStateStructs = await Promise.all(
voiceStates.map((vs) => {
if (vs.member?.joinedAt) members.push(vs.member);
return structures.createDiscordenoVoiceState(guildId, vs);
})
);
const props: Record<string, ReturnType<typeof createNewProp>> = {};
for (const key of Object.keys(rest) as (keyof typeof rest)[]) {
eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoGuild function.`);
// If its empty default allows all, otherwise only allow those users required.
if (cache.requiredStructureProperties.guilds.size && !cache.requiredStructureProperties.guilds.has(key)) {
continue;
}
const toggleBits = guildToggles[key as keyof typeof guildToggles];
if (toggleBits) {
bitfield |= rest[key] ? toggleBits : 0n;
continue;
}
props[key] = createNewProp(
GUILD_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key]
);
}
const hashes = [
{ name: "icon", toggle: guildToggles.animatedIcon, value: icon },
{ name: "banner", toggle: guildToggles.animatedBanner, value: banner },
{ name: "splash", toggle: guildToggles.animatedSplash, value: splash },
] as const;
for (const hash of hashes) {
// If its empty default allows all, otherwise only allow those users required.
if (cache.requiredStructureProperties.guilds.size && !cache.requiredStructureProperties.guilds.has(hash.name)) {
continue;
}
const transformed = hash.value ? iconHashToBigInt(hash.value) : undefined;
if (transformed) {
props[hash.name] = createNewProp(hash.value);
if (transformed.animated) bitfield |= hash.toggle;
}
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("roles")) {
props.roles = createNewProp(new Collection(roles.map((r: DiscordenoRole) => [r.id, r])));
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("joinedAt")) {
props.joinedAt = createNewProp(Date.parse(joinedAt));
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("presences")) {
props.presences = createNewProp(new Collection(presences.map((p) => [snowflakeToBigint(p.user!.id), p])));
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("memberCount")) {
props.memberCount = createNewProp(memberCount);
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("emojis")) {
props.emojis = createNewProp(new Collection(emojis.map((emoji) => [snowflakeToBigint(emoji.id!), emoji])));
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("voiceStates")) {
props.voiceStates = createNewProp(new Collection(voiceStateStructs.map((vs) => [vs.userId, vs])));
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("shardId")) {
props.shardId = createNewProp(shardId);
}
if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("bitfield")) {
props.bitfield = createNewProp(bitfield);
}
const guild: DiscordenoGuild = Object.create(baseGuild, props);
await cacheMembers(guild.id, members as GuildMemberWithUser[]);
return guild;
id: bot.transformers.snowflake(payload.guild.id),
ownerId: bot.transformers.snowflake(payload.guild.owner_id),
permissions: payload.guild.permissions ? bot.transformers.snowflake(payload.guild.permissions) : 0n,
afkChannelId: payload.guild.afk_channel_id ? bot.transformers.snowflake(payload.guild.afk_channel_id) : undefined,
widgetChannelId: payload.guild.widget_channel_id
? bot.transformers.snowflake(payload.guild.widget_channel_id)
: undefined,
applicationId: payload.guild.application_id ? bot.transformers.snowflake(payload.guild.application_id) : undefined,
systemChannelId: payload.guild.system_channel_id
? bot.transformers.snowflake(payload.guild.system_channel_id)
: undefined,
rulesChannelId: payload.guild.rules_channel_id
? bot.transformers.snowflake(payload.guild.rules_channel_id)
: undefined,
publicUpdatesChannelId: payload.guild.public_updates_channel_id
? bot.transformers.snowflake(payload.guild.public_updates_channel_id)
: undefined,
};
}
export interface DiscordenoGuild
@@ -418,63 +151,4 @@ export interface DiscordenoGuild
isOwner: boolean;
/** Holds all the boolean toggles. */
bitfield: bigint;
// GETTERS
/** Members in this guild. */
members: Collection<bigint, DiscordenoMember>;
/** Channels in this guild. */
channels: Collection<bigint, DiscordenoChannel>;
/** The afk channel if one is set */
afkChannel?: DiscordenoChannel;
/** The public update channel if one is set */
publicUpdatesChannel?: DiscordenoChannel;
/** The rules channel in this guild if one is set */
rulesChannel?: DiscordenoChannel;
/** The system channel in this guild if one is set */
systemChannel?: DiscordenoChannel;
/** The bot member in this guild if cached */
bot?: DiscordenoMember;
/** The bot guild member in this guild if cached */
botMember?: Omit<GuildMember, "joinedAt" | "premiumSince" | "roles"> & {
joinedAt?: number;
premiumSince?: number;
roles: bigint[];
};
/** The bots voice state if there is one in this guild */
botVoice?: DiscordenoVoiceState;
/** The owner member of this guild */
owner?: DiscordenoMember;
/** Whether or not this guild is partnered */
partnered: boolean;
/** Whether or not this guild is verified */
verified: boolean;
// METHODS
/** The banner url for this server */
bannerURL(size?: DiscordImageSize, format?: DiscordImageFormat): string | undefined;
/** The splash url for this server */
splashURL(size?: DiscordImageSize, format?: DiscordImageFormat): string | undefined;
/** The full URL of the icon from Discords CDN. Undefined when no icon is set. */
iconURL(size?: DiscordImageSize, format?: DiscordImageFormat): string | undefined;
/** Delete a guild permanently. User must be owner. Returns 204 No Content on success. Fires a Guild Delete Gateway event. */
delete(): ReturnType<typeof deleteGuild>;
/** Leave a guild */
leave(): ReturnType<typeof leaveGuild>;
/** Edit the server. Requires the MANAGE_GUILD permission. */
edit(options: ModifyGuild): ReturnType<typeof editGuild>;
/** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */
auditLogs(options?: GetGuildAuditLog): ReturnType<typeof getAuditLogs>;
/** Returns a ban object for the given user or a 404 not found if the ban cannot be found. Requires the BAN_MEMBERS permission. */
getBan(memberId: bigint): ReturnType<typeof getBan>;
/** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */
bans(): ReturnType<typeof getBans>;
/** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */
ban(memberId: bigint, options?: CreateGuildBan): ReturnType<typeof banMember>;
/** Remove the ban for a user. Requires BAN_MEMBERS permission */
unban(memberId: bigint): ReturnType<typeof unbanMember>;
/** Get all the invites for this guild. Requires MANAGE_GUILD permission */
invites(): ReturnType<typeof getInvites>;
/** Get the JSON version of the Guild object used to create this. Includes the shardId as well */
toJSON(): Guild & { shardId: number };
}

View File

@@ -1,296 +1,61 @@
import { eventHandlers } from "../bot.ts";
import { cache, cacheHandlers } from "../cache.ts";
import { sendDirectMessage } from "../helpers/members/send_direct_message.ts";
import { addReaction } from "../helpers/messages/add_reaction.ts";
import { addReactions } from "../helpers/messages/add_reactions.ts";
import { deleteMessage } from "../helpers/messages/delete_message.ts";
import { editMessage } from "../helpers/messages/edit_message.ts";
import { pinMessage } from "../helpers/messages/pin_message.ts";
import { removeAllReactions } from "../helpers/messages/remove_all_reactions.ts";
import { removeReaction } from "../helpers/messages/remove_reaction.ts";
import { removeReactionEmoji } from "../helpers/messages/remove_reaction_emoji.ts";
import { sendMessage } from "../helpers/messages/send_message.ts";
import type { GuildMember } from "../types/members/guild_member.ts";
import type { CreateMessage } from "../types/messages/create_message.ts";
import type { EditMessage } from "../types/messages/edit_message.ts";
import type { Message } from "../types/messages/message.ts";
import { bigintToSnowflake, snowflakeToBigint } from "../util/bigint.ts";
import { Bot } from "../bot.ts";
import { Message } from "../types/messages/message.ts";
import { CHANNEL_MENTION_REGEX } from "../util/constants.ts";
import { iconBigintToHash } from "../util/hash.ts";
import { channelToThread, DiscordenoThread } from "../util/transformers/channel_to_thread.ts";
import { createNewProp } from "../util/utils.ts";
import { DiscordenoChannel } from "./channel.ts";
import { DiscordenoGuild } from "./guild.ts";
import { DiscordenoMember } from "./member.ts";
import { DiscordenoRole } from "./role.ts";
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
const MESSAGE_SNOWFLAKES = ["id", "channelId", "guildId", "webhookId", "applicationId"];
export function transformMessage(bot: Bot, data: SnakeCasedPropertiesDeep<Message>) {
return {
// UNTRANSFORMED STUFF HERE
content: data.content || "",
isBot: data.author.bot || false,
tag: `${data.author.username}#${data.author.discriminator.toString().padStart(4, "0")}`,
timestamp: Date.parse(data.timestamp),
editedTimestamp: data.edited_timestamp ? Date.parse(data.edited_timestamp) : undefined,
bitfield: (data.tts ? 1n : 0n) | (data.mention_everyone ? 2n : 0n) | (data.pinned ? 4n : 0n),
attachments: data.attachments,
embeds: data.embeds,
reactions: data.reactions,
type: data.type,
activity: data.activity,
application: data.application,
flags: data.flags,
interaction: data.interaction,
thread: data.thread,
components: data.components,
stickerItems: data.sticker_items,
const messageToggles = {
/** Whether this was a TTS message */
tts: 1n,
/** Whether this message mentions everyone */
mentionEveryone: 2n,
/** Whether this message is pinned */
pinned: 4n,
};
const baseMessage: Partial<DiscordenoMessage> = {
get channel() {
if (this.guildId) return cache.channels.get(this.channelId!);
return cache.channels.get(this.authorId!);
},
get guild() {
if (!this.guildId) return undefined;
return cache.guilds.get(this.guildId);
},
get member() {
if (!this.authorId) return undefined;
return cache.members.get(this.authorId);
},
get guildMember() {
if (!this.guildId) return undefined;
return this.member?.guilds.get(this.guildId);
},
get link() {
return `https://discord.com/channels/${this.guildId || "@me"}/${this.channelId}/${this.id}`;
},
get mentionedRoles() {
return this.mentionedRoleIds?.map((id) => this.guild?.roles.get(id)) || [];
},
get mentionedChannels() {
return this.mentionedChannelIds?.map((id) => cache.channels.get(id)) || [];
},
get mentionedMembers() {
return this.mentionedUserIds?.map((id) => cache.members.get(id)) || [];
},
// METHODS
delete(reason, delayMilliseconds) {
return deleteMessage(this.channelId!, this.id!, reason, delayMilliseconds);
},
edit(content) {
return editMessage(this.channelId!, this.id!, content);
},
pin() {
return pinMessage(this.channelId!, this.id!);
},
addReaction(reaction) {
return addReaction(this.channelId!, this.id!, reaction);
},
addReactions(reactions, ordered) {
return addReactions(this.channelId!, this.id!, reactions, ordered);
},
reply(content, mentionUser = true) {
const contentWithMention: CreateMessage =
typeof content === "string"
? {
content,
allowedMentions: {
repliedUser: mentionUser,
},
messageReference: {
messageId: bigintToSnowflake(this.id!),
failIfNotExists: false,
},
}
: {
...content,
allowedMentions: {
repliedUser: mentionUser,
...(content.allowedMentions || {}),
},
messageReference: {
messageId: bigintToSnowflake(this.id!),
failIfNotExists: content.messageReference?.failIfNotExists === true,
},
};
if (this.guildId) return sendMessage(this.channelId!, contentWithMention);
return sendDirectMessage(this.authorId!, contentWithMention);
},
send(content) {
if (this.guildId) return sendMessage(this.channelId!, content);
return sendDirectMessage(this.authorId!, content);
},
async alert(content, timeout = 10, reason = "") {
if (this.guildId) {
return await sendMessage(this.channelId!, content).then((response) => {
response?.delete(reason, timeout * 1000).catch(console.error);
});
}
return await sendDirectMessage(this.authorId!, content).then((response) => {
response?.delete(reason, timeout * 1000).catch(console.error);
});
},
async alertReply(content, timeout = 10, reason = "") {
return await this.reply!(content).then((response) => response?.delete(reason, timeout * 1000).catch(console.error));
},
removeAllReactions() {
return removeAllReactions(this.channelId!, this.id!);
},
removeReactionEmoji(reaction) {
return removeReactionEmoji(this.channelId!, this.id!, reaction);
},
removeReaction(reaction, userId) {
return removeReaction(this.channelId!, this.id!, reaction, { userId });
},
get tts() {
return Boolean(this.bitfield! & messageToggles.tts);
},
get mentionEveryone() {
return Boolean(this.bitfield! & messageToggles.mentionEveryone);
},
get pinned() {
return Boolean(this.bitfield! & messageToggles.pinned);
},
get thread() {
return cache.threads.get(this.id!);
},
toJSON() {
return {
id: this.id?.toString(),
channelId: this.channelId?.toString(),
guildId: this.guildId?.toString(),
author: {
id: this.authorId?.toString(),
username: this.tag?.substring(0, this.tag.length - 5),
discriminator: this.tag?.substring(this.tag.length - 4),
avatar: this.member?.avatar ? iconBigintToHash(this.member.avatar) : undefined,
bot: this.member?.bot,
system: this.member?.system,
mfaEnabled: this.member?.mfaEnabled,
locale: this.member?.locale,
verified: this.member?.verified,
email: this.member?.email,
flags: this.member?.flags,
premiumType: this.member?.premiumType,
publicFlags: this.member?.publicFlags,
},
member: this.member,
content: this.content,
timestamp: this.timestamp ? new Date(this.timestamp).toISOString() : undefined,
editedTimestamp: this.editedTimestamp ? new Date(this.editedTimestamp).toISOString() : undefined,
tts: this.tts,
mentionEveryone: this.mentionEveryone,
mentions: this.mentions,
mentionRoles: this.mentionRoles,
mentionChannels: this.mentionChannels,
attachments: this.attachments,
embeds: this.embeds,
reactions: this.reactions,
nonce: this.nonce,
pinned: this.pinned,
webhookId: this.webhookId,
type: this.type,
activity: this.activity,
application: this.application,
applicationId: this.applicationId,
messageReference: this.messageReference,
flags: this.flags,
stickers: this.stickers,
referencedMessage: this.referencedMessage,
interaction: this.interaction,
thread: this.thread,
components: this.components,
} as Message;
},
};
export async function createDiscordenoMessage(data: Message) {
const {
guildId = "",
mentionChannels = [],
mentions = [],
mentionRoles = [],
editedTimestamp,
author,
messageReference,
...rest
} = data;
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
const requiredPropsSize = cache.requiredStructureProperties.messages.size;
for (const key of Object.keys(rest) as (keyof typeof rest)[]) {
eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoMessage function.`);
// If empty all are allowed, otherwise check if this prop is allowed
if (requiredPropsSize && !cache.requiredStructureProperties.messages.has(key)) continue;
const toggleBits = messageToggles[key as keyof typeof messageToggles];
if (toggleBits) {
bitfield |= rest[key] ? toggleBits : 0n;
continue;
}
// Don't add member to props since it would overwrite the message.member getter
// thread should not be cached on a message
if (["member", "thread"].includes(key)) continue;
props[key] = createNewProp(
MESSAGE_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key]
);
}
if (rest.thread) await cacheHandlers.set("threads", snowflakeToBigint(data.id), channelToThread(rest.thread));
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("authorId"))
props.authorId = createNewProp(snowflakeToBigint(author.id));
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("isBot"))
props.isBot = createNewProp(author.bot || false);
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("tag"))
props.tag = createNewProp(`${author.username}#${author.discriminator.toString().padStart(4, "0")}`);
// Discord doesnt give guild id for getMessage() so this will fill it in
const guildIdFinal =
snowflakeToBigint(guildId) ||
(await cacheHandlers.get("channels", snowflakeToBigint(data.channelId)))?.guildId ||
0n;
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("content"))
props.content = createNewProp(data.content || "");
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("guildId"))
props.guildId = createNewProp(guildIdFinal);
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("mentionedUserIds"))
props.mentionedUserIds = createNewProp(mentions.map((m) => snowflakeToBigint(m.id)));
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("mentionedRoleIds"))
props.mentionedRoleIds = createNewProp(mentionRoles.map((id) => snowflakeToBigint(id)));
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("mentionedChannelIds"))
props.mentionedChannelIds = createNewProp([
// Keep any ids that discord sends
...mentionChannels.map((m) => snowflakeToBigint(m.id)),
// TRANSFORMED STUFF BELOW
id: bot.transformers.snowflake(data.id),
guildId: data.guild_id ? bot.transformers.snowflake(data.guild_id) : undefined,
channelId: bot.transformers.snowflake(data.channel_id),
webhookId: data.webhook_id ? bot.transformers.snowflake(data.webhook_id) : undefined,
authorId: bot.transformers.snowflake(data.author.id),
applicationId: data.application_id ? bot.transformers.snowflake(data.application_id) : undefined,
messageReference: data.message_reference
? {
messageId: data.message_reference.message_id
? bot.transformers.snowflake(data.message_reference.message_id)
: undefined,
channelId: data.message_reference.channel_id
? bot.transformers.snowflake(data.message_reference.channel_id)
: undefined,
guildId: data.message_reference.guild_id
? bot.transformers.snowflake(data.message_reference.guild_id)
: undefined,
}
: undefined,
mentionedUserIds: data.mentions ? data.mentions.map((m) => bot.transformers.snowflake(m.id)) : [],
mentionedRoleIds: data.mention_roles ? data.mention_roles.map((id) => bot.transformers.snowflake(id)) : [],
mentionedChannelIds: [
// Keep any ids tht discord sends
...(data.mention_channels ?? []).map((m) => bot.transformers.snowflake(m.id)),
// Add any other ids that can be validated in a channel mention format
...(rest.content?.match(CHANNEL_MENTION_REGEX) || []).map((text) =>
...(data.content?.match(CHANNEL_MENTION_REGEX) || []).map((text) =>
// converts the <#123> into 123
snowflakeToBigint(text.substring(2, text.length - 1))
bot.transformers.snowflake(text.substring(2, text.length - 1))
),
]);
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("timestamp"))
props.timestamp = createNewProp(Date.parse(data.timestamp));
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("editedTimestamp"))
props.editedTimestamp = createNewProp(editedTimestamp ? Date.parse(editedTimestamp) : undefined);
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("messageReference"))
props.messageReference = createNewProp(
messageReference
? {
messageId: messageReference.messageId ? snowflakeToBigint(messageReference.messageId) : undefined,
channelId: messageReference.channelId ? snowflakeToBigint(messageReference.channelId) : undefined,
guildId: messageReference.guildId ? snowflakeToBigint(messageReference.guildId) : undefined,
}
: undefined
);
if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("bitfield"))
props.bitfield = createNewProp(bitfield);
return Object.create(baseMessage, props) as DiscordenoMessage;
],
};
}
export interface DiscordenoMessage
@@ -339,58 +104,4 @@ export interface DiscordenoMessage
timestamp: number;
/** When this message was edited (or undefined if never) */
editedTimestamp?: number;
// GETTERS
/** The channel where this message was sent. Can be undefined if uncached. */
channel?: DiscordenoChannel;
/** The guild of this message. Can be undefined if not in cache or in DM */
guild?: DiscordenoGuild;
/** The member for the user who sent the message. Can be undefined if not in cache or in dm. */
member?: DiscordenoMember;
/** The guild member details for this guild and member. Can be undefined if not in cache or in dm. */
guildMember?: Omit<GuildMember, "joinedAt" | "premiumSince" | "roles"> & {
joinedAt?: number;
premiumSince?: number;
roles: bigint[];
};
/** The url link to this message */
link: string;
/** The role objects for all the roles that were mentioned in this message */
mentionedRoles: (DiscordenoRole | undefined)[];
/** The channel objects for all the channels that were mentioned in this message. */
mentionedChannels: (DiscordenoChannel | undefined)[];
/** The member objects for all the members that were mentioned in this message. */
mentionedMembers: (DiscordenoMember | undefined)[];
/** The thread if this message has a thread. */
thread?: DiscordenoThread;
// METHODS
/** Delete the message */
delete(reason?: string, delayMilliseconds?: number): ReturnType<typeof deleteMessage>;
/** Edit the message */
edit(content: string | EditMessage): ReturnType<typeof editMessage>;
/** Pins the message in the channel */
pin(): ReturnType<typeof pinMessage>;
/** Add a reaction to the message */
addReaction(reaction: string): ReturnType<typeof addReaction>;
/** Add multiple reactions to the message without or without order. */
addReactions(reactions: string[], ordered?: boolean): ReturnType<typeof addReactions>;
/** Send a inline reply to this message */
reply(content: string | CreateMessage, mentionUser?: boolean): ReturnType<typeof sendMessage>;
/** Send a message to this channel where this message is */
send(content: string | CreateMessage): ReturnType<typeof sendMessage>;
/** Send a message to this channel and then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */
alert(content: string | CreateMessage, timeout?: number, reason?: string): Promise<void>;
/** Send a inline reply to this message but then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */
alertReply(content: string | CreateMessage, timeout?: number, reason?: string): Promise<unknown>;
/** Removes all reactions for all emojis on this message */
removeAllReactions(): ReturnType<typeof removeAllReactions>;
/** Removes all reactions for a single emoji on this message */
removeReactionEmoji(reaction: string): ReturnType<typeof removeReactionEmoji>;
/** Removes a reaction from the given user on this message, defaults to bot */
removeReaction(reaction: string, userId?: bigint): ReturnType<typeof removeReaction>;
/** Convert to json */
toJSON(): Message;
}

View File

@@ -1,22 +1,32 @@
import { Bot } from "../bot.ts";
import { Role } from "../types/mod.ts";
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
export function transformRole(
bot: Bot,
payload: { role: Role } & {
payload: { role: SnakeCasedPropertiesDeep<Role> } & {
guildId: bigint;
}
) {
): DiscordenoRole {
return {
// TODO: decide if its better to spread like this or do manually
...payload,
// UNTRANSFORMED STUFF HERE
// TODO: decide if we should use spread above or do manually
// name: payload.role.name,
name: payload.role.name,
guildId: payload.guildId,
position: payload.role.position,
color: payload.role.color,
bitfield:
(payload.role.hoist ? 1n : 0n) |
(payload.role.managed ? 2n : 0n) |
(payload.role.mentionable ? 4n : 0n) |
(payload.role.tags?.premium_subscriber ? 8n : 0n),
// TRANSFORMED STUFF BELOW
id: bot.transformers.snowflake(payload.role.id),
botId: payload.role.tags?.botId ? bot.transformers.snowflake(payload.role.tags?.botId) : undefined,
botId: payload.role.tags?.bot_id ? bot.transformers.snowflake(payload.role.tags.bot_id) : undefined,
integrationId: payload.role.tags?.integration_id
? bot.transformers.snowflake(payload.role.tags.integration_id)
: undefined,
permissions: bot.transformers.snowflake(payload.role.permissions),
};
}
@@ -28,7 +38,7 @@ export interface DiscordenoRole extends Omit<Role, "tags" | "id" | "permissions"
/** If this role is the nitro boost role. */
isNitroBoostRole: boolean;
/** The integration id that is associated with this role */
integrationId: bigint;
integrationId?: bigint;
/** The roles guildId */
guildId: bigint;
/** Permission bit set */

View File

@@ -1,123 +1,30 @@
import { eventHandlers } from "../bot.ts";
import { cache } from "../cache.ts";
import type { GuildMember } from "../types/members/guild_member.ts";
import type { VoiceState } from "../types/voice/voice_state.ts";
import { snowflakeToBigint } from "../util/bigint.ts";
import { createNewProp } from "../util/utils.ts";
import { DiscordenoGuild } from "./guild.ts";
import { DiscordenoMember } from "./member.ts";
import { Bot } from "../bot.ts";
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
const VOICE_STATE_SNOWFLAKES = ["userId", "channelId", "guildId"];
export function transformVoiceState(
bot: Bot,
payload: { voiceState: SnakeCasedPropertiesDeep<VoiceState> } & { guildId: bigint }
) {
return {
bitfield:
(payload.voiceState.deaf ? 1n : 0n) |
(payload.voiceState.mute ? 2n : 0n) |
(payload.voiceState.self_deaf ? 4n : 0n) |
(payload.voiceState.self_mute ? 8n : 0n) |
(payload.voiceState.self_stream ? 16n : 0n) |
(payload.voiceState.self_video ? 32n : 0n) |
(payload.voiceState.suppress ? 64n : 0n),
export const voiceStateToggles = {
/** Whether this user is deafened by the server */
deaf: 1n,
/** Whether this user is muted by the server */
mute: 2n,
/** Whether this user is locally deafened */
selfDeaf: 4n,
/** Whether this user is locally muted */
selfMute: 8n,
/** Whether this user is streaming using "Go Live" */
selfStream: 16n,
/** Whether this user's camera is enabled */
selfVideo: 32n,
/** Whether this user is muted by the current user */
suppress: 64n,
};
requestToSpeakTimestamp: payload.voiceState.request_to_speak_timestamp,
sessionId: payload.voiceState.session_id,
const baseRole: Partial<DiscordenoVoiceState> = {
get member() {
return cache.members.get(this.userId!);
},
get guildMember() {
return this.member?.guilds.get(this.guildId!);
},
get guild() {
return cache.guilds.get(this.guildId!);
},
get deaf() {
return Boolean(this.bitfield! & voiceStateToggles.deaf);
},
get mute() {
return Boolean(this.bitfield! & voiceStateToggles.mute);
},
get selfDeaf() {
return Boolean(this.bitfield! & voiceStateToggles.selfDeaf);
},
get selfMute() {
return Boolean(this.bitfield! & voiceStateToggles.selfMute);
},
get selfStream() {
return Boolean(this.bitfield! & voiceStateToggles.selfStream);
},
get selfVideo() {
return Boolean(this.bitfield! & voiceStateToggles.selfVideo);
},
get suppress() {
return Boolean(this.bitfield! & voiceStateToggles.suppress);
},
toJSON() {
return {
guildId: this.guildId?.toString(),
channelId: this.channelId?.toString(),
userId: this.userId?.toString(),
member: this.member,
sessionId: this.sessionId,
deaf: this.deaf,
mute: this.mute,
selfDeaf: this.selfDeaf,
selfMute: this.selfMute,
selfStream: this.selfStream,
selfVideo: this.selfVideo,
suppress: this.suppress,
requestToSpeakTimestamp: this.requestToSpeakTimestamp,
} as VoiceState;
},
};
// deno-lint-ignore require-await
export async function createDiscordenoVoiceState(guildId: bigint, data: VoiceState) {
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
for (const key of Object.keys(data) as (keyof typeof data)[]) {
eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoVoiceState function.`);
// if is empty allow all, otherwise check if prop is required
if (cache.requiredStructureProperties.voiceStates.size && !cache.requiredStructureProperties.voiceStates.has(key))
continue;
// We don't need to cache member twice. It will be in cache.members
if (key === "member") continue;
const toggleBits = voiceStateToggles[key as keyof typeof voiceStateToggles];
if (toggleBits) {
bitfield |= data[key] ? toggleBits : 0n;
continue;
}
props[key] = createNewProp(
VOICE_STATE_SNOWFLAKES.includes(key)
? data[key]
? snowflakeToBigint(data[key] as string)
: undefined
: data[key]
);
}
if (
!cache.requiredStructureProperties.voiceStates.size ||
cache.requiredStructureProperties.voiceStates.has("guildId")
)
props.guildId = createNewProp(guildId);
if (
!cache.requiredStructureProperties.voiceStates.size ||
cache.requiredStructureProperties.voiceStates.has("bitfield")
)
props.bitfield = createNewProp(bitfield);
return Object.create(baseRole, props) as DiscordenoVoiceState;
channelId: payload.voiceState.channel_id ? bot.transformers.snowflake(payload.voiceState.channel_id) : undefined,
guildId:
payload.guildId || (payload.voiceState.guild_id ? bot.transformers.snowflake(payload.voiceState.guild_id) : 0n),
userId:
payload.guildId || (payload.voiceState.user_id ? bot.transformers.snowflake(payload.voiceState.user_id) : 0n),
};
}
export interface DiscordenoVoiceState extends Omit<VoiceState, "channelId" | "guildId" | "userId" | "member"> {
@@ -129,16 +36,4 @@ export interface DiscordenoVoiceState extends Omit<VoiceState, "channelId" | "gu
userId: bigint;
/** Holds all the boolean toggles. */
bitfield: bigint;
// GETTERS
member: DiscordenoMember;
guildMember?: Omit<GuildMember, "joinedAt" | "premiumSince" | "roles"> & {
joinedAt?: number;
premiumSince?: number;
roles: bigint[];
};
/** The guild where this role is. If undefined, the guild is not cached */
guild?: DiscordenoGuild;
/** Converts to the raw JSON format. */
toJSON(): VoiceState;
}

View File

@@ -16,4 +16,4 @@ export function iconBigintToHash(icon: bigint) {
const hash = icon.toString(16);
// Hashes starting with a are animated and with b are not so need to handle that
return hash.startsWith("a") ? `a_${hash.substring(1)}` : hash.substring(1);
}
}