refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -17,13 +17,7 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export type AuthSession = Readonly<{
id: string;
approx_last_used_at: string | null;
client_os: string;
client_platform: string;
client_location: string | null;
}>;
import type {AuthSessionLocation, AuthSessionResponse} from '@fluxer/schema/src/domains/auth/AuthSchemas';
export class AuthSessionRecord {
readonly id: string;
@@ -31,22 +25,25 @@ export class AuthSessionRecord {
readonly clientOs: string;
readonly clientPlatform: string;
readonly clientLocation: string | null;
readonly isCurrent: boolean;
private readonly clientInfo: AuthSessionResponse['client_info'] | null;
constructor(data: AuthSession) {
this.id = data.id;
constructor(data: AuthSessionResponse) {
this.id = data.id_hash;
this.approxLastUsedAt = data.approx_last_used_at ? new Date(data.approx_last_used_at) : null;
this.clientOs = data.client_os;
this.clientPlatform = data.client_platform;
this.clientLocation = data.client_location;
this.clientInfo = data.client_info ?? null;
this.clientOs = this.clientInfo?.os ?? 'Unknown';
this.clientPlatform = this.clientInfo?.platform ?? 'Unknown';
this.clientLocation = getLocationLabel(this.clientInfo?.location ?? null);
this.isCurrent = data.current;
}
toJSON(): AuthSession {
toJSON(): AuthSessionResponse {
return {
id: this.id,
id_hash: this.id,
approx_last_used_at: this.approxLastUsedAt?.toISOString() ?? null,
client_os: this.clientOs,
client_platform: this.clientPlatform,
client_location: this.clientLocation,
client_info: this.clientInfo,
current: this.isCurrent,
};
}
@@ -54,3 +51,12 @@ export class AuthSessionRecord {
return JSON.stringify(this) === JSON.stringify(other);
}
}
function getLocationLabel(location: AuthSessionLocation | null): string | null {
if (!location) {
return null;
}
const parts = [location.city, location.region, location.country].filter(Boolean);
return parts.length ? parts.join(', ') : null;
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {type UserPartial, UserRecord} from '~/records/UserRecord';
import UserStore from '~/stores/UserStore';
export type BetaCode = Readonly<{
code: string;
created_at: string;
redeemed_at: string | null;
redeemer: UserPartial | null;
}>;
export class BetaCodeRecord {
readonly code: string;
readonly createdAt: Date;
readonly redeemedAt: Date | null;
readonly redeemer: UserRecord | null;
constructor(data: BetaCode) {
this.code = data.code;
this.createdAt = new Date(data.created_at);
this.redeemedAt = data.redeemed_at ? new Date(data.redeemed_at) : null;
this.redeemer = data.redeemer ? new UserRecord(data.redeemer) : null;
if (this.redeemer != null) {
UserStore.cacheUsers([this.redeemer.toJSON()]);
}
}
toJSON(): BetaCode {
return {
code: this.code,
created_at: this.createdAt.toISOString(),
redeemed_at: this.redeemedAt?.toISOString() ?? null,
redeemer: this.redeemer ? this.redeemer.toJSON() : null,
};
}
equals(other: BetaCodeRecord): boolean {
return JSON.stringify(this) === JSON.stringify(other);
}
}

View File

@@ -17,18 +17,13 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {ChannelTypes} from '~/Constants';
import type {UserPartial} from '~/records/UserRecord';
import UserPinnedDMStore from '~/stores/UserPinnedDMStore';
import UserStore from '~/stores/UserStore';
import * as SnowflakeUtils from '~/utils/SnowflakeUtils';
type ChannelOverwrite = Readonly<{
id: string;
type: number;
allow: string;
deny: string;
}>;
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import UserPinnedDMStore from '@app/stores/UserPinnedDMStore';
import UserStore from '@app/stores/UserStore';
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
import type {Channel, ChannelOverwrite, DefaultReactionEmoji} from '@fluxer/schema/src/domains/channel/ChannelSchemas';
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import * as SnowflakeUtils from '@fluxer/snowflake/src/SnowflakeUtils';
export class ChannelOverwriteRecord {
readonly id: string;
@@ -66,40 +61,12 @@ export class ChannelOverwriteRecord {
}
}
export type DefaultReactionEmoji = Readonly<{
emoji_id: string | null;
emoji_name: string | null;
}>;
export type Channel = Readonly<{
id: string;
guild_id?: string;
name?: string;
topic?: string | null;
url?: string | null;
icon?: string | null;
owner_id?: string | null;
type: number;
position?: number;
parent_id?: string | null;
bitrate?: number | null;
user_limit?: number | null;
rtc_region?: string | null;
last_message_id?: string | null;
last_pin_timestamp?: string | null;
permission_overwrites?: ReadonlyArray<ChannelOverwrite>;
recipients?: ReadonlyArray<UserPartial>;
nsfw?: boolean;
rate_limit_per_user?: number;
nicks?: Readonly<Record<string, string>>;
flags?: number;
member_count?: number;
message_count?: number;
total_message_sent?: number;
default_reaction_emoji?: DefaultReactionEmoji | null;
}>;
interface ChannelRecordOptions {
instanceId?: string;
}
export class ChannelRecord {
readonly instanceId: string;
readonly id: string;
readonly guildId?: string;
readonly name?: string;
@@ -126,7 +93,8 @@ export class ChannelRecord {
readonly totalMessageSent?: number;
readonly defaultReactionEmoji?: DefaultReactionEmoji | null;
constructor(channel: Channel) {
constructor(channel: Channel, options?: ChannelRecordOptions) {
this.instanceId = options?.instanceId ?? RuntimeConfigStore.localInstanceDomain;
this.id = channel.id;
this.guildId = channel.guild_id;
this.name = channel.name;
@@ -257,35 +225,38 @@ export class ChannelRecord {
newRecipients = this.recipientIds.map((id) => UserStore.getUser(id)!.toJSON());
}
return new ChannelRecord({
id: this.id,
guild_id: updates.guild_id ?? this.guildId,
name: updates.name ?? this.name,
topic: updates.topic !== undefined ? updates.topic : this.topic,
url: updates.url !== undefined ? updates.url : this.url,
icon: updates.icon !== undefined ? updates.icon : this.icon,
owner_id: updates.owner_id !== undefined ? updates.owner_id : this.ownerId,
type: updates.type ?? this.type,
position: updates.position ?? this.position,
parent_id: updates.parent_id !== undefined ? updates.parent_id : this.parentId,
bitrate: updates.bitrate !== undefined ? updates.bitrate : this.bitrate,
user_limit: updates.user_limit !== undefined ? updates.user_limit : this.userLimit,
rtc_region: updates.rtc_region !== undefined ? updates.rtc_region : this.rtcRegion,
last_message_id: updates.last_message_id !== undefined ? updates.last_message_id : this.lastMessageId,
last_pin_timestamp: updates.last_pin_timestamp ?? this.lastPinTimestamp?.toISOString() ?? undefined,
permission_overwrites: !this.isPrivate()
? (updates.permission_overwrites ?? Object.values(this.permissionOverwrites).map((o) => o.toJSON()))
: undefined,
recipients: newRecipients.length > 0 ? newRecipients : undefined,
nsfw: updates.nsfw ?? this.nsfw,
rate_limit_per_user: updates.rate_limit_per_user ?? this.rateLimitPerUser,
nicks: updates.nicks ?? this.nicks,
flags: updates.flags ?? this.flags,
member_count: updates.member_count ?? this.memberCount,
message_count: updates.message_count ?? this.messageCount,
total_message_sent: updates.total_message_sent ?? this.totalMessageSent,
default_reaction_emoji: updates.default_reaction_emoji ?? this.defaultReactionEmoji,
});
return new ChannelRecord(
{
id: this.id,
guild_id: updates.guild_id ?? this.guildId,
name: updates.name ?? this.name,
topic: updates.topic !== undefined ? updates.topic : this.topic,
url: updates.url !== undefined ? updates.url : this.url,
icon: updates.icon !== undefined ? updates.icon : this.icon,
owner_id: updates.owner_id !== undefined ? updates.owner_id : this.ownerId,
type: updates.type ?? this.type,
position: updates.position ?? this.position,
parent_id: updates.parent_id !== undefined ? updates.parent_id : this.parentId,
bitrate: updates.bitrate !== undefined ? updates.bitrate : this.bitrate,
user_limit: updates.user_limit !== undefined ? updates.user_limit : this.userLimit,
rtc_region: updates.rtc_region !== undefined ? updates.rtc_region : this.rtcRegion,
last_message_id: updates.last_message_id !== undefined ? updates.last_message_id : this.lastMessageId,
last_pin_timestamp: updates.last_pin_timestamp ?? this.lastPinTimestamp?.toISOString() ?? undefined,
permission_overwrites: !this.isPrivate()
? (updates.permission_overwrites ?? Object.values(this.permissionOverwrites).map((o) => o.toJSON()))
: undefined,
recipients: newRecipients.length > 0 ? newRecipients : undefined,
nsfw: updates.nsfw ?? this.nsfw,
rate_limit_per_user: updates.rate_limit_per_user ?? this.rateLimitPerUser,
nicks: updates.nicks ?? this.nicks,
flags: updates.flags ?? this.flags,
member_count: updates.member_count ?? this.memberCount,
message_count: updates.message_count ?? this.messageCount,
total_message_sent: updates.total_message_sent ?? this.totalMessageSent,
default_reaction_emoji: updates.default_reaction_emoji ?? this.defaultReactionEmoji,
},
{instanceId: this.instanceId},
);
}
withOverwrite(overwrite: ChannelOverwriteRecord): ChannelRecord {
@@ -293,18 +264,22 @@ export class ChannelRecord {
return this;
}
return new ChannelRecord({
...this.toJSON(),
permission_overwrites: Object.values({
...this.permissionOverwrites,
[overwrite.id]: overwrite,
}).map((o) => o.toJSON()),
});
return new ChannelRecord(
{
...this.toJSON(),
permission_overwrites: Object.values({
...this.permissionOverwrites,
[overwrite.id]: overwrite,
}).map((o) => o.toJSON()),
},
{instanceId: this.instanceId},
);
}
equals(other: ChannelRecord): boolean {
if (this === other) return true;
if (this.instanceId !== other.instanceId) return false;
if (this.id !== other.id) return false;
if (this.guildId !== other.guildId) return false;
if (this.name !== other.name) return false;

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {ConnectionType} from '@fluxer/constants/src/ConnectionConstants';
import type {ConnectionResponse} from '@fluxer/schema/src/domains/connection/ConnectionSchemas';
export class ConnectionRecord {
readonly id: string;
readonly type: ConnectionType;
readonly name: string;
readonly verified: boolean;
readonly visibilityFlags: number;
readonly sortOrder: number;
constructor(connection: ConnectionResponse) {
this.id = connection.id;
this.type = connection.type;
this.name = connection.name;
this.verified = connection.verified;
this.visibilityFlags = connection.visibility_flags;
this.sortOrder = connection.sort_order;
}
equals(other: ConnectionRecord): boolean {
return (
this.id === other.id &&
this.type === other.type &&
this.name === other.name &&
this.verified === other.verified &&
this.visibilityFlags === other.visibilityFlags &&
this.sortOrder === other.sortOrder
);
}
toJSON(): ConnectionResponse {
return {
id: this.id,
type: this.type,
name: this.name,
verified: this.verified,
visibility_flags: this.visibilityFlags,
sort_order: this.sortOrder,
};
}
}

View File

@@ -25,6 +25,7 @@ export interface DeveloperApplicationBot {
bio?: string | null;
token?: string;
banner?: string | null;
flags?: number;
}
export interface DeveloperApplication {
@@ -64,6 +65,7 @@ export class DeveloperApplicationRecord implements DeveloperApplication {
bio: application.bot.bio ?? null,
token: application.bot.token,
banner: application.bot.banner ?? null,
flags: application.bot.flags,
};
}
}
@@ -89,7 +91,12 @@ export class DeveloperApplicationRecord implements DeveloperApplication {
bot_public: this.bot_public,
bot_require_code_grant: this.bot_require_code_grant,
client_secret: this.client_secret,
bot: this.bot ? {...this.bot} : undefined,
bot: this.bot
? {
...this.bot,
flags: this.bot.flags,
}
: undefined,
};
}
}

View File

@@ -17,7 +17,7 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import * as SnowflakeUtils from '~/utils/SnowflakeUtils';
import * as SnowflakeUtils from '@fluxer/snowflake/src/SnowflakeUtils';
export type FavoriteMeme = Readonly<{
id: string;
@@ -35,7 +35,8 @@ export type FavoriteMeme = Readonly<{
duration: number | null;
is_gifv: boolean;
url: string;
tenor_id: string | null;
klipy_slug: string | null;
tenor_slug_id: string | null;
}>;
export class FavoriteMemeRecord {
@@ -54,7 +55,8 @@ export class FavoriteMemeRecord {
readonly duration: number | null;
readonly isGifv: boolean;
readonly url: string;
readonly tenorId: string | null;
readonly klipySlug: string | null;
readonly tenorSlugId: string | null;
constructor(meme: FavoriteMeme) {
this.id = meme.id;
@@ -72,7 +74,8 @@ export class FavoriteMemeRecord {
this.duration = meme.duration;
this.isGifv = meme.is_gifv;
this.url = meme.url;
this.tenorId = meme.tenor_id;
this.klipySlug = meme.klipy_slug;
this.tenorSlugId = meme.tenor_slug_id;
}
get createdAtTimestamp(): number {
@@ -120,7 +123,8 @@ export class FavoriteMemeRecord {
this.duration === other.duration &&
this.isGifv === other.isGifv &&
this.url === other.url &&
this.tenorId === other.tenorId
this.klipySlug === other.klipySlug &&
this.tenorSlugId === other.tenorSlugId
);
}
@@ -141,7 +145,8 @@ export class FavoriteMemeRecord {
duration: this.duration,
is_gifv: this.isGifv,
url: this.url,
tenor_id: this.tenorId,
klipy_slug: this.klipySlug,
tenor_slug_id: this.tenorSlugId,
};
}
}

View File

@@ -17,19 +17,9 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserPartial} from '~/records/UserRecord';
import * as AvatarUtils from '~/utils/AvatarUtils';
export type GuildEmoji = Readonly<{
id: string;
name: string;
animated: boolean;
user?: UserPartial;
}>;
export interface GuildEmojiWithUser extends GuildEmoji {
user?: UserPartial;
}
import * as AvatarUtils from '@app/utils/AvatarUtils';
import type {GuildEmoji} from '@fluxer/schema/src/domains/guild/GuildEmojiSchemas';
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
export class GuildEmojiRecord {
readonly id: string;

View File

@@ -17,57 +17,44 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {GuildMemberProfileFlags} from '~/Constants';
import type {GuildRoleRecord} from '~/records/GuildRoleRecord';
import type {UserPartial} from '~/records/UserRecord';
import {UserRecord} from '~/records/UserRecord';
import AuthenticationStore from '~/stores/AuthenticationStore';
import GuildStore from '~/stores/GuildStore';
import UserStore from '~/stores/UserStore';
import * as ColorUtils from '~/utils/ColorUtils';
import type {GuildRoleRecord} from '@app/records/GuildRoleRecord';
import {UserRecord} from '@app/records/UserRecord';
import AuthenticationStore from '@app/stores/AuthenticationStore';
import GuildStore from '@app/stores/GuildStore';
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import UserStore from '@app/stores/UserStore';
import * as ColorUtils from '@app/utils/ColorUtils';
import {GuildMemberProfileFlags} from '@fluxer/constants/src/GuildConstants';
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
export type GuildMember = Readonly<{
user: UserPartial;
nick?: string | null;
avatar?: string | null;
banner?: string | null;
accent_color?: string | null;
roles: ReadonlyArray<string>;
joined_at: string;
join_source_type?: number | null;
source_invite_code?: string | null;
inviter_id?: string | null;
mute?: boolean;
deaf?: boolean;
communication_disabled_until?: string | null;
profile_flags?: number | null;
}>;
interface GuildMemberRecordOptions {
instanceId?: string;
}
export class GuildMemberRecord {
readonly instanceId: string;
readonly guildId: string;
readonly user: UserRecord;
readonly nick: string | null;
readonly avatar: string | null;
readonly banner: string | null;
readonly accentColor: string | null;
readonly accentColor: number | null;
readonly roles: ReadonlySet<string>;
readonly joinedAt: Date;
readonly joinSourceType: number | null;
readonly sourceInviteCode: string | null;
readonly inviterId: string | null;
readonly mute: boolean;
readonly deaf: boolean;
readonly communicationDisabledUntil: Date | null;
readonly profileFlags: number;
constructor(guildId: string, guildMember: GuildMember) {
constructor(guildId: string, guildMember: GuildMemberData, options?: GuildMemberRecordOptions) {
this.instanceId = options?.instanceId ?? RuntimeConfigStore.localInstanceDomain;
this.guildId = guildId;
const cachedUser = UserStore.getUser(guildMember.user.id);
if (cachedUser) {
this.user = cachedUser;
} else {
this.user = new UserRecord(guildMember.user);
this.user = new UserRecord(guildMember.user, {instanceId: this.instanceId});
UserStore.cacheUsers([this.user.toJSON()]);
}
@@ -77,9 +64,6 @@ export class GuildMemberRecord {
this.accentColor = guildMember.accent_color ?? null;
this.roles = new Set(guildMember.roles);
this.joinedAt = new Date(guildMember.joined_at);
this.joinSourceType = guildMember.join_source_type ?? null;
this.sourceInviteCode = guildMember.source_invite_code ?? null;
this.inviterId = guildMember.inviter_id ?? null;
this.mute = guildMember.mute ?? false;
this.deaf = guildMember.deaf ?? false;
this.communicationDisabledUntil = guildMember.communication_disabled_until
@@ -96,31 +80,36 @@ export class GuildMemberRecord {
return (this.profileFlags & GuildMemberProfileFlags.BANNER_UNSET) !== 0;
}
withUpdates(updates: Partial<GuildMember>): GuildMemberRecord {
return new GuildMemberRecord(this.guildId, {
user: updates.user ?? this.user.toJSON(),
nick: updates.nick ?? this.nick,
avatar: updates.avatar ?? this.avatar,
banner: updates.banner ?? this.banner,
accent_color: updates.accent_color ?? this.accentColor,
roles: updates.roles ?? Array.from(this.roles),
joined_at: updates.joined_at ?? this.joinedAt.toISOString(),
join_source_type: updates.join_source_type ?? this.joinSourceType,
source_invite_code: updates.source_invite_code ?? this.sourceInviteCode,
inviter_id: updates.inviter_id ?? this.inviterId,
mute: updates.mute ?? this.mute,
deaf: updates.deaf ?? this.deaf,
communication_disabled_until:
updates.communication_disabled_until ?? this.communicationDisabledUntil?.toISOString() ?? null,
profile_flags: updates.profile_flags ?? this.profileFlags,
});
withUpdates(updates: Partial<GuildMemberData>): GuildMemberRecord {
return new GuildMemberRecord(
this.guildId,
{
user: updates.user ?? this.user.toJSON(),
nick: updates.nick ?? this.nick,
avatar: updates.avatar ?? this.avatar,
banner: updates.banner ?? this.banner,
accent_color: updates.accent_color ?? this.accentColor,
roles: updates.roles ?? Array.from(this.roles),
joined_at: updates.joined_at ?? this.joinedAt.toISOString(),
mute: updates.mute ?? this.mute,
deaf: updates.deaf ?? this.deaf,
communication_disabled_until:
updates.communication_disabled_until ?? this.communicationDisabledUntil?.toISOString() ?? null,
profile_flags: updates.profile_flags ?? this.profileFlags,
},
{instanceId: this.instanceId},
);
}
withRoles(roles: Iterable<string>): GuildMemberRecord {
return new GuildMemberRecord(this.guildId, {
...this.toJSON(),
roles: Array.from(roles),
});
return new GuildMemberRecord(
this.guildId,
{
...this.toJSON(),
roles: Array.from(roles),
},
{instanceId: this.instanceId},
);
}
getSortedRoles(): ReadonlyArray<GuildRoleRecord> {
@@ -170,7 +159,7 @@ export class GuildMemberRecord {
return this.communicationDisabledUntil.getTime() > Date.now();
}
toJSON(): GuildMember {
toJSON(): GuildMemberData {
return {
user: this.user.toJSON(),
nick: this.nick,
@@ -179,9 +168,6 @@ export class GuildMemberRecord {
accent_color: this.accentColor,
roles: Array.from(this.roles),
joined_at: this.joinedAt.toISOString(),
join_source_type: this.joinSourceType,
source_invite_code: this.sourceInviteCode,
inviter_id: this.inviterId,
mute: this.mute,
deaf: this.deaf,
communication_disabled_until: this.communicationDisabledUntil?.toISOString() ?? null,

View File

@@ -17,80 +17,32 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {GuildSplashCardAlignmentValue} from '~/Constants';
import {GuildRoleRecord} from '@app/records/GuildRoleRecord';
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import type {GuildReadyData} from '@app/types/gateway/GatewayGuildTypes';
import {LARGE_GUILD_THRESHOLD} from '@fluxer/constants/src/GatewayConstants';
import type {GuildSplashCardAlignmentValue} from '@fluxer/constants/src/GuildConstants';
import {GuildFeatures, GuildSplashCardAlignment} from '@fluxer/constants/src/GuildConstants';
import {
GuildFeatures,
GuildSplashCardAlignment,
LARGE_GUILD_THRESHOLD,
MAX_GUILD_EMOJIS_ANIMATED,
MAX_GUILD_EMOJIS_ANIMATED_MORE_EMOJI,
MAX_GUILD_EMOJIS_STATIC,
MAX_GUILD_EMOJIS_STATIC_MORE_EMOJI,
MAX_GUILD_STICKERS,
MAX_GUILD_STICKERS_MORE_STICKERS,
MessageNotifications,
} from '~/Constants';
import type {Channel} from '~/records/ChannelRecord';
import type {GuildEmoji} from '~/records/GuildEmojiRecord';
import type {GuildMember} from '~/records/GuildMemberRecord';
import type {GuildRole} from '~/records/GuildRoleRecord';
import {GuildRoleRecord} from '~/records/GuildRoleRecord';
import type {GuildSticker} from '~/records/GuildStickerRecord';
import type {Presence} from '~/stores/PresenceStore';
import type {VoiceState} from '~/stores/voice/MediaEngineFacade';
import * as SnowflakeUtils from '~/utils/SnowflakeUtils';
} from '@fluxer/constants/src/LimitConstants';
import {MessageNotifications} from '@fluxer/constants/src/NotificationConstants';
import type {Guild} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
import * as SnowflakeUtils from '@fluxer/snowflake/src/SnowflakeUtils';
export type Guild = Readonly<{
id: string;
name: string;
icon: string | null;
banner?: string | null;
banner_width?: number | null;
banner_height?: number | null;
splash?: string | null;
splash_width?: number | null;
splash_height?: number | null;
splash_card_alignment?: GuildSplashCardAlignmentValue;
embed_splash?: string | null;
embed_splash_width?: number | null;
embed_splash_height?: number | null;
vanity_url_code: string | null;
owner_id: string;
system_channel_id: string | null;
system_channel_flags?: number;
rules_channel_id?: string | null;
afk_channel_id?: string | null;
afk_timeout?: number;
features: ReadonlyArray<string>;
verification_level?: number;
mfa_level?: number;
nsfw_level?: number;
explicit_content_filter?: number;
default_message_notifications?: number;
disabled_operations?: number;
joined_at?: string;
unavailable?: boolean;
member_count?: number;
}>;
export type GuildReadyData = Readonly<{
id: string;
properties: Omit<Guild, 'roles'>;
channels: ReadonlyArray<Channel>;
emojis: ReadonlyArray<GuildEmoji>;
stickers?: ReadonlyArray<GuildSticker>;
members: ReadonlyArray<GuildMember>;
member_count: number;
presences?: ReadonlyArray<Presence>;
voice_states?: ReadonlyArray<VoiceState>;
roles: ReadonlyArray<GuildRole>;
joined_at: string;
unavailable?: boolean;
}>;
interface GuildRecordOptions {
instanceId?: string;
}
type GuildInput = Guild | GuildRecord;
export class GuildRecord {
readonly instanceId: string;
readonly id: string;
readonly name: string;
readonly icon: string | null;
@@ -121,9 +73,12 @@ export class GuildRecord {
private readonly _disabledOperations: number;
readonly joinedAt: string | null;
readonly unavailable: boolean;
readonly messageHistoryCutoff: string | null;
readonly memberCount: number;
constructor(guild: GuildInput) {
constructor(guild: GuildInput, options?: GuildRecordOptions) {
this.instanceId =
options?.instanceId ?? (guild instanceof GuildRecord ? guild.instanceId : RuntimeConfigStore.localInstanceDomain);
this.id = guild.id;
this.name = guild.name;
this.icon = guild.icon;
@@ -153,6 +108,7 @@ export class GuildRecord {
this.defaultMessageNotifications = this.normalizeDefaultMessageNotifications(guild);
this._disabledOperations = this.normalizeDisabledOperations(guild);
this.joinedAt = this.normalizeJoinedAt(guild);
this.messageHistoryCutoff = this.normalizeMessageHistoryCutoff(guild);
this.unavailable = guild.unavailable ?? false;
this.memberCount = this.normalizeMemberCount(guild);
}
@@ -274,6 +230,10 @@ export class GuildRecord {
return this.normalizeField(guild, 'joined_at', 'joinedAt');
}
private normalizeMessageHistoryCutoff(guild: GuildInput): string | null {
return this.normalizeField(guild, 'message_history_cutoff', 'messageHistoryCutoff');
}
private normalizeMemberCount(guild: GuildInput): number {
if (this.isGuildInput(guild)) {
const value = (guild as Guild).member_count;
@@ -290,7 +250,7 @@ export class GuildRecord {
return this._disabledOperations;
}
static fromGuildReadyData(guildData: GuildReadyData): GuildRecord {
static fromGuildReadyData(guildData: GuildReadyData, instanceId?: string): GuildRecord {
const roles = Object.freeze(
guildData.roles.reduce<Record<string, GuildRoleRecord>>(
(acc, role) => ({
@@ -302,12 +262,15 @@ export class GuildRecord {
),
);
return new GuildRecord({
...guildData.properties,
roles,
joined_at: guildData.joined_at,
unavailable: guildData.unavailable,
});
return new GuildRecord(
{
...guildData.properties,
roles,
joined_at: guildData.joined_at,
unavailable: guildData.unavailable,
},
{instanceId},
);
}
toJSON(): Guild & {
@@ -341,6 +304,7 @@ export class GuildRecord {
explicit_content_filter: this.explicitContentFilter,
default_message_notifications: this.defaultMessageNotifications,
disabled_operations: this._disabledOperations,
message_history_cutoff: this.messageHistoryCutoff,
joined_at: this.joinedAt ?? undefined,
unavailable: this.unavailable,
member_count: this.memberCount,
@@ -349,44 +313,54 @@ export class GuildRecord {
}
withUpdates(guild: Partial<Guild>): GuildRecord {
return new GuildRecord({
...this,
name: guild.name ?? this.name,
icon: guild.icon ?? this.icon,
banner: guild.banner ?? this.banner,
bannerWidth: guild.banner_width ?? this.bannerWidth,
bannerHeight: guild.banner_height ?? this.bannerHeight,
splash: guild.splash ?? this.splash,
splashWidth: guild.splash_width ?? this.splashWidth,
splashHeight: guild.splash_height ?? this.splashHeight,
splashCardAlignment: guild.splash_card_alignment ?? this.splashCardAlignment,
embedSplash: guild.embed_splash ?? this.embedSplash,
embedSplashWidth: guild.embed_splash_width ?? this.embedSplashWidth,
embedSplashHeight: guild.embed_splash_height ?? this.embedSplashHeight,
features: guild.features ? new Set(guild.features) : this.features,
vanityURLCode: guild.vanity_url_code ?? this.vanityURLCode,
ownerId: guild.owner_id ?? this.ownerId,
systemChannelId: guild.system_channel_id ?? this.systemChannelId,
systemChannelFlags: guild.system_channel_flags ?? this.systemChannelFlags,
rulesChannelId: guild.rules_channel_id ?? this.rulesChannelId,
afkChannelId: guild.afk_channel_id ?? this.afkChannelId,
afkTimeout: guild.afk_timeout ?? this.afkTimeout,
verificationLevel: guild.verification_level ?? this.verificationLevel,
mfaLevel: guild.mfa_level ?? this.mfaLevel,
nsfwLevel: guild.nsfw_level ?? this.nsfwLevel,
explicitContentFilter: guild.explicit_content_filter ?? this.explicitContentFilter,
defaultMessageNotifications: guild.default_message_notifications ?? this.defaultMessageNotifications,
disabledOperations: guild.disabled_operations ?? this.disabledOperations,
unavailable: guild.unavailable ?? this.unavailable,
memberCount: guild.member_count ?? this.memberCount,
});
return new GuildRecord(
{
...this,
name: guild.name ?? this.name,
icon: guild.icon ?? this.icon,
banner: guild.banner ?? this.banner,
bannerWidth: guild.banner_width ?? this.bannerWidth,
bannerHeight: guild.banner_height ?? this.bannerHeight,
splash: guild.splash ?? this.splash,
splashWidth: guild.splash_width ?? this.splashWidth,
splashHeight: guild.splash_height ?? this.splashHeight,
splashCardAlignment: guild.splash_card_alignment ?? this.splashCardAlignment,
embedSplash: guild.embed_splash ?? this.embedSplash,
embedSplashWidth: guild.embed_splash_width ?? this.embedSplashWidth,
embedSplashHeight: guild.embed_splash_height ?? this.embedSplashHeight,
features: guild.features ? new Set(guild.features) : this.features,
vanityURLCode: guild.vanity_url_code ?? this.vanityURLCode,
ownerId: guild.owner_id ?? this.ownerId,
systemChannelId: guild.system_channel_id ?? this.systemChannelId,
systemChannelFlags: guild.system_channel_flags ?? this.systemChannelFlags,
rulesChannelId: guild.rules_channel_id ?? this.rulesChannelId,
afkChannelId: guild.afk_channel_id ?? this.afkChannelId,
afkTimeout: guild.afk_timeout ?? this.afkTimeout,
verificationLevel: guild.verification_level ?? this.verificationLevel,
mfaLevel: guild.mfa_level ?? this.mfaLevel,
nsfwLevel: guild.nsfw_level ?? this.nsfwLevel,
explicitContentFilter: guild.explicit_content_filter ?? this.explicitContentFilter,
defaultMessageNotifications: guild.default_message_notifications ?? this.defaultMessageNotifications,
disabledOperations: guild.disabled_operations ?? this.disabledOperations,
messageHistoryCutoff:
guild.message_history_cutoff !== undefined
? (guild.message_history_cutoff ?? null)
: this.messageHistoryCutoff,
unavailable: guild.unavailable ?? this.unavailable,
memberCount: guild.member_count ?? this.memberCount,
},
{instanceId: this.instanceId},
);
}
withRoles(roles: Record<string, GuildRoleRecord>): GuildRecord {
return new GuildRecord({
...this,
roles: Object.freeze({...roles}),
});
return new GuildRecord(
{
...this,
roles: Object.freeze({...roles}),
},
{instanceId: this.instanceId},
);
}
addRole(role: GuildRoleRecord): GuildRecord {
@@ -451,7 +425,11 @@ export class GuildRecord {
}
get isLargeGuild(): boolean {
return this.features.has(GuildFeatures.LARGE_GUILD_OVERRIDE) || this.memberCount > LARGE_GUILD_THRESHOLD;
return (
this.features.has(GuildFeatures.LARGE_GUILD_OVERRIDE) ||
this.features.has(GuildFeatures.VERY_LARGE_GUILD) ||
this.memberCount > LARGE_GUILD_THRESHOLD
);
}
get effectiveMessageNotifications(): number {

View File

@@ -17,18 +17,15 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export interface GuildRole {
readonly id: string;
readonly name: string;
readonly color: number;
readonly position: number;
readonly hoist_position?: number | null;
readonly permissions: string;
readonly hoist: boolean;
readonly mentionable: boolean;
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import type {GuildRole} from '@fluxer/schema/src/domains/guild/GuildRoleSchemas';
interface GuildRoleRecordOptions {
instanceId?: string;
}
export class GuildRoleRecord {
readonly instanceId: string;
readonly id: string;
readonly guildId: string;
readonly name: string;
@@ -39,7 +36,8 @@ export class GuildRoleRecord {
readonly hoist: boolean;
readonly mentionable: boolean;
constructor(guildId: string, guildRole: GuildRole) {
constructor(guildId: string, guildRole: GuildRole, options?: GuildRoleRecordOptions) {
this.instanceId = options?.instanceId ?? RuntimeConfigStore.localInstanceDomain;
this.id = guildRole.id;
this.guildId = guildId;
this.name = guildRole.name;
@@ -56,16 +54,20 @@ export class GuildRoleRecord {
}
withUpdates(updates: Partial<GuildRole>): GuildRoleRecord {
return new GuildRoleRecord(this.guildId, {
id: this.id,
name: updates.name ?? this.name,
color: updates.color ?? this.color,
position: updates.position ?? this.position,
hoist_position: updates.hoist_position !== undefined ? updates.hoist_position : this.hoistPosition,
permissions: updates.permissions ?? this.permissions.toString(),
hoist: updates.hoist ?? this.hoist,
mentionable: updates.mentionable ?? this.mentionable,
});
return new GuildRoleRecord(
this.guildId,
{
id: this.id,
name: updates.name ?? this.name,
color: updates.color ?? this.color,
position: updates.position ?? this.position,
hoist_position: updates.hoist_position !== undefined ? updates.hoist_position : this.hoistPosition,
permissions: updates.permissions ?? this.permissions.toString(),
hoist: updates.hoist ?? this.hoist,
mentionable: updates.mentionable ?? this.mentionable,
},
{instanceId: this.instanceId},
);
}
get isEveryone(): boolean {
@@ -74,6 +76,7 @@ export class GuildRoleRecord {
equals(other: GuildRoleRecord): boolean {
return (
this.instanceId === other.instanceId &&
this.id === other.id &&
this.guildId === other.guildId &&
this.name === other.name &&

View File

@@ -17,26 +17,9 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {StickerFormatTypes} from '~/Constants';
import type {UserPartial} from '~/records/UserRecord';
import * as AvatarUtils from '~/utils/AvatarUtils';
export type GuildSticker = Readonly<{
id: string;
name: string;
description: string;
tags: Array<string>;
format_type: number;
user?: UserPartial;
}>;
export interface GuildStickerWithUser extends GuildSticker {
user?: UserPartial;
}
export function isStickerAnimated(sticker: GuildSticker) {
return sticker.format_type === StickerFormatTypes.GIF;
}
import * as AvatarUtils from '@app/utils/AvatarUtils';
import type {GuildSticker} from '@fluxer/schema/src/domains/guild/GuildEmojiSchemas';
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
export class GuildStickerRecord {
readonly id: string;
@@ -45,7 +28,7 @@ export class GuildStickerRecord {
readonly description: string;
readonly tags: ReadonlyArray<string>;
readonly url: string;
readonly formatType: number;
readonly animated: boolean;
readonly user?: UserPartial;
constructor(guildId: string, data: GuildSticker) {
@@ -56,24 +39,20 @@ export class GuildStickerRecord {
this.tags = Object.freeze([...data.tags]);
this.url = AvatarUtils.getStickerURL({
id: data.id,
animated: isStickerAnimated(data),
animated: data.animated,
size: 320,
});
this.formatType = data.format_type;
this.animated = data.animated;
this.user = data.user;
}
isAnimated() {
return isStickerAnimated(this.toJSON());
}
withUpdates(updates: Partial<GuildSticker>): GuildStickerRecord {
return new GuildStickerRecord(this.guildId, {
id: updates.id ?? this.id,
name: updates.name ?? this.name,
description: updates.description ?? this.description,
tags: updates.tags ?? [...this.tags],
format_type: updates.format_type ?? this.formatType,
animated: updates.animated ?? this.animated,
user: updates.user ?? this.user,
});
}
@@ -85,7 +64,7 @@ export class GuildStickerRecord {
this.name === other.name &&
this.description === other.description &&
JSON.stringify(this.tags) === JSON.stringify(other.tags) &&
this.formatType === other.formatType &&
this.animated === other.animated &&
this.user?.id === other.user?.id
);
}
@@ -96,7 +75,7 @@ export class GuildStickerRecord {
name: this.name,
description: this.description,
tags: [...this.tags],
format_type: this.formatType,
animated: this.animated,
user: this.user,
};
}

View File

@@ -17,193 +17,35 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {MessageFlags, MessageStates, MessageTypes} from '~/Constants';
import type {GuildMember} from '~/records/GuildMemberRecord';
import type {UserPartial} from '~/records/UserRecord';
import {UserRecord} from '~/records/UserRecord';
import AuthenticationStore from '~/stores/AuthenticationStore';
import ChannelStore from '~/stores/ChannelStore';
import GuildMemberStore from '~/stores/GuildMemberStore';
import GuildStore from '~/stores/GuildStore';
import RelationshipStore from '~/stores/RelationshipStore';
import UserStore from '~/stores/UserStore';
import type {Invite as InviteType} from '~/types/InviteTypes';
import * as GiftCodeUtils from '~/utils/giftCodeUtils';
import * as InviteUtils from '~/utils/InviteUtils';
import {emojiEquals, type ReactionEmoji} from '~/utils/ReactionUtils';
import * as ThemeUtils from '~/utils/ThemeUtils';
export type Invite = InviteType;
export interface EmbedAuthor {
name: string;
url?: string;
icon_url?: string;
proxy_icon_url?: string;
}
export interface EmbedFooter {
text: string;
icon_url?: string;
proxy_icon_url?: string;
}
export interface EmbedMedia {
url: string;
proxy_url?: string;
content_type?: string;
content_hash?: string | null;
width?: number;
height?: number;
placeholder?: string;
flags: number;
description?: string;
duration?: number;
nsfw?: boolean;
}
export interface EmbedField {
name: string;
value: string;
inline: boolean;
}
export interface MessageEmbed {
id: string;
type: string;
url?: string;
title?: string;
color?: number;
timestamp?: string;
description?: string;
author?: EmbedAuthor;
image?: EmbedMedia;
thumbnail?: EmbedMedia;
footer?: EmbedFooter;
fields?: ReadonlyArray<EmbedField>;
provider?: EmbedAuthor;
video?: EmbedMedia;
audio?: EmbedMedia;
flags?: number;
}
export interface MessageReference {
message_id: string;
channel_id: string;
guild_id?: string;
type?: number;
}
export interface MessageReaction {
emoji: ReactionEmoji;
count: number;
me?: true;
me_burst?: boolean;
count_details?: {
burst: number;
normal: number;
};
}
export interface MessageAttachment {
id: string;
filename: string;
title?: string;
description?: string;
caption?: string;
content_type?: string;
size: number;
url: string | null;
proxy_url: string | null;
width?: number;
height?: number;
placeholder?: string;
placeholder_version?: number;
flags: number;
duration_secs?: number;
duration?: number;
waveform?: string;
content_hash?: string | null;
nsfw?: boolean;
expires_at?: string | null;
expired?: boolean;
}
export interface MessageCall {
participants: Array<string>;
ended_timestamp?: string | null;
}
export interface MessageSnapshot {
type: number;
content: string;
embeds?: ReadonlyArray<MessageEmbed>;
attachments?: ReadonlyArray<MessageAttachment>;
timestamp: string;
}
export interface MessageStickerItem {
id: string;
name: string;
format_type: number;
}
export interface ChannelMention {
id: string;
guild_id: string;
type: number;
name: string;
parent_id?: string | null;
}
export interface AllowedMentions {
parse?: ReadonlyArray<'roles' | 'users' | 'everyone'>;
roles?: ReadonlyArray<string>;
users?: ReadonlyArray<string>;
replied_user?: boolean;
}
export interface MessageMention extends UserPartial {
member?: Omit<GuildMember, 'user'>;
}
export type Message = Readonly<{
id: string;
channel_id: string;
guild_id?: string;
author: UserPartial;
member?: Omit<GuildMember, 'user'>;
webhook_id?: string;
application_id?: string;
type: number;
flags: number;
pinned: boolean;
tts?: boolean;
mention_everyone: boolean;
content: string;
timestamp: string;
edited_timestamp?: string;
mentions?: ReadonlyArray<MessageMention>;
mention_roles?: ReadonlyArray<string>;
mention_channels?: ReadonlyArray<ChannelMention>;
embeds?: ReadonlyArray<MessageEmbed>;
attachments?: ReadonlyArray<MessageAttachment>;
stickers?: ReadonlyArray<MessageStickerItem>;
reactions?: ReadonlyArray<MessageReaction>;
message_reference?: MessageReference;
referenced_message?: Message | null;
message_snapshots?: ReadonlyArray<MessageSnapshot>;
call?: MessageCall | null;
state?: string;
nonce?: string;
blocked?: boolean;
loggingName?: string;
_allowedMentions?: AllowedMentions;
_favoriteMemeId?: string;
}>;
import {UserRecord} from '@app/records/UserRecord';
import AuthenticationStore from '@app/stores/AuthenticationStore';
import ChannelStore from '@app/stores/ChannelStore';
import GuildMemberStore from '@app/stores/GuildMemberStore';
import GuildStore from '@app/stores/GuildStore';
import RelationshipStore from '@app/stores/RelationshipStore';
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import UserStore from '@app/stores/UserStore';
import * as GiftCodeUtils from '@app/utils/GiftCodeUtils';
import * as InviteUtils from '@app/utils/InviteUtils';
import {emojiEquals} from '@app/utils/ReactionUtils';
import * as ThemeUtils from '@app/utils/ThemeUtils';
import {MessageFlags, MessageStates, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
import type {MessageEmbed} from '@fluxer/schema/src/domains/message/EmbedSchemas';
import type {
AllowedMentions,
ChannelMention,
Message,
MessageAttachment,
MessageCall,
MessageReaction,
MessageReference,
MessageSnapshot,
MessageStickerItem,
ReactionEmoji,
} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
interface TransformedMessageCall {
participants: Array<string>;
participants: ReadonlyArray<string>;
endedTimestamp: Date | null;
}
@@ -249,15 +91,16 @@ const getOrCreateEmbedId = (embed: Omit<MessageEmbed, 'id'>): string => {
interface MessageRecordOptions {
skipUserCache?: boolean;
instanceId?: string;
}
export class MessageRecord {
readonly instanceId: string;
readonly id: string;
readonly channelId: string;
readonly guildId?: string;
readonly author: UserRecord;
readonly webhookId?: string;
readonly applicationId?: string;
readonly type: number;
readonly flags: number;
readonly pinned: boolean;
@@ -290,23 +133,31 @@ export class MessageRecord {
readonly stickers?: ReadonlyArray<MessageStickerItem>;
constructor(message: Message, options?: MessageRecordOptions) {
this.instanceId = options?.instanceId ?? RuntimeConfigStore.localInstanceDomain;
const shouldCacheAuthor = !message.webhook_id;
if (!options?.skipUserCache) {
UserStore.cacheUsers([message.author, ...(message.mentions ?? [])]);
const authorsToCache = [...(shouldCacheAuthor ? [message.author] : []), ...(message.mentions ?? [])].filter(
Boolean,
);
if (authorsToCache.length > 0) {
UserStore.cacheUsers(authorsToCache);
}
}
const isBlocked = RelationshipStore.isBlocked(message.author.id);
if (message.webhook_id) {
this.author = new UserRecord(message.author);
this.author = new UserRecord(message.author, {instanceId: this.instanceId});
} else {
this.author = UserStore.getUser(message.author.id) || new UserRecord(message.author);
this.author =
UserStore.getUser(message.author.id) || new UserRecord(message.author, {instanceId: this.instanceId});
}
this.id = message.id;
this.channelId = message.channel_id;
this.guildId = message.guild_id;
this.webhookId = message.webhook_id;
this.applicationId = message.application_id;
this.type = message.type;
this.flags = message.flags;
this.pinned = message.pinned;
@@ -371,6 +222,10 @@ export class MessageRecord {
);
}
isClientSystemMessage(): boolean {
return this.type === MessageTypes.CLIENT_SYSTEM;
}
isSystemMessage(): boolean {
return !this.isUserMessage();
}
@@ -411,7 +266,6 @@ export class MessageRecord {
guild_id: updates.guild_id ?? this.guildId,
author: updates.author ?? this.author.toJSON(),
webhook_id: updates.webhook_id ?? this.webhookId,
application_id: updates.application_id ?? this.applicationId,
type: updates.type ?? this.type,
flags: updates.flags ?? this.flags,
pinned: updates.pinned ?? this.pinned,
@@ -435,7 +289,7 @@ export class MessageRecord {
blocked: updates.blocked ?? this.blocked,
loggingName: updates.loggingName ?? this.loggingName,
},
{skipUserCache: true},
{skipUserCache: true, instanceId: this.instanceId},
);
}
@@ -502,6 +356,7 @@ export class MessageRecord {
equals(other: MessageRecord): boolean {
if (this === other) return true;
if (this.instanceId !== other.instanceId) return false;
if (this.id !== other.id) return false;
if (this.channelId !== other.channelId) return false;
if (this.guildId !== other.guildId) return false;
@@ -514,7 +369,6 @@ export class MessageRecord {
if (this.nonce !== other.nonce) return false;
if (this.blocked !== other.blocked) return false;
if (this.webhookId !== other.webhookId) return false;
if (this.applicationId !== other.applicationId) return false;
if (this.loggingName !== other.loggingName) return false;
if (this.timestamp.getTime() !== other.timestamp.getTime()) return false;
@@ -574,7 +428,7 @@ export class MessageRecord {
for (let i = 0; i < this.stickerItems.length; i++) {
const s1 = this.stickerItems[i];
const s2 = other.stickerItems[i];
if (s1.id !== s2.id || s1.name !== s2.name || s1.format_type !== s2.format_type) {
if (s1.id !== s2.id || s1.name !== s2.name || s1.animated !== s2.animated) {
return false;
}
}
@@ -655,7 +509,6 @@ export class MessageRecord {
guild_id: this.guildId,
author: this.author.toJSON(),
webhook_id: this.webhookId,
application_id: this.applicationId,
type: this.type,
flags: this.flags,
pinned: this.pinned,

View File

@@ -17,14 +17,21 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {GuildMember, GuildMemberRecord} from '~/records/GuildMemberRecord';
import type {GuildRecord} from '~/records/GuildRecord';
import type {UserPartial, UserProfile} from '~/records/UserRecord';
import GuildMemberStore from '~/stores/GuildMemberStore';
import GuildStore from '~/stores/GuildStore';
import UserStore from '~/stores/UserStore';
import type {GuildMemberRecord} from '@app/records/GuildMemberRecord';
import type {GuildRecord} from '@app/records/GuildRecord';
import GuildMemberStore from '@app/stores/GuildMemberStore';
import GuildStore from '@app/stores/GuildStore';
import UserStore from '@app/stores/UserStore';
import type {ConnectionResponse} from '@fluxer/schema/src/domains/connection/ConnectionSchemas';
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import type {UserPartial, UserProfile} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
export type ProfileMutualGuild = Readonly<{
export interface ProfileMutualGuild {
id: string;
nick: string | null;
}
export type MiniGuildMember = Readonly<{
id: string;
nick: string | null;
}>;
@@ -34,12 +41,13 @@ export type Profile = Readonly<{
user_profile: UserProfile;
guild_member_profile?: UserProfile | null;
timezone_offset: number | null;
guild_member?: GuildMember;
guild_member?: GuildMemberData;
premium_type?: number;
premium_since?: string;
premium_lifetime_sequence?: number;
mutual_friends?: Array<UserPartial>;
mutual_guilds?: Array<ProfileMutualGuild>;
connected_accounts?: Array<ConnectionResponse>;
}>;
export class ProfileRecord {
@@ -53,6 +61,7 @@ export class ProfileRecord {
readonly premiumLifetimeSequence: number | null;
readonly mutualFriends: ReadonlyArray<UserPartial> | null;
readonly mutualGuilds: ReadonlyArray<ProfileMutualGuild> | null;
readonly connectedAccounts: ReadonlyArray<ConnectionResponse> | null;
constructor(profile: Profile, guildId?: string) {
this.userId = profile.user.id;
@@ -65,6 +74,7 @@ export class ProfileRecord {
this.premiumLifetimeSequence = profile.premium_lifetime_sequence ?? null;
this.mutualFriends = profile.mutual_friends ? Object.freeze([...profile.mutual_friends]) : null;
this.mutualGuilds = profile.mutual_guilds ? Object.freeze([...profile.mutual_guilds]) : null;
this.connectedAccounts = profile.connected_accounts ? Object.freeze([...profile.connected_accounts]) : null;
}
withUpdates(updates: Partial<Profile>): ProfileRecord {
@@ -85,6 +95,8 @@ export class ProfileRecord {
: (this.premiumLifetimeSequence ?? undefined),
mutual_friends: updates.mutual_friends ?? (this.mutualFriends ? [...this.mutualFriends] : undefined),
mutual_guilds: updates.mutual_guilds ?? (this.mutualGuilds ? [...this.mutualGuilds] : undefined),
connected_accounts:
updates.connected_accounts ?? (this.connectedAccounts ? [...this.connectedAccounts] : undefined),
},
this.guildId ?? undefined,
);
@@ -139,13 +151,14 @@ export class ProfileRecord {
this.premiumSince === other.premiumSince &&
this.premiumLifetimeSequence === other.premiumLifetimeSequence &&
JSON.stringify(this.mutualFriends) === JSON.stringify(other.mutualFriends) &&
JSON.stringify(this.mutualGuilds) === JSON.stringify(other.mutualGuilds)
JSON.stringify(this.mutualGuilds) === JSON.stringify(other.mutualGuilds) &&
JSON.stringify(this.connectedAccounts) === JSON.stringify(other.connectedAccounts)
);
}
toJSON(): Profile {
return {
user: UserStore.getUser(this.userId)!,
user: UserStore.getUser(this.userId)!.toJSON(),
user_profile: {...this.userProfile},
guild_member_profile: this.guildMemberProfile ? {...this.guildMemberProfile} : undefined,
timezone_offset: this.timezoneOffset,
@@ -154,6 +167,7 @@ export class ProfileRecord {
premium_lifetime_sequence: this.premiumLifetimeSequence ?? undefined,
mutual_friends: this.mutualFriends ? [...this.mutualFriends] : undefined,
mutual_guilds: this.mutualGuilds ? [...this.mutualGuilds] : undefined,
connected_accounts: this.connectedAccounts ? [...this.connectedAccounts] : undefined,
};
}
}

View File

@@ -17,8 +17,10 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserPartial, UserRecord} from '~/records/UserRecord';
import UserStore from '~/stores/UserStore';
import type {UserRecord} from '@app/records/UserRecord';
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import UserStore from '@app/stores/UserStore';
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
export type Relationship = Readonly<{
id: string;
@@ -28,14 +30,20 @@ export type Relationship = Readonly<{
nickname?: string | null;
}>;
interface RelationshipRecordOptions {
instanceId?: string;
}
export class RelationshipRecord {
readonly instanceId: string;
readonly id: string;
readonly type: number;
readonly userId: string;
readonly since: Date;
readonly nickname: string | null;
constructor(relationship: Relationship) {
constructor(relationship: Relationship, options?: RelationshipRecordOptions) {
this.instanceId = options?.instanceId ?? RuntimeConfigStore.localInstanceDomain;
if (relationship.user) {
UserStore.cacheUsers([relationship.user]);
this.userId = relationship.user.id;
@@ -60,12 +68,15 @@ export class RelationshipRecord {
}
: this.user?.toJSON();
return new RelationshipRecord({
id: relationship.id ?? this.id,
type: relationship.type ?? this.type,
since: relationship.since ?? this.since.toISOString(),
nickname: relationship.nickname ?? this.nickname,
user: mergedUser,
});
return new RelationshipRecord(
{
id: relationship.id ?? this.id,
type: relationship.type ?? this.type,
since: relationship.since ?? this.since.toISOString(),
nickname: relationship.nickname ?? this.nickname,
user: mergedUser,
},
{instanceId: this.instanceId},
);
}
}

View File

@@ -17,9 +17,10 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {type Message, MessageRecord} from '~/records/MessageRecord';
import {MessageRecord} from '@app/records/MessageRecord';
import type {Message} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
export interface SavedMessageEntryResponse {
export interface SavedMessageEntry {
id: string;
channel_id: string;
message_id: string;
@@ -42,7 +43,7 @@ export class SavedMessageEntryRecord {
readonly status: SavedMessageStatus;
readonly message: MessageRecord | null;
constructor(data: SavedMessageEntryResponse) {
constructor(data: SavedMessageEntry) {
this.id = data.id;
this.channelId = data.channel_id;
this.messageId = data.message_id;
@@ -50,7 +51,7 @@ export class SavedMessageEntryRecord {
this.message = data.message ? new MessageRecord(data.message) : null;
}
static fromResponse(response: SavedMessageEntryResponse): SavedMessageEntryRecord {
static fromResponse(response: SavedMessageEntry): SavedMessageEntryRecord {
return new SavedMessageEntryRecord(response);
}

View File

@@ -17,97 +17,26 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import DeveloperOptionsStore from '@app/stores/DeveloperOptionsStore';
import RuntimeConfigStore from '@app/stores/RuntimeConfigStore';
import {getMaxAttachmentFileSize} from '@app/utils/AttachmentUtils';
import {LimitResolver} from '@app/utils/limits/LimitResolverAdapter';
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import {
MAX_BIO_LENGTH_NON_PREMIUM,
MAX_BIO_LENGTH_PREMIUM,
MAX_ATTACHMENTS_PER_MESSAGE,
MAX_BIO_LENGTH,
MAX_BOOKMARKS_NON_PREMIUM,
MAX_BOOKMARKS_PREMIUM,
MAX_FAVORITE_MEME_TAGS,
MAX_FAVORITE_MEMES_NON_PREMIUM,
MAX_FAVORITE_MEMES_PREMIUM,
MAX_GROUP_DM_RECIPIENTS,
MAX_GUILDS_NON_PREMIUM,
MAX_GUILDS_PREMIUM,
MAX_MESSAGE_LENGTH_NON_PREMIUM,
MAX_MESSAGE_LENGTH_PREMIUM,
UserFlags,
} from '~/Constants';
import DeveloperOptionsStore from '~/stores/DeveloperOptionsStore';
import RuntimeConfigStore from '~/stores/RuntimeConfigStore';
import {getAttachmentMaxSize} from '~/utils/AttachmentUtils';
import * as SnowflakeUtils from '~/utils/SnowflakeUtils';
export type BackupCode = Readonly<{
code: string;
consumed: boolean;
}>;
export type UserProfile = Readonly<{
bio: string | null;
banner: string | null;
banner_color?: number | null;
pronouns: string | null;
accent_color: string | null;
}>;
export type UserPartial = Readonly<{
id: string;
username: string;
discriminator: string;
global_name?: string | null;
avatar: string | null;
avatar_color?: number | null;
bot?: boolean;
system?: boolean;
flags: number;
}>;
export type RequiredAction =
| 'REQUIRE_VERIFIED_EMAIL'
| 'REQUIRE_REVERIFIED_EMAIL'
| 'REQUIRE_VERIFIED_PHONE'
| 'REQUIRE_REVERIFIED_PHONE'
| 'REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE'
| 'REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE'
| 'REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE'
| 'REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE';
export type UserPrivate = Readonly<
UserPartial &
UserProfile & {
email: string | null;
mfa_enabled: boolean;
phone: string | null;
authenticator_types: Array<number>;
verified: boolean;
premium_type: number | null;
premium_since: string | null;
premium_until: string | null;
premium_will_cancel: boolean;
premium_billing_cycle: string | null;
premium_lifetime_sequence: number | null;
premium_badge_hidden: boolean;
premium_badge_masked: boolean;
premium_badge_timestamp_hidden: boolean;
premium_badge_sequence_hidden: boolean;
premium_purchase_disabled: boolean;
premium_enabled_override: boolean;
password_last_changed_at: string | null;
required_actions: Array<RequiredAction> | null;
nsfw_allowed: boolean;
pending_manual_verification: boolean;
pending_bulk_message_deletion: {
scheduled_at: string;
channel_count: number;
message_count: number;
} | null;
has_dismissed_premium_onboarding: boolean;
has_ever_purchased: boolean;
has_unread_gift_inventory: boolean;
unread_gift_inventory_count: number;
used_mobile_client: boolean;
}
>;
export type User = Readonly<UserPartial & Partial<UserPrivate>>;
MAX_PRIVATE_CHANNELS_PER_USER,
MAX_RELATIONSHIPS,
} from '@fluxer/constants/src/LimitConstants';
import {PublicUserFlags} from '@fluxer/constants/src/UserConstants';
import type {RequiredAction, User, UserPartial, UserPrivate} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import * as SnowflakeUtils from '@fluxer/snowflake/src/SnowflakeUtils';
export type PendingBulkMessageDeletion = Readonly<{
scheduledAt: Date;
@@ -115,7 +44,12 @@ export type PendingBulkMessageDeletion = Readonly<{
messageCount: number;
}>;
interface UserRecordOptions {
instanceId?: string;
}
export class UserRecord {
readonly instanceId: string;
readonly id: string;
readonly username: string;
readonly discriminator: string;
@@ -126,14 +60,15 @@ export class UserRecord {
readonly system: boolean;
readonly flags: number;
private readonly _email?: string | null;
private readonly _emailBounced?: boolean;
readonly bio?: string | null;
readonly banner?: string | null;
readonly bannerColor?: number | null;
readonly pronouns?: string | null;
readonly accentColor?: string | null;
readonly accentColor?: number | null;
readonly mfaEnabled?: boolean;
readonly phone?: string | null;
readonly authenticatorTypes?: Array<number>;
readonly authenticatorTypes?: ReadonlyArray<number>;
private readonly _verified?: boolean;
private readonly _premiumType?: number | null;
private readonly _premiumSince?: Date | null;
@@ -149,7 +84,6 @@ export class UserRecord {
readonly premiumEnabledOverride?: boolean;
readonly passwordLastChangedAt?: Date | null;
readonly requiredActions?: Array<RequiredAction> | null;
readonly pendingManualVerification?: boolean;
readonly pendingBulkMessageDeletion: PendingBulkMessageDeletion | null;
private readonly _nsfwAllowed?: boolean;
readonly hasDismissedPremiumOnboarding?: boolean;
@@ -157,8 +91,10 @@ export class UserRecord {
private readonly _hasUnreadGiftInventory?: boolean;
private readonly _unreadGiftInventoryCount?: number;
private readonly _usedMobileClient?: boolean;
private readonly _traits: ReadonlyArray<string>;
constructor(user: User) {
constructor(user: User, options?: UserRecordOptions) {
this.instanceId = options?.instanceId ?? RuntimeConfigStore.localInstanceDomain;
this.id = user.id;
this.username = user.username;
this.discriminator = user.discriminator;
@@ -170,6 +106,7 @@ export class UserRecord {
this.flags = user.flags;
if ('email' in user) this._email = user.email;
if ('email_bounced' in user) this._emailBounced = user.email_bounced;
if ('bio' in user) this.bio = user.bio;
if ('banner' in user) this.banner = user.banner;
if ('banner_color' in user) this.bannerColor = user.banner_color;
@@ -195,9 +132,9 @@ export class UserRecord {
if ('password_last_changed_at' in user)
this.passwordLastChangedAt = user.password_last_changed_at ? new Date(user.password_last_changed_at) : null;
if ('required_actions' in user) {
this.requiredActions = user.required_actions && user.required_actions.length > 0 ? user.required_actions : null;
const actions = user.required_actions;
this.requiredActions = actions && actions.length > 0 ? [...actions] : null;
}
if ('pending_manual_verification' in user) this.pendingManualVerification = user.pending_manual_verification;
if ('pending_bulk_message_deletion' in user && user.pending_bulk_message_deletion) {
this.pendingBulkMessageDeletion = {
scheduledAt: new Date(user.pending_bulk_message_deletion.scheduled_at),
@@ -214,6 +151,11 @@ export class UserRecord {
if ('has_unread_gift_inventory' in user) this._hasUnreadGiftInventory = user.has_unread_gift_inventory;
if ('unread_gift_inventory_count' in user) this._unreadGiftInventoryCount = user.unread_gift_inventory_count;
if ('used_mobile_client' in user) this._usedMobileClient = user.used_mobile_client;
if ('traits' in user) {
this._traits = Object.freeze((user.traits ?? []).slice());
} else {
this._traits = [];
}
}
get email(): string | null | undefined {
@@ -223,6 +165,10 @@ export class UserRecord {
return this._email;
}
get emailBounced(): boolean | undefined {
return this._emailBounced;
}
get verified(): boolean | undefined {
const verifiedOverride = DeveloperOptionsStore.emailVerifiedOverride;
if (verifiedOverride != null) {
@@ -287,12 +233,15 @@ export class UserRecord {
return this._usedMobileClient;
}
withUpdates(updates: Partial<User>): UserRecord {
withUpdates(updates: Partial<User>, options?: {clearMissingOptionalFields?: boolean}): UserRecord {
const clearMissingOptionalFields = options?.clearMissingOptionalFields ?? false;
const baseFields: UserPartial = {
id: updates.id ?? this.id,
username: updates.username ?? this.username,
discriminator: updates.discriminator ?? this.discriminator,
global_name: 'global_name' in updates ? (updates.global_name as string | null) : this.globalName,
avatar: 'avatar' in updates ? (updates.avatar as string | null) : this.avatar,
avatar_color: 'avatar_color' in updates ? (updates.avatar_color as number | null) : (this.avatarColor ?? null),
bot: updates.bot ?? this.bot,
system: updates.system ?? this.system,
flags: updates.flags ?? this.flags,
@@ -311,6 +260,9 @@ export class UserRecord {
const privateFields: Partial<UserPrivate> = {
...(this._email !== undefined || updates.email !== undefined ? {email: updates.email ?? this._email} : {}),
...(this._emailBounced !== undefined || updates.email_bounced !== undefined
? {email_bounced: updates.email_bounced ?? this._emailBounced}
: {}),
...(this.bio !== undefined || 'bio' in updates
? {bio: 'bio' in updates && updates.bio !== undefined ? (updates.bio as string | null) : this.bio}
: {}),
@@ -356,7 +308,7 @@ export class UserRecord {
? {
accent_color:
'accent_color' in updates && updates.accent_color !== undefined
? (updates.accent_color as string | null)
? (updates.accent_color as number | null)
: this.accentColor,
}
: {}),
@@ -414,21 +366,18 @@ export class UserRecord {
updates.password_last_changed_at ?? this.passwordLastChangedAt?.toISOString() ?? null,
}
: {}),
...(this.pendingManualVerification !== undefined || updates.pending_manual_verification !== undefined
? {
pending_manual_verification: updates.pending_manual_verification ?? this.pendingManualVerification,
}
: {}),
pending_bulk_message_deletion: pendingBulkMessageDeletionValue,
...(this.requiredActions !== undefined || 'required_actions' in updates
? {
required_actions:
'required_actions' in updates
? updates.required_actions && (updates.required_actions as Array<RequiredAction>).length > 0
? 'required_actions' in updates
? {
required_actions:
updates.required_actions && (updates.required_actions as Array<RequiredAction>).length > 0
? (updates.required_actions as Array<RequiredAction>)
: null
: this.requiredActions,
}
: null,
}
: clearMissingOptionalFields
? {}
: {required_actions: this.requiredActions}
: {}),
...(this._nsfwAllowed !== undefined || updates.nsfw_allowed !== undefined
? {nsfw_allowed: updates.nsfw_allowed ?? this._nsfwAllowed}
@@ -451,12 +400,16 @@ export class UserRecord {
...(this._usedMobileClient !== undefined || updates.used_mobile_client !== undefined
? {used_mobile_client: updates.used_mobile_client ?? this._usedMobileClient}
: {}),
traits: updates.traits ?? [...this.traits],
};
return new UserRecord({
...baseFields,
...privateFields,
});
return new UserRecord(
{
...baseFields,
...privateFields,
},
{instanceId: this.instanceId},
);
}
get displayName(): string {
@@ -471,39 +424,70 @@ export class UserRecord {
return new Date(SnowflakeUtils.extractTimestamp(this.id));
}
get traits(): ReadonlyArray<string> {
return this._traits;
}
isPremium(): boolean {
if (RuntimeConfigStore.isSelfHosted()) {
return true;
}
return this.premiumType != null && this.premiumType > 0;
}
get maxGuilds(): number {
return this.isPremium() ? MAX_GUILDS_PREMIUM : MAX_GUILDS_NON_PREMIUM;
return this.resolveRuntimeLimit('max_guilds', MAX_GUILDS_NON_PREMIUM);
}
get maxMessageLength(): number {
return this.isPremium() ? MAX_MESSAGE_LENGTH_PREMIUM : MAX_MESSAGE_LENGTH_NON_PREMIUM;
return this.resolveRuntimeLimit('max_message_length', MAX_MESSAGE_LENGTH_NON_PREMIUM);
}
get maxAttachmentSize(): number {
return getAttachmentMaxSize(this.isPremium());
get maxAttachmentFileSize(): number {
return this.resolveRuntimeLimit('max_attachment_file_size', getMaxAttachmentFileSize());
}
get maxAttachmentsPerMessage(): number {
return this.resolveRuntimeLimit('max_attachments_per_message', MAX_ATTACHMENTS_PER_MESSAGE);
}
get maxBioLength(): number {
return this.isPremium() ? MAX_BIO_LENGTH_PREMIUM : MAX_BIO_LENGTH_NON_PREMIUM;
return this.resolveRuntimeLimit('max_bio_length', MAX_BIO_LENGTH);
}
get maxBookmarks(): number {
return this.isPremium() ? MAX_BOOKMARKS_PREMIUM : MAX_BOOKMARKS_NON_PREMIUM;
return this.resolveRuntimeLimit('max_bookmarks', MAX_BOOKMARKS_NON_PREMIUM);
}
get maxFavoriteMemes(): number {
return this.isPremium() ? MAX_FAVORITE_MEMES_PREMIUM : MAX_FAVORITE_MEMES_NON_PREMIUM;
return this.resolveRuntimeLimit('max_favorite_memes', MAX_FAVORITE_MEMES_NON_PREMIUM);
}
get maxFavoriteMemeTags(): number {
return this.resolveRuntimeLimit('max_favorite_meme_tags', MAX_FAVORITE_MEME_TAGS);
}
get maxGroupDmRecipients(): number {
return this.resolveRuntimeLimit('max_group_dm_recipients', MAX_GROUP_DM_RECIPIENTS);
}
get maxPrivateChannels(): number {
return this.resolveRuntimeLimit('max_private_channels_per_user', MAX_PRIVATE_CHANNELS_PER_USER);
}
get maxRelationships(): number {
return this.resolveRuntimeLimit('max_relationships', MAX_RELATIONSHIPS);
}
private resolveRuntimeLimit(key: LimitKey, fallback: number): number {
return LimitResolver.resolve({
key,
fallback,
context: {
traits: this.traits,
},
});
}
isStaff(): boolean {
return (this.flags & UserFlags.STAFF) !== 0;
return (this.flags & PublicUserFlags.STAFF) !== 0;
}
isClaimed(): boolean {
@@ -512,6 +496,7 @@ export class UserRecord {
equals(other: UserRecord): boolean {
return (
this.instanceId === other.instanceId &&
this.id === other.id &&
this.username === other.username &&
this.discriminator === other.discriminator &&
@@ -521,6 +506,7 @@ export class UserRecord {
this.system === other.system &&
this.flags === other.flags &&
this._email === other._email &&
this._emailBounced === other._emailBounced &&
this.bio === other.bio &&
this.banner === other.banner &&
this.bannerColor === other.bannerColor &&
@@ -543,7 +529,6 @@ export class UserRecord {
this.premiumEnabledOverride === other.premiumEnabledOverride &&
this.passwordLastChangedAt?.getTime() === other.passwordLastChangedAt?.getTime() &&
JSON.stringify(this.requiredActions) === JSON.stringify(other.requiredActions) &&
this.pendingManualVerification === other.pendingManualVerification &&
((this.pendingBulkMessageDeletion === null && other.pendingBulkMessageDeletion === null) ||
(this.pendingBulkMessageDeletion != null &&
other.pendingBulkMessageDeletion != null &&
@@ -555,7 +540,8 @@ export class UserRecord {
this.hasDismissedPremiumOnboarding === other.hasDismissedPremiumOnboarding &&
this._hasUnreadGiftInventory === other._hasUnreadGiftInventory &&
this._unreadGiftInventoryCount === other._unreadGiftInventoryCount &&
this._usedMobileClient === other._usedMobileClient
this._usedMobileClient === other._usedMobileClient &&
JSON.stringify(this.traits) === JSON.stringify(other.traits)
);
}
@@ -572,6 +558,7 @@ export class UserRecord {
discriminator: this.discriminator,
global_name: this.globalName,
avatar: this.avatar,
avatar_color: this.avatarColor ?? null,
bot: this.bot,
system: this.system,
flags: this.flags,
@@ -579,6 +566,7 @@ export class UserRecord {
const privateFields: Partial<UserPrivate> = {
...(this._email !== undefined ? {email: this._email} : {}),
...(this._emailBounced !== undefined ? {email_bounced: this._emailBounced} : {}),
...(this.bio !== undefined ? {bio: this.bio} : {}),
...(this.banner !== undefined ? {banner: this.banner} : {}),
...(this.avatarColor !== undefined ? {avatar_color: this.avatarColor} : {}),
@@ -609,9 +597,6 @@ export class UserRecord {
? {password_last_changed_at: normalizeDate(this.passwordLastChangedAt)}
: {}),
...(this.requiredActions !== undefined ? {required_actions: this.requiredActions} : {}),
...(this.pendingManualVerification !== undefined
? {pending_manual_verification: this.pendingManualVerification}
: {}),
...(this.pendingBulkMessageDeletion !== undefined
? {
pending_bulk_message_deletion: this.pendingBulkMessageDeletion
@@ -632,6 +617,7 @@ export class UserRecord {
? {unread_gift_inventory_count: this._unreadGiftInventoryCount}
: {}),
...(this._usedMobileClient !== undefined ? {used_mobile_client: this._usedMobileClient} : {}),
traits: [...this.traits],
};
return {

View File

@@ -17,20 +17,12 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {UserPartial, UserRecord} from '~/records/UserRecord';
import UserStore from '~/stores/UserStore';
import * as SnowflakeUtils from '~/utils/SnowflakeUtils';
import {webhookUrl} from '~/utils/UrlUtils';
export type Webhook = Readonly<{
id: string;
guild_id: string;
channel_id: string;
user: UserPartial;
name: string;
avatar: string | null;
token: string;
}>;
import type {UserRecord} from '@app/records/UserRecord';
import UserStore from '@app/stores/UserStore';
import {webhookUrl} from '@app/utils/UrlUtils';
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import type {Webhook} from '@fluxer/schema/src/domains/webhook/WebhookSchemas';
import * as SnowflakeUtils from '@fluxer/snowflake/src/SnowflakeUtils';
export class WebhookRecord {
readonly id: string;