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

@@ -0,0 +1,66 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {AdminApiKeyRow} from '@fluxer/api/src/database/types/AdminAuthTypes';
export class AdminApiKey {
readonly keyId: bigint;
readonly keyHash: string;
readonly name: string;
readonly createdById: UserID;
readonly createdAt: Date;
readonly lastUsedAt: Date | null;
readonly expiresAt: Date | null;
readonly version: number;
readonly acls: Set<string>;
constructor(row: AdminApiKeyRow) {
this.keyId = row.key_id;
this.keyHash = row.key_hash;
this.name = row.name;
this.createdById = row.created_by_user_id;
this.createdAt = row.created_at;
this.lastUsedAt = row.last_used_at ?? null;
this.expiresAt = row.expires_at ?? null;
this.version = row.version;
this.acls = row.acls ?? new Set();
}
toRow(): AdminApiKeyRow {
return {
key_id: this.keyId,
key_hash: this.keyHash,
name: this.name,
created_by_user_id: this.createdById,
created_at: this.createdAt,
last_used_at: this.lastUsedAt,
expires_at: this.expiresAt,
version: this.version,
acls: this.acls.size > 0 ? this.acls : new Set(),
};
}
isExpired(): boolean {
if (!this.expiresAt) {
return false;
}
return this.expiresAt < new Date();
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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 {ApplicationID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {ApplicationRow} from '@fluxer/api/src/database/types/OAuth2Types';
export class Application {
readonly applicationId: ApplicationID;
readonly ownerUserId: UserID;
readonly name: string;
readonly botUserId: UserID | null;
readonly botIsPublic: boolean;
readonly botRequireCodeGrant: boolean;
readonly oauth2RedirectUris: Set<string>;
readonly clientSecretHash: string | null;
readonly botTokenHash: string | null;
readonly botTokenPreview: string | null;
readonly botTokenCreatedAt: Date | null;
readonly clientSecretCreatedAt: Date | null;
readonly version: number;
constructor(row: ApplicationRow) {
this.applicationId = row.application_id;
this.ownerUserId = row.owner_user_id;
this.name = row.name;
this.botUserId = row.bot_user_id;
this.botIsPublic = row.bot_is_public ?? row.bot_user_id !== null;
this.botRequireCodeGrant = row.bot_require_code_grant ?? false;
this.oauth2RedirectUris = row.oauth2_redirect_uris ?? new Set<string>();
this.clientSecretHash = row.client_secret_hash;
this.botTokenHash = row.bot_token_hash;
this.botTokenPreview = row.bot_token_preview;
this.botTokenCreatedAt = row.bot_token_created_at;
this.clientSecretCreatedAt = row.client_secret_created_at;
this.version = row.version ?? 1;
}
toRow(): ApplicationRow {
return {
application_id: this.applicationId,
owner_user_id: this.ownerUserId,
name: this.name,
bot_user_id: this.botUserId,
bot_is_public: this.botIsPublic,
bot_require_code_grant: this.botRequireCodeGrant,
oauth2_redirect_uris: this.oauth2RedirectUris,
client_secret_hash: this.clientSecretHash,
bot_token_hash: this.botTokenHash,
bot_token_preview: this.botTokenPreview,
bot_token_created_at: this.botTokenCreatedAt,
client_secret_created_at: this.clientSecretCreatedAt,
version: this.version,
};
}
hasBotUser(): boolean {
return this.botUserId !== null;
}
getBotUserId(): UserID | null {
return this.botUserId;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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 {AttachmentID} from '@fluxer/api/src/BrandedTypes';
import type {MessageAttachment} from '@fluxer/api/src/database/types/MessageTypes';
export class Attachment {
readonly id: AttachmentID;
readonly filename: string;
readonly size: bigint;
readonly title: string | null;
readonly description: string | null;
readonly width: number | null;
readonly height: number | null;
readonly contentType: string;
readonly contentHash: string | null;
readonly placeholder: string | null;
readonly flags: number;
readonly duration: number | null;
readonly nsfw: boolean | null;
readonly waveform: string | null;
constructor(attachment: MessageAttachment) {
this.id = attachment.attachment_id;
this.filename = attachment.filename;
this.size = attachment.size;
this.title = attachment.title ?? null;
this.description = attachment.description ?? null;
this.width = attachment.width ?? null;
this.height = attachment.height ?? null;
this.contentType = attachment.content_type;
this.contentHash = attachment.content_hash ?? null;
this.placeholder = attachment.placeholder ?? null;
this.flags = attachment.flags ?? 0;
this.duration = attachment.duration ?? null;
this.nsfw = attachment.nsfw ?? null;
this.waveform = attachment.waveform ?? null;
}
toMessageAttachment(): MessageAttachment {
return {
attachment_id: this.id,
filename: this.filename,
size: this.size,
title: this.title,
description: this.description,
width: this.width,
height: this.height,
content_type: this.contentType,
content_hash: this.contentHash,
placeholder: this.placeholder,
flags: this.flags,
duration: this.duration,
nsfw: this.nsfw,
waveform: this.waveform,
};
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {AuthSessionRow} from '@fluxer/api/src/database/types/AuthTypes';
export class AuthSession {
readonly userId: UserID;
readonly sessionIdHash: Buffer;
readonly createdAt: Date;
readonly approximateLastUsedAt: Date;
readonly clientIp: string;
readonly clientUserAgent: string | null;
readonly clientIsDesktop: boolean | null;
readonly clientOs?: string | null;
readonly clientPlatform?: string | null;
readonly version: number;
constructor(row: AuthSessionRow) {
this.userId = row.user_id;
this.sessionIdHash = row.session_id_hash;
this.createdAt = row.created_at;
this.approximateLastUsedAt = row.approx_last_used_at;
this.clientIp = row.client_ip;
this.clientUserAgent = row.client_user_agent ?? null;
this.clientIsDesktop = row.client_is_desktop ?? null;
this.clientOs = row.client_os ?? null;
this.clientPlatform = row.client_platform ?? null;
this.version = row.version;
}
toRow(): AuthSessionRow {
return {
user_id: this.userId,
session_id_hash: this.sessionIdHash,
created_at: this.createdAt,
approx_last_used_at: this.approximateLastUsedAt,
client_ip: this.clientIp,
client_user_agent: this.clientUserAgent,
client_is_desktop: this.clientIsDesktop,
client_os: this.clientOs,
client_platform: this.clientPlatform,
version: this.version,
};
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {MessageCall} from '@fluxer/api/src/database/types/MessageTypes';
export class CallInfo {
readonly participantIds: Set<UserID>;
readonly endedTimestamp: Date | null;
constructor(call: MessageCall) {
this.participantIds = call.participant_ids ?? new Set();
this.endedTimestamp = call.ended_timestamp ? new Date(call.ended_timestamp) : null;
}
toMessageCall(): MessageCall {
return {
participant_ids: this.participantIds.size > 0 ? this.participantIds : null,
ended_timestamp: this.endedTimestamp,
};
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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 {ChannelID, GuildID, MessageID, RoleID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {ChannelRow, PermissionOverwrite} from '@fluxer/api/src/database/types/ChannelTypes';
import {ChannelPermissionOverwrite} from '@fluxer/api/src/models/ChannelPermissionOverwrite';
export class Channel {
readonly id: ChannelID;
readonly guildId: GuildID | null;
readonly type: number;
readonly name: string | null;
readonly topic: string | null;
readonly iconHash: string | null;
readonly url: string | null;
readonly parentId: ChannelID | null;
readonly position: number;
readonly ownerId: UserID | null;
readonly recipientIds: Set<UserID>;
readonly isNsfw: boolean;
readonly rateLimitPerUser: number;
readonly bitrate: number | null;
readonly userLimit: number | null;
readonly rtcRegion: string | null;
readonly lastMessageId: MessageID | null;
readonly lastPinTimestamp: Date | null;
readonly permissionOverwrites: Map<RoleID | UserID, ChannelPermissionOverwrite>;
readonly nicknames: Map<string, string>;
readonly isSoftDeleted: boolean;
readonly indexedAt: Date | null;
readonly version: number;
constructor(row: ChannelRow) {
this.id = row.channel_id;
this.guildId = row.guild_id ?? null;
this.type = row.type;
this.name = row.name ?? null;
this.topic = row.topic ?? null;
this.iconHash = row.icon_hash ?? null;
this.url = row.url ?? null;
this.parentId = row.parent_id ?? null;
this.position = row.position ?? 0;
this.ownerId = row.owner_id ?? null;
this.recipientIds = row.recipient_ids ?? new Set();
this.isNsfw = row.nsfw ?? false;
this.rateLimitPerUser = row.rate_limit_per_user ?? 0;
this.bitrate = row.bitrate ?? 0;
this.userLimit = row.user_limit ?? 0;
this.rtcRegion = row.rtc_region ?? null;
this.lastMessageId = row.last_message_id ?? null;
this.lastPinTimestamp = row.last_pin_timestamp ?? null;
this.permissionOverwrites = new Map();
if (row.permission_overwrites) {
for (const [id, overwrite] of row.permission_overwrites) {
this.permissionOverwrites.set(id, new ChannelPermissionOverwrite(overwrite));
}
}
this.nicknames = row.nicks ?? new Map();
this.isSoftDeleted = row.soft_deleted;
this.indexedAt = row.indexed_at ?? null;
this.version = row.version;
}
toRow(): ChannelRow {
const permOverwritesMap: Map<UserID | RoleID, PermissionOverwrite> | null =
this.permissionOverwrites.size > 0
? new Map(
Array.from(this.permissionOverwrites.entries()).map(([id, overwrite]) => [
id,
overwrite.toPermissionOverwrite(),
]),
)
: null;
return {
channel_id: this.id,
guild_id: this.guildId,
type: this.type,
name: this.name,
topic: this.topic,
icon_hash: this.iconHash,
url: this.url,
parent_id: this.parentId,
position: this.position,
owner_id: this.ownerId,
recipient_ids: this.recipientIds.size > 0 ? this.recipientIds : null,
nsfw: this.isNsfw,
rate_limit_per_user: this.rateLimitPerUser,
bitrate: this.bitrate,
user_limit: this.userLimit,
rtc_region: this.rtcRegion,
last_message_id: this.lastMessageId,
last_pin_timestamp: this.lastPinTimestamp,
permission_overwrites: permOverwritesMap,
nicks: this.nicknames.size > 0 ? this.nicknames : null,
soft_deleted: this.isSoftDeleted,
indexed_at: this.indexedAt,
version: this.version,
};
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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 {PermissionOverwrite} from '@fluxer/api/src/database/types/ChannelTypes';
export class ChannelPermissionOverwrite {
readonly type: number;
readonly allow: bigint;
readonly deny: bigint;
constructor(overwrite: PermissionOverwrite) {
this.type = overwrite.type;
this.allow = overwrite.allow_ ?? 0n;
this.deny = overwrite.deny_ ?? 0n;
}
toPermissionOverwrite(): PermissionOverwrite {
return {
type: this.type,
allow_: this.allow,
deny_: this.deny,
};
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import {createEmailRevertToken} from '@fluxer/api/src/BrandedTypes';
import type {EmailRevertTokenRow} from '@fluxer/api/src/database/types/AuthTypes';
export class EmailRevertToken {
readonly token: string;
readonly userId: UserID;
readonly email: string;
constructor(row: EmailRevertTokenRow) {
this.token = row.token_;
this.userId = row.user_id;
this.email = row.email;
}
toRow(): EmailRevertTokenRow {
return {
token_: createEmailRevertToken(this.token),
user_id: this.userId,
email: this.email,
};
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import {createEmailVerificationToken} from '@fluxer/api/src/BrandedTypes';
import type {EmailVerificationTokenRow} from '@fluxer/api/src/database/types/AuthTypes';
export class EmailVerificationToken {
readonly token: string;
readonly userId: UserID;
readonly email: string;
constructor(row: EmailVerificationTokenRow) {
this.token = row.token_;
this.userId = row.user_id;
this.email = row.email;
}
toRow(): EmailVerificationTokenRow {
return {
token_: createEmailVerificationToken(this.token),
user_id: this.userId,
email: this.email,
};
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 {MessageEmbed, MessageEmbedChild} from '@fluxer/api/src/database/types/MessageTypes';
import {EmbedAuthor} from '@fluxer/api/src/models/EmbedAuthor';
import {EmbedField} from '@fluxer/api/src/models/EmbedField';
import {EmbedFooter} from '@fluxer/api/src/models/EmbedFooter';
import {EmbedMedia} from '@fluxer/api/src/models/EmbedMedia';
import {EmbedProvider} from '@fluxer/api/src/models/EmbedProvider';
import {sanitizeOptionalAbsoluteUrlOrNull} from '@fluxer/api/src/utils/UrlSanitizer';
export class Embed {
readonly type: string | null;
readonly title: string | null;
readonly description: string | null;
readonly url: string | null;
readonly timestamp: Date | null;
readonly color: number | null;
readonly author: EmbedAuthor | null;
readonly provider: EmbedProvider | null;
readonly thumbnail: EmbedMedia | null;
readonly image: EmbedMedia | null;
readonly video: EmbedMedia | null;
readonly footer: EmbedFooter | null;
readonly fields: Array<EmbedField>;
readonly children: Array<Embed>;
readonly nsfw: boolean | null;
constructor(embed: MessageEmbed | MessageEmbedChild, allowChildren: boolean = true) {
this.type = embed.type ?? null;
this.title = embed.title ?? null;
this.description = embed.description ?? null;
this.url = sanitizeOptionalAbsoluteUrlOrNull(embed.url);
this.timestamp = embed.timestamp ? new Date(embed.timestamp) : null;
this.color = embed.color ?? null;
this.author = embed.author ? new EmbedAuthor(embed.author) : null;
this.provider = embed.provider ? new EmbedProvider(embed.provider) : null;
this.thumbnail = embed.thumbnail ? new EmbedMedia(embed.thumbnail) : null;
this.image = embed.image ? new EmbedMedia(embed.image) : null;
this.video = embed.video ? new EmbedMedia(embed.video) : null;
this.footer = embed.footer ? new EmbedFooter(embed.footer) : null;
this.fields = (embed.fields ?? []).map((field) => new EmbedField(field));
this.children =
allowChildren && 'children' in embed && embed.children
? embed.children.map((child) => new Embed(child, false))
: [];
this.nsfw = embed.nsfw ?? null;
}
toMessageEmbed(): MessageEmbed {
return {
...this.toMessageEmbedChild(),
children: this.children.length > 0 ? this.children.map((child) => child.toMessageEmbedChild()) : null,
};
}
private toMessageEmbedChild(): MessageEmbedChild {
return {
type: this.type,
title: this.title,
description: this.description,
url: this.url,
timestamp: this.timestamp,
color: this.color,
author: this.author?.toMessageEmbedAuthor() ?? null,
provider: this.provider?.toMessageEmbedProvider() ?? null,
thumbnail: this.thumbnail?.toMessageEmbedMedia() ?? null,
image: this.image?.toMessageEmbedMedia() ?? null,
video: this.video?.toMessageEmbedMedia() ?? null,
footer: this.footer?.toMessageEmbedFooter() ?? null,
fields: this.fields.length > 0 ? this.fields.map((field) => field.toMessageEmbedField()) : null,
nsfw: this.nsfw,
};
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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 {MessageEmbedAuthor} from '@fluxer/api/src/database/types/MessageTypes';
import {sanitizeOptionalAbsoluteUrlOrNull} from '@fluxer/api/src/utils/UrlSanitizer';
export class EmbedAuthor {
readonly name: string | null;
readonly url: string | null;
readonly iconUrl: string | null;
constructor(author: MessageEmbedAuthor) {
this.name = author.name ?? null;
this.url = sanitizeOptionalAbsoluteUrlOrNull(author.url);
this.iconUrl = sanitizeOptionalAbsoluteUrlOrNull(author.icon_url);
}
toMessageEmbedAuthor(): MessageEmbedAuthor {
return {
name: this.name,
url: this.url,
icon_url: this.iconUrl,
};
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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 {MessageEmbedField} from '@fluxer/api/src/database/types/MessageTypes';
export class EmbedField {
readonly name: string;
readonly value: string;
readonly inline: boolean;
constructor(field: MessageEmbedField) {
this.name = field.name ?? '';
this.value = field.value ?? '';
this.inline = field.inline ?? false;
}
toMessageEmbedField(): MessageEmbedField {
return {
name: this.name || null,
value: this.value || null,
inline: this.inline,
};
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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 {MessageEmbedFooter} from '@fluxer/api/src/database/types/MessageTypes';
import {sanitizeOptionalAbsoluteUrlOrNull} from '@fluxer/api/src/utils/UrlSanitizer';
export class EmbedFooter {
readonly text: string | null;
readonly iconUrl: string | null;
constructor(footer: MessageEmbedFooter) {
this.text = footer.text ?? null;
this.iconUrl = sanitizeOptionalAbsoluteUrlOrNull(footer.icon_url);
}
toMessageEmbedFooter(): MessageEmbedFooter {
return {
text: this.text,
icon_url: this.iconUrl,
};
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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 {MessageEmbedMedia} from '@fluxer/api/src/database/types/MessageTypes';
export class EmbedMedia {
readonly url: string | null;
readonly width: number | null;
readonly height: number | null;
readonly description: string | null;
readonly contentType: string | null;
readonly contentHash: string | null;
readonly placeholder: string | null;
readonly flags: number;
readonly duration: number | null;
constructor(media: MessageEmbedMedia) {
this.url = media.url ?? null;
this.width = media.width ?? null;
this.height = media.height ?? null;
this.description = media.description ?? null;
this.contentType = media.content_type ?? null;
this.contentHash = media.content_hash ?? null;
this.placeholder = media.placeholder ?? null;
this.flags = media.flags ?? 0;
this.duration = media.duration ?? null;
}
toMessageEmbedMedia(): MessageEmbedMedia {
return {
url: this.url,
width: this.width,
height: this.height,
description: this.description,
content_type: this.contentType,
content_hash: this.contentHash,
placeholder: this.placeholder,
flags: this.flags,
duration: this.duration,
};
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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 {MessageEmbedProvider} from '@fluxer/api/src/database/types/MessageTypes';
import {sanitizeOptionalAbsoluteUrlOrNull} from '@fluxer/api/src/utils/UrlSanitizer';
export class EmbedProvider {
readonly name: string | null;
readonly url: string | null;
constructor(provider: MessageEmbedProvider) {
this.name = provider.name ?? null;
this.url = sanitizeOptionalAbsoluteUrlOrNull(provider.url);
}
toMessageEmbedProvider(): MessageEmbedProvider {
return {
name: this.name,
url: this.url,
};
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 {GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {ExpressionPackRow} from '@fluxer/api/src/database/types/UserTypes';
export type ExpressionPackType = 'emoji' | 'sticker';
export class ExpressionPack {
readonly id: GuildID;
readonly type: ExpressionPackType;
readonly creatorId: UserID;
readonly name: string;
readonly description: string | null;
readonly createdAt: Date;
readonly updatedAt: Date;
readonly version: number;
constructor(row: ExpressionPackRow) {
this.id = row.pack_id;
this.type = row.pack_type as ExpressionPackType;
this.creatorId = row.creator_id;
this.name = row.name;
this.description = row.description ?? null;
this.createdAt = row.created_at;
this.updatedAt = row.updated_at;
this.version = row.version;
}
toRow(): ExpressionPackRow {
return {
pack_id: this.id,
pack_type: this.type,
creator_id: this.creatorId,
name: this.name,
description: this.description,
created_at: this.createdAt,
updated_at: this.updatedAt,
version: this.version,
};
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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 {AttachmentID, MemeID, UserID} from '@fluxer/api/src/BrandedTypes';
import {userIdToChannelId} from '@fluxer/api/src/BrandedTypes';
import type {FavoriteMemeRow} from '@fluxer/api/src/database/types/UserTypes';
import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
export class FavoriteMeme {
readonly id: MemeID;
readonly userId: UserID;
readonly name: string;
readonly altText: string | null;
readonly tags: Array<string>;
readonly attachmentId: AttachmentID;
readonly filename: string;
readonly contentType: string;
readonly contentHash: string | null;
readonly size: bigint;
readonly width: number | null;
readonly height: number | null;
readonly duration: number | null;
readonly isGifv: boolean;
readonly klipySlug: string | null;
readonly tenorSlugId: string | null;
readonly createdAt: Date;
readonly version: number;
constructor(row: FavoriteMemeRow) {
this.id = row.meme_id;
this.userId = row.user_id;
this.name = row.name;
this.altText = row.alt_text ?? null;
this.tags = row.tags ?? [];
this.attachmentId = row.attachment_id;
this.filename = row.filename;
this.contentType = row.content_type;
this.contentHash = row.content_hash ?? null;
this.size = row.size;
this.width = row.width ?? null;
this.height = row.height ?? null;
this.duration = row.duration ?? null;
this.isGifv = row.is_gifv ?? false;
this.klipySlug = row.klipy_slug ?? null;
this.tenorSlugId = row.tenor_id_str ?? null;
this.createdAt = snowflakeToDate(this.id);
this.version = row.version;
}
toRow(): FavoriteMemeRow {
return {
user_id: this.userId,
meme_id: this.id,
name: this.name,
alt_text: this.altText,
tags: this.tags.length > 0 ? this.tags : null,
attachment_id: this.attachmentId,
filename: this.filename,
content_type: this.contentType,
content_hash: this.contentHash,
size: this.size,
width: this.width,
height: this.height,
duration: this.duration,
is_gifv: this.isGifv,
klipy_slug: this.klipySlug,
tenor_id_str: this.tenorSlugId,
version: this.version,
};
}
get storageKey(): string {
const channelId = userIdToChannelId(this.userId);
return `attachments/${channelId}/${this.attachmentId}/${this.filename}`;
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {GiftCodeRow} from '@fluxer/api/src/database/types/PaymentTypes';
export class GiftCode {
readonly code: string;
readonly durationMonths: number;
readonly createdAt: Date;
readonly createdByUserId: UserID;
readonly redeemedAt: Date | null;
readonly redeemedByUserId: UserID | null;
readonly stripePaymentIntentId: string | null;
readonly visionarySequenceNumber: number | null;
readonly checkoutSessionId: string | null;
readonly version: number;
constructor(row: GiftCodeRow) {
this.code = row.code;
this.durationMonths = row.duration_months;
this.createdAt = row.created_at;
this.createdByUserId = row.created_by_user_id as UserID;
this.redeemedAt = row.redeemed_at ?? null;
this.redeemedByUserId = row.redeemed_by_user_id ? (row.redeemed_by_user_id as UserID) : null;
this.stripePaymentIntentId = row.stripe_payment_intent_id ?? null;
this.visionarySequenceNumber = row.visionary_sequence_number ?? null;
this.checkoutSessionId = row.checkout_session_id ?? null;
this.version = row.version;
}
toRow(): GiftCodeRow {
return {
code: this.code,
duration_months: this.durationMonths,
created_at: this.createdAt,
created_by_user_id: this.createdByUserId,
redeemed_at: this.redeemedAt,
redeemed_by_user_id: this.redeemedByUserId,
stripe_payment_intent_id: this.stripePaymentIntentId,
visionary_sequence_number: this.visionarySequenceNumber,
checkout_session_id: this.checkoutSessionId,
version: this.version,
};
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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 {ChannelID, GuildID, UserID, VanityURLCode} from '@fluxer/api/src/BrandedTypes';
import type {GuildRow} from '@fluxer/api/src/database/types/GuildTypes';
import {GuildSplashCardAlignment, type GuildSplashCardAlignmentValue} from '@fluxer/constants/src/GuildConstants';
export class Guild {
readonly id: GuildID;
readonly ownerId: UserID;
readonly name: string;
readonly vanityUrlCode: VanityURLCode | null;
readonly iconHash: string | null;
readonly bannerHash: string | null;
readonly bannerWidth: number | null;
readonly bannerHeight: number | null;
readonly splashHash: string | null;
readonly splashWidth: number | null;
readonly splashHeight: number | null;
readonly splashCardAlignment: GuildSplashCardAlignmentValue;
readonly embedSplashHash: string | null;
readonly embedSplashWidth: number | null;
readonly embedSplashHeight: number | null;
readonly features: Set<string>;
readonly verificationLevel: number;
readonly mfaLevel: number;
readonly nsfwLevel: number;
readonly explicitContentFilter: number;
readonly defaultMessageNotifications: number;
readonly systemChannelId: ChannelID | null;
readonly systemChannelFlags: number;
readonly rulesChannelId: ChannelID | null;
readonly afkChannelId: ChannelID | null;
readonly afkTimeout: number;
readonly disabledOperations: number;
readonly memberCount: number;
readonly auditLogsIndexedAt: Date | null;
readonly membersIndexedAt: Date | null;
readonly messageHistoryCutoff: Date | null;
readonly version: number;
constructor(row: GuildRow) {
this.id = row.guild_id;
this.ownerId = row.owner_id;
this.name = row.name;
this.vanityUrlCode = row.vanity_url_code ?? null;
this.iconHash = row.icon_hash ?? null;
this.bannerHash = row.banner_hash ?? null;
this.bannerWidth = row.banner_width ?? null;
this.bannerHeight = row.banner_height ?? null;
this.splashHash = row.splash_hash ?? null;
this.splashWidth = row.splash_width ?? null;
this.splashHeight = row.splash_height ?? null;
this.splashCardAlignment = row.splash_card_alignment ?? GuildSplashCardAlignment.CENTER;
this.embedSplashHash = row.embed_splash_hash ?? null;
this.embedSplashWidth = row.embed_splash_width ?? null;
this.embedSplashHeight = row.embed_splash_height ?? null;
this.features = row.features ?? new Set();
this.verificationLevel = row.verification_level ?? 0;
this.mfaLevel = row.mfa_level ?? 0;
this.nsfwLevel = row.nsfw_level ?? 0;
this.explicitContentFilter = row.explicit_content_filter ?? 0;
this.defaultMessageNotifications = row.default_message_notifications ?? 0;
this.systemChannelId = row.system_channel_id ?? null;
this.systemChannelFlags = row.system_channel_flags ?? 0;
this.rulesChannelId = row.rules_channel_id ?? null;
this.afkChannelId = row.afk_channel_id ?? null;
this.afkTimeout = row.afk_timeout ?? 0;
this.disabledOperations = row.disabled_operations ?? 0;
this.memberCount = row.member_count ?? 0;
this.auditLogsIndexedAt = row.audit_logs_indexed_at ?? null;
this.membersIndexedAt = row.members_indexed_at ?? null;
this.messageHistoryCutoff = row.message_history_cutoff ?? null;
this.version = row.version;
}
toRow(): GuildRow {
return {
guild_id: this.id,
owner_id: this.ownerId,
name: this.name,
vanity_url_code: this.vanityUrlCode,
icon_hash: this.iconHash,
banner_hash: this.bannerHash,
banner_width: this.bannerWidth,
banner_height: this.bannerHeight,
splash_hash: this.splashHash,
splash_width: this.splashWidth,
splash_height: this.splashHeight,
splash_card_alignment: this.splashCardAlignment,
embed_splash_hash: this.embedSplashHash,
embed_splash_width: this.embedSplashWidth,
embed_splash_height: this.embedSplashHeight,
features: this.features.size > 0 ? this.features : null,
verification_level: this.verificationLevel,
mfa_level: this.mfaLevel,
nsfw_level: this.nsfwLevel,
explicit_content_filter: this.explicitContentFilter,
default_message_notifications: this.defaultMessageNotifications,
system_channel_id: this.systemChannelId,
system_channel_flags: this.systemChannelFlags,
rules_channel_id: this.rulesChannelId,
afk_channel_id: this.afkChannelId,
afk_timeout: this.afkTimeout,
disabled_operations: this.disabledOperations,
member_count: this.memberCount,
audit_logs_indexed_at: this.auditLogsIndexedAt,
members_indexed_at: this.membersIndexedAt,
message_history_cutoff: this.messageHistoryCutoff,
version: this.version,
};
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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 {GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {GuildAuditLogRow} from '@fluxer/api/src/database/types/GuildTypes';
import type {GuildAuditLogChange} from '@fluxer/api/src/guild/GuildAuditLogTypes';
import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
export class GuildAuditLog {
readonly guildId: GuildID;
readonly logId: bigint;
readonly userId: UserID;
readonly targetId: string | null;
readonly actionType: number;
readonly reason: string | null;
readonly options: Map<string, string>;
readonly changes: GuildAuditLogChange | null;
readonly createdAt: Date;
constructor(row: GuildAuditLogRow) {
this.guildId = row.guild_id;
this.logId = row.log_id;
this.userId = row.user_id;
this.targetId = row.target_id ?? null;
this.actionType = row.action_type;
this.reason = row.reason ?? null;
this.options = row.options ?? new Map();
this.changes = row.changes ? this.safeParseChanges(row.changes) : null;
this.createdAt = snowflakeToDate(this.logId);
}
toRow(): GuildAuditLogRow {
return {
guild_id: this.guildId,
log_id: this.logId,
user_id: this.userId,
target_id: this.targetId,
action_type: this.actionType,
reason: this.reason,
options: this.options.size > 0 ? this.options : null,
changes: this.changes ? JSON.stringify(this.changes) : null,
};
}
private safeParseChanges(raw: string): GuildAuditLogChange | null {
try {
return JSON.parse(raw) as GuildAuditLogChange;
} catch {
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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 {GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {GuildBanRow} from '@fluxer/api/src/database/types/GuildTypes';
export class GuildBan {
readonly guildId: GuildID;
readonly userId: UserID;
readonly moderatorId: UserID;
readonly bannedAt: Date;
readonly expiresAt: Date | null;
readonly reason: string | null;
readonly ipAddress: string | null;
constructor(row: GuildBanRow) {
this.guildId = row.guild_id;
this.userId = row.user_id;
this.moderatorId = row.moderator_id;
this.bannedAt = row.banned_at;
this.expiresAt = row.expires_at ?? null;
this.reason = row.reason ?? null;
this.ipAddress = row.ip ?? null;
}
toRow(): GuildBanRow {
return {
guild_id: this.guildId,
user_id: this.userId,
moderator_id: this.moderatorId,
banned_at: this.bannedAt,
expires_at: this.expiresAt,
reason: this.reason,
ip: this.ipAddress,
};
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 {ChannelOverride} from '@fluxer/api/src/database/types/UserTypes';
import {MuteConfiguration} from '@fluxer/api/src/models/MuteConfiguration';
export class GuildChannelOverride {
readonly collapsed: boolean;
readonly messageNotifications: number | null;
readonly muted: boolean;
readonly muteConfig: MuteConfiguration | null;
constructor(override: ChannelOverride) {
this.collapsed = override.collapsed ?? false;
this.messageNotifications = override.message_notifications ?? null;
this.muted = override.muted ?? false;
this.muteConfig = override.mute_config ? new MuteConfiguration(override.mute_config) : null;
}
toChannelOverride(): ChannelOverride {
return {
collapsed: this.collapsed,
message_notifications: this.messageNotifications,
muted: this.muted,
mute_config: this.muteConfig?.toMuteConfig() ?? null,
};
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 {EmojiID, GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {GuildEmojiRow} from '@fluxer/api/src/database/types/GuildTypes';
export class GuildEmoji {
readonly guildId: GuildID;
readonly id: EmojiID;
readonly name: string;
readonly creatorId: UserID;
readonly isAnimated: boolean;
readonly version: number;
constructor(row: GuildEmojiRow) {
this.guildId = row.guild_id;
this.id = row.emoji_id;
this.name = row.name;
this.creatorId = row.creator_id;
this.isAnimated = row.animated ?? false;
this.version = row.version;
}
toRow(): GuildEmojiRow {
return {
guild_id: this.guildId,
emoji_id: this.id,
name: this.name,
creator_id: this.creatorId,
animated: this.isAnimated,
version: this.version,
};
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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 {GuildID, InviteCode, RoleID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {GuildMemberRow} from '@fluxer/api/src/database/types/GuildTypes';
import {GuildMemberProfileFlags} from '@fluxer/constants/src/GuildConstants';
export class GuildMember {
readonly guildId: GuildID;
readonly userId: UserID;
readonly joinedAt: Date;
readonly nickname: string | null;
readonly avatarHash: string | null;
readonly bannerHash: string | null;
readonly bio: string | null;
readonly pronouns: string | null;
readonly accentColor: number | null;
readonly joinSourceType: number | null;
readonly sourceInviteCode: InviteCode | null;
readonly inviterId: UserID | null;
readonly isDeaf: boolean;
readonly isMute: boolean;
readonly communicationDisabledUntil: Date | null;
readonly roleIds: Set<RoleID>;
readonly isPremiumSanitized: boolean;
readonly isTemporary: boolean;
readonly profileFlags: number;
readonly version: number;
constructor(row: GuildMemberRow) {
this.guildId = row.guild_id;
this.userId = row.user_id;
this.joinedAt = row.joined_at;
this.nickname = row.nick ?? null;
this.avatarHash = row.avatar_hash ?? null;
this.bannerHash = row.banner_hash ?? null;
this.bio = row.bio ?? null;
this.pronouns = row.pronouns ?? null;
this.accentColor = row.accent_color ?? null;
this.joinSourceType = row.join_source_type ?? null;
this.sourceInviteCode = row.source_invite_code ?? null;
this.inviterId = row.inviter_id ?? null;
this.isDeaf = row.deaf ?? false;
this.isMute = row.mute ?? false;
this.communicationDisabledUntil = row.communication_disabled_until ?? null;
this.roleIds = row.role_ids ?? new Set();
this.isPremiumSanitized = row.is_premium_sanitized ?? false;
this.isTemporary = row.temporary ?? false;
this.profileFlags = row.profile_flags ?? 0;
this.version = row.version;
}
hasProfileFlag(flag: number): boolean {
return (this.profileFlags & flag) === flag;
}
isAvatarUnset(): boolean {
return this.hasProfileFlag(GuildMemberProfileFlags.AVATAR_UNSET);
}
isBannerUnset(): boolean {
return this.hasProfileFlag(GuildMemberProfileFlags.BANNER_UNSET);
}
toRow(): GuildMemberRow {
return {
guild_id: this.guildId,
user_id: this.userId,
joined_at: this.joinedAt,
nick: this.nickname,
avatar_hash: this.avatarHash,
banner_hash: this.bannerHash,
bio: this.bio,
pronouns: this.pronouns,
accent_color: this.accentColor,
join_source_type: this.joinSourceType,
source_invite_code: this.sourceInviteCode,
inviter_id: this.inviterId,
deaf: this.isDeaf,
mute: this.isMute,
communication_disabled_until: this.communicationDisabledUntil,
role_ids: this.roleIds.size > 0 ? this.roleIds : null,
is_premium_sanitized: this.isPremiumSanitized,
temporary: this.isTemporary,
profile_flags: this.profileFlags || null,
version: this.version,
};
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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 {GuildID, RoleID} from '@fluxer/api/src/BrandedTypes';
import type {GuildRoleRow} from '@fluxer/api/src/database/types/GuildTypes';
export class GuildRole {
readonly guildId: GuildID;
readonly id: RoleID;
readonly name: string;
readonly permissions: bigint;
readonly position: number;
readonly hoistPosition: number | null;
readonly color: number;
readonly iconHash: string | null;
readonly unicodeEmoji: string | null;
readonly isHoisted: boolean;
readonly isMentionable: boolean;
readonly version: number;
constructor(row: GuildRoleRow) {
this.guildId = row.guild_id;
this.id = row.role_id;
this.name = row.name;
this.permissions = row.permissions;
this.position = row.position;
this.hoistPosition = row.hoist_position ?? null;
this.color = row.color ?? 0;
this.iconHash = row.icon_hash ?? null;
this.unicodeEmoji = row.unicode_emoji ?? null;
this.isHoisted = row.hoist ?? false;
this.isMentionable = row.mentionable ?? false;
this.version = row.version;
}
get effectiveHoistPosition(): number {
return this.hoistPosition ?? this.position;
}
toRow(): GuildRoleRow {
return {
guild_id: this.guildId,
role_id: this.id,
name: this.name,
permissions: this.permissions,
position: this.position,
hoist_position: this.hoistPosition,
color: this.color,
icon_hash: this.iconHash,
unicode_emoji: this.unicodeEmoji,
hoist: this.isHoisted,
mentionable: this.isMentionable,
version: this.version,
};
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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 {GuildID, StickerID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {GuildStickerRow} from '@fluxer/api/src/database/types/GuildTypes';
export class GuildSticker {
readonly guildId: GuildID;
readonly id: StickerID;
readonly name: string;
readonly description: string | null;
readonly animated: boolean;
readonly tags: Array<string>;
readonly creatorId: UserID;
readonly version: number;
constructor(row: GuildStickerRow) {
this.guildId = row.guild_id;
this.id = row.sticker_id;
this.name = row.name;
this.description = row.description ?? null;
this.animated = row.animated;
this.tags = row.tags ?? [];
this.creatorId = row.creator_id;
this.version = row.version;
}
toRow(): GuildStickerRow {
return {
guild_id: this.guildId,
sticker_id: this.id,
name: this.name,
description: this.description,
animated: this.animated,
tags: this.tags.length > 0 ? this.tags : null,
creator_id: this.creatorId,
version: this.version,
};
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 {ChannelID, GuildID, InviteCode, UserID} from '@fluxer/api/src/BrandedTypes';
import type {InviteRow} from '@fluxer/api/src/database/types/ChannelTypes';
export class Invite {
readonly code: InviteCode;
readonly type: number;
readonly guildId: GuildID | null;
readonly channelId: ChannelID | null;
readonly inviterId: UserID | null;
readonly createdAt: Date;
readonly uses: number;
readonly maxUses: number;
readonly maxAge: number;
readonly temporary: boolean;
readonly version: number;
constructor(row: InviteRow) {
this.code = row.code;
this.type = row.type;
this.guildId = row.guild_id ?? null;
this.channelId = row.channel_id ?? null;
this.inviterId = row.inviter_id ?? null;
this.createdAt = row.created_at;
this.uses = row.uses ?? 0;
this.maxUses = row.max_uses ?? 0;
this.maxAge = row.max_age ?? 0;
this.temporary = row.temporary ?? false;
this.version = row.version;
}
toRow(): InviteRow {
return {
code: this.code,
type: this.type,
guild_id: this.guildId,
channel_id: this.channelId,
inviter_id: this.inviterId,
created_at: this.createdAt,
uses: this.uses,
max_uses: this.maxUses,
max_age: this.maxAge,
temporary: this.temporary,
version: this.version,
};
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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 {ChannelID, MessageID, RoleID, UserID, WebhookID} from '@fluxer/api/src/BrandedTypes';
import type {MessageRow} from '@fluxer/api/src/database/types/MessageTypes';
import {Attachment} from '@fluxer/api/src/models/Attachment';
import {CallInfo} from '@fluxer/api/src/models/CallInfo';
import {Embed} from '@fluxer/api/src/models/Embed';
import {MessageRef} from '@fluxer/api/src/models/MessageRef';
import {MessageSnapshot} from '@fluxer/api/src/models/MessageSnapshot';
import {StickerItem} from '@fluxer/api/src/models/StickerItem';
export class Message {
readonly channelId: ChannelID;
readonly bucket: number;
readonly id: MessageID;
readonly authorId: UserID | null;
readonly type: number;
readonly webhookId: WebhookID | null;
readonly webhookName: string | null;
readonly webhookAvatarHash: string | null;
readonly content: string | null;
readonly editedTimestamp: Date | null;
readonly pinnedTimestamp: Date | null;
readonly flags: number;
readonly mentionEveryone: boolean;
readonly mentionedUserIds: Set<UserID>;
readonly mentionedRoleIds: Set<RoleID>;
readonly mentionedChannelIds: Set<ChannelID>;
readonly attachments: Array<Attachment>;
readonly embeds: Array<Embed>;
readonly stickers: Array<StickerItem>;
readonly reference: MessageRef | null;
readonly messageSnapshots: Array<MessageSnapshot>;
readonly call: CallInfo | null;
readonly hasReaction: boolean | null;
readonly version: number;
constructor(row: MessageRow) {
this.channelId = row.channel_id;
this.bucket = row.bucket;
this.id = row.message_id;
this.authorId = row.author_id ?? null;
this.type = row.type;
this.webhookId = row.webhook_id ?? null;
this.webhookName = row.webhook_name ?? null;
this.webhookAvatarHash = row.webhook_avatar_hash ?? null;
this.content = row.content ?? null;
this.editedTimestamp = row.edited_timestamp ?? null;
this.pinnedTimestamp = row.pinned_timestamp ?? null;
this.flags = row.flags ?? 0;
this.mentionEveryone = row.mention_everyone ?? false;
this.mentionedUserIds = row.mention_users ?? new Set();
this.mentionedRoleIds = row.mention_roles ?? new Set();
this.mentionedChannelIds = row.mention_channels ?? new Set();
this.attachments = (row.attachments ?? []).map((att) => new Attachment(att));
this.embeds = (row.embeds ?? []).map((embed) => new Embed(embed));
this.stickers = (row.sticker_items ?? []).map((sticker) => new StickerItem(sticker));
this.reference = row.message_reference ? new MessageRef(row.message_reference) : null;
this.messageSnapshots = (row.message_snapshots ?? []).map((snapshot) => new MessageSnapshot(snapshot));
this.call = row.call ? new CallInfo(row.call) : null;
this.hasReaction = row.has_reaction ?? null;
this.version = row.version;
}
toRow(): MessageRow {
return {
channel_id: this.channelId,
bucket: this.bucket,
message_id: this.id,
author_id: this.authorId,
type: this.type,
webhook_id: this.webhookId,
webhook_name: this.webhookName,
webhook_avatar_hash: this.webhookAvatarHash,
content: this.content,
edited_timestamp: this.editedTimestamp,
pinned_timestamp: this.pinnedTimestamp,
flags: this.flags,
mention_everyone: this.mentionEveryone,
mention_users: this.mentionedUserIds.size > 0 ? this.mentionedUserIds : null,
mention_roles: this.mentionedRoleIds.size > 0 ? this.mentionedRoleIds : null,
mention_channels: this.mentionedChannelIds.size > 0 ? this.mentionedChannelIds : null,
attachments: this.attachments.length > 0 ? this.attachments.map((att) => att.toMessageAttachment()) : null,
embeds: this.embeds.length > 0 ? this.embeds.map((embed) => embed.toMessageEmbed()) : null,
sticker_items: this.stickers.length > 0 ? this.stickers.map((sticker) => sticker.toMessageStickerItem()) : null,
message_reference: this.reference?.toMessageReference() ?? null,
message_snapshots:
this.messageSnapshots.length > 0 ? this.messageSnapshots.map((snapshot) => snapshot.toMessageSnapshot()) : null,
call: this.call?.toMessageCall() ?? null,
has_reaction: this.hasReaction ?? null,
version: this.version,
};
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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 {ChannelID, EmojiID, MessageID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {MessageReactionRow} from '@fluxer/api/src/database/types/MessageTypes';
export class MessageReaction {
readonly channelId: ChannelID;
readonly bucket: number;
readonly messageId: MessageID;
readonly userId: UserID;
readonly emojiId: EmojiID;
readonly emojiName: string;
readonly isEmojiAnimated: boolean;
constructor(row: MessageReactionRow) {
this.channelId = row.channel_id;
this.bucket = row.bucket;
this.messageId = row.message_id;
this.userId = row.user_id;
this.emojiId = row.emoji_id;
this.emojiName = row.emoji_name;
this.isEmojiAnimated = row.emoji_animated ?? false;
}
toRow(): MessageReactionRow {
return {
channel_id: this.channelId,
bucket: this.bucket,
message_id: this.messageId,
user_id: this.userId,
emoji_id: this.emojiId,
emoji_name: this.emojiName,
emoji_animated: this.isEmojiAnimated,
};
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 {ChannelID, GuildID, MessageID} from '@fluxer/api/src/BrandedTypes';
import type {MessageReference} from '@fluxer/api/src/database/types/MessageTypes';
export class MessageRef {
readonly channelId: ChannelID;
readonly messageId: MessageID;
readonly guildId: GuildID | null;
readonly type: number;
constructor(ref: MessageReference) {
this.channelId = ref.channel_id;
this.messageId = ref.message_id;
this.guildId = ref.guild_id ?? null;
this.type = ref.type;
}
toMessageReference(): MessageReference {
return {
channel_id: this.channelId,
message_id: this.messageId,
guild_id: this.guildId,
type: this.type,
};
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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 {ChannelID, RoleID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {MessageSnapshot as CassandraMessageSnapshot} from '@fluxer/api/src/database/types/MessageTypes';
import {Attachment} from '@fluxer/api/src/models/Attachment';
import {Embed} from '@fluxer/api/src/models/Embed';
import {StickerItem} from '@fluxer/api/src/models/StickerItem';
export class MessageSnapshot {
readonly content: string | null;
readonly timestamp: Date;
readonly editedTimestamp: Date | null;
readonly mentionedUserIds: Set<UserID>;
readonly mentionedRoleIds: Set<RoleID>;
readonly mentionedChannelIds: Set<ChannelID>;
readonly attachments: Array<Attachment>;
readonly embeds: Array<Embed>;
readonly stickers: Array<StickerItem>;
readonly type: number;
readonly flags: number;
constructor(snapshot: CassandraMessageSnapshot) {
this.content = snapshot.content ?? null;
this.timestamp = new Date(snapshot.timestamp);
this.editedTimestamp = snapshot.edited_timestamp ? new Date(snapshot.edited_timestamp) : null;
this.mentionedUserIds = snapshot.mention_users ?? new Set();
this.mentionedRoleIds = snapshot.mention_roles ?? new Set();
this.mentionedChannelIds = snapshot.mention_channels ?? new Set();
this.attachments = (snapshot.attachments ?? []).map((att) => new Attachment(att));
this.embeds = (snapshot.embeds ?? []).map((embed) => new Embed(embed));
this.stickers = (snapshot.sticker_items ?? []).map((sticker) => new StickerItem(sticker));
this.type = snapshot.type;
this.flags = snapshot.flags;
}
toMessageSnapshot(): CassandraMessageSnapshot {
return {
content: this.content,
timestamp: this.timestamp,
edited_timestamp: this.editedTimestamp,
mention_users: this.mentionedUserIds.size > 0 ? this.mentionedUserIds : null,
mention_roles: this.mentionedRoleIds.size > 0 ? this.mentionedRoleIds : null,
mention_channels: this.mentionedChannelIds.size > 0 ? this.mentionedChannelIds : null,
attachments: this.attachments.length > 0 ? this.attachments.map((att) => att.toMessageAttachment()) : null,
embeds: this.embeds.length > 0 ? this.embeds.map((embed) => embed.toMessageEmbed()) : null,
sticker_items: this.stickers.length > 0 ? this.stickers.map((sticker) => sticker.toMessageStickerItem()) : null,
type: this.type,
flags: this.flags,
};
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import {createMfaBackupCode} from '@fluxer/api/src/BrandedTypes';
import type {MfaBackupCodeRow} from '@fluxer/api/src/database/types/AuthTypes';
export class MfaBackupCode {
readonly userId: UserID;
readonly code: string;
readonly consumed: boolean;
constructor(row: MfaBackupCodeRow) {
this.userId = row.user_id;
this.code = row.code;
this.consumed = row.consumed ?? false;
}
toRow(): MfaBackupCodeRow {
return {
user_id: this.userId,
code: createMfaBackupCode(this.code),
consumed: this.consumed,
};
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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 {MuteConfig} from '@fluxer/api/src/database/types/UserTypes';
export class MuteConfiguration {
readonly endTime: Date | null;
readonly selectedTimeWindow: number | null;
constructor(config: MuteConfig) {
this.endTime = config.end_time ?? null;
this.selectedTimeWindow = config.selected_time_window ?? null;
}
toMuteConfig(): MuteConfig {
return {
end_time: this.endTime,
selected_time_window: this.selectedTimeWindow,
};
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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 {ApplicationID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {OAuth2AccessTokenRow} from '@fluxer/api/src/database/types/OAuth2Types';
export class OAuth2AccessToken {
readonly token: string;
readonly applicationId: ApplicationID;
readonly userId: UserID | null;
readonly scope: Set<string>;
readonly createdAt: Date;
constructor(row: OAuth2AccessTokenRow) {
this.token = row.token_;
this.applicationId = row.application_id;
this.userId = row.user_id;
this.scope = row.scope;
this.createdAt = row.created_at;
}
toRow(): OAuth2AccessTokenRow {
return {
token_: this.token,
application_id: this.applicationId,
user_id: this.userId,
scope: this.scope,
created_at: this.createdAt,
};
}
hasScope(scope: string): boolean {
return this.scope.has(scope);
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 {ApplicationID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {OAuth2AuthorizationCodeRow} from '@fluxer/api/src/database/types/OAuth2Types';
export class OAuth2AuthorizationCode {
readonly code: string;
readonly applicationId: ApplicationID;
readonly userId: UserID;
readonly redirectUri: string;
readonly scope: Set<string>;
readonly nonce: string | null;
readonly createdAt: Date;
constructor(row: OAuth2AuthorizationCodeRow) {
this.code = row.code;
this.applicationId = row.application_id;
this.userId = row.user_id;
this.redirectUri = row.redirect_uri;
this.scope = row.scope;
this.nonce = row.nonce;
this.createdAt = row.created_at;
}
toRow(): OAuth2AuthorizationCodeRow {
return {
code: this.code,
application_id: this.applicationId,
user_id: this.userId,
redirect_uri: this.redirectUri,
scope: this.scope,
nonce: this.nonce,
created_at: this.createdAt,
};
}
hasScope(scope: string): boolean {
return this.scope.has(scope);
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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 {ApplicationID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {OAuth2RefreshTokenRow} from '@fluxer/api/src/database/types/OAuth2Types';
export class OAuth2RefreshToken {
readonly token: string;
readonly applicationId: ApplicationID;
readonly userId: UserID;
readonly scope: Set<string>;
readonly createdAt: Date;
constructor(row: OAuth2RefreshTokenRow) {
this.token = row.token_;
this.applicationId = row.application_id;
this.userId = row.user_id;
this.scope = row.scope;
this.createdAt = row.created_at;
}
toRow(): OAuth2RefreshTokenRow {
return {
token_: this.token,
application_id: this.applicationId,
user_id: this.userId,
scope: this.scope,
created_at: this.createdAt,
};
}
hasScope(scope: string): boolean {
return this.scope.has(scope);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import {createPasswordResetToken} from '@fluxer/api/src/BrandedTypes';
import type {PasswordResetTokenRow} from '@fluxer/api/src/database/types/AuthTypes';
export class PasswordResetToken {
readonly token: string;
readonly userId: UserID;
readonly email: string;
constructor(row: PasswordResetTokenRow) {
this.token = row.token_;
this.userId = row.user_id;
this.email = row.email;
}
toRow(): PasswordResetTokenRow {
return {
token_: createPasswordResetToken(this.token),
user_id: this.userId,
email: this.email,
};
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {PaymentRow} from '@fluxer/api/src/database/types/PaymentTypes';
export class Payment {
readonly checkoutSessionId: string;
readonly userId: UserID;
readonly stripeCustomerId: string | null;
readonly paymentIntentId: string | null;
readonly subscriptionId: string | null;
readonly invoiceId: string | null;
readonly priceId: string | null;
readonly productType: string | null;
readonly amountCents: number;
readonly currency: string;
readonly status: string;
readonly isGift: boolean;
readonly giftCode: string | null;
readonly createdAt: Date;
readonly completedAt: Date | null;
readonly version: number;
constructor(row: PaymentRow) {
this.checkoutSessionId = row.checkout_session_id;
this.userId = row.user_id as UserID;
this.stripeCustomerId = row.stripe_customer_id ?? null;
this.paymentIntentId = row.payment_intent_id ?? null;
this.subscriptionId = row.subscription_id ?? null;
this.invoiceId = row.invoice_id ?? null;
this.priceId = row.price_id ?? null;
this.productType = row.product_type ?? null;
this.amountCents = row.amount_cents;
this.currency = row.currency;
this.status = row.status;
this.isGift = row.is_gift;
this.giftCode = row.gift_code ?? null;
this.createdAt = row.created_at;
this.completedAt = row.completed_at ?? null;
this.version = row.version;
}
toRow(): PaymentRow {
return {
checkout_session_id: this.checkoutSessionId,
user_id: this.userId,
stripe_customer_id: this.stripeCustomerId,
payment_intent_id: this.paymentIntentId,
subscription_id: this.subscriptionId,
invoice_id: this.invoiceId,
price_id: this.priceId,
product_type: this.productType,
amount_cents: this.amountCents,
currency: this.currency,
status: this.status,
is_gift: this.isGift,
gift_code: this.giftCode,
created_at: this.createdAt,
completed_at: this.completedAt,
version: this.version,
};
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {PushSubscriptionRow} from '@fluxer/api/src/database/types/UserTypes';
export class PushSubscription {
readonly userId: UserID;
readonly subscriptionId: string;
readonly endpoint: string;
readonly p256dhKey: string;
readonly authKey: string;
readonly userAgent: string | null;
constructor(row: PushSubscriptionRow) {
this.userId = row.user_id;
this.subscriptionId = row.subscription_id;
this.endpoint = row.endpoint;
this.p256dhKey = row.p256dh_key;
this.authKey = row.auth_key;
this.userAgent = row.user_agent ?? null;
}
toRow(): PushSubscriptionRow {
return {
user_id: this.userId,
subscription_id: this.subscriptionId,
endpoint: this.endpoint,
p256dh_key: this.p256dhKey,
auth_key: this.authKey,
user_agent: this.userAgent,
};
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 {ChannelID, MessageID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {ReadStateRow} from '@fluxer/api/src/database/types/ChannelTypes';
export class ReadState {
readonly userId: UserID;
readonly channelId: ChannelID;
readonly lastMessageId: MessageID | null;
readonly mentionCount: number;
readonly lastPinTimestamp: Date | null;
constructor(row: ReadStateRow) {
this.userId = row.user_id;
this.channelId = row.channel_id;
this.lastMessageId = row.message_id ?? null;
this.mentionCount = row.mention_count ?? 0;
this.lastPinTimestamp = row.last_pin_timestamp ?? null;
}
toRow(): ReadStateRow {
return {
user_id: this.userId,
channel_id: this.channelId,
message_id: this.lastMessageId,
mention_count: this.mentionCount,
last_pin_timestamp: this.lastPinTimestamp,
};
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 {ChannelID, GuildID, MessageID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {RecentMentionRow} from '@fluxer/api/src/database/types/UserTypes';
export class RecentMention {
readonly userId: UserID;
readonly channelId: ChannelID;
readonly messageId: MessageID;
readonly guildId: GuildID;
readonly isEveryone: boolean;
readonly isRole: boolean;
constructor(row: RecentMentionRow) {
this.userId = row.user_id;
this.channelId = row.channel_id;
this.messageId = row.message_id;
this.guildId = row.guild_id;
this.isEveryone = row.is_everyone;
this.isRole = row.is_role;
}
toRow(): RecentMentionRow {
return {
user_id: this.userId,
channel_id: this.channelId,
message_id: this.messageId,
guild_id: this.guildId,
is_everyone: this.isEveryone,
is_role: this.isRole,
};
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {RelationshipRow} from '@fluxer/api/src/database/types/UserTypes';
export class Relationship {
readonly sourceUserId: UserID;
readonly targetUserId: UserID;
readonly type: number;
readonly nickname: string | null;
readonly since: Date | null;
readonly version: number;
constructor(row: RelationshipRow) {
this.sourceUserId = row.source_user_id;
this.targetUserId = row.target_user_id;
this.type = row.type;
this.nickname = row.nickname ?? null;
this.since = row.since ?? null;
this.version = row.version;
}
toRow(): RelationshipRow {
return {
source_user_id: this.sourceUserId,
target_user_id: this.targetUserId,
type: this.type,
nickname: this.nickname,
since: this.since,
version: this.version,
};
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 {ChannelID, MessageID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {SavedMessageRow} from '@fluxer/api/src/database/types/UserTypes';
export class SavedMessage {
readonly userId: UserID;
readonly channelId: ChannelID;
readonly messageId: MessageID;
readonly savedAt: Date;
constructor(row: SavedMessageRow) {
this.userId = row.user_id;
this.channelId = row.channel_id;
this.messageId = row.message_id;
this.savedAt = row.saved_at;
}
toRow(): SavedMessageRow {
return {
user_id: this.userId,
channel_id: this.channelId,
message_id: this.messageId,
saved_at: this.savedAt,
};
}
}

View File

@@ -0,0 +1,198 @@
/*
* 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 {ChannelID, MessageID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {AttachmentRequestData} from '@fluxer/api/src/channel/AttachmentDTOs';
import type {MessageRequest} from '@fluxer/api/src/channel/MessageTypes';
import type {ScheduledMessageRow} from '@fluxer/api/src/database/types/UserTypes';
import type {AllowedMentionsRequest} from '@fluxer/schema/src/domains/message/SharedMessageSchemas';
export type ScheduledMessageStatus = 'pending' | 'invalid';
export interface ScheduledMessageReference {
message_id: string;
channel_id?: string;
guild_id?: string;
type?: number;
}
export interface ScheduledAllowedMentions {
parse?: AllowedMentionsRequest['parse'];
users?: Array<string>;
roles?: Array<string>;
replied_user?: boolean;
}
export interface ScheduledMessagePayload {
content?: string | null;
embeds?: MessageRequest['embeds'];
attachments?: Array<AttachmentRequestData>;
message_reference?: ScheduledMessageReference;
allowed_mentions?: ScheduledAllowedMentions;
flags?: number;
nonce?: string;
favorite_meme_id?: string;
sticker_ids?: Array<string>;
tts?: boolean;
}
export interface ScheduledMessageResponse {
id: string;
channel_id: string;
scheduled_at: string;
scheduled_local_at: string;
timezone: string;
status: ScheduledMessageStatus;
status_reason: string | null;
payload: ScheduledMessagePayload;
created_at: string;
invalidated_at: string | null;
}
export class ScheduledMessage {
readonly userId: UserID;
readonly id: MessageID;
readonly channelId: ChannelID;
readonly scheduledAt: Date;
readonly scheduledLocalAt: string;
readonly timezone: string;
readonly payload: ScheduledMessagePayload;
readonly status: ScheduledMessageStatus;
readonly statusReason: string | null;
readonly createdAt: Date;
readonly invalidatedAt: Date | null;
constructor(params: {
userId: UserID;
id: MessageID;
channelId: ChannelID;
scheduledAt: Date;
scheduledLocalAt: string;
timezone: string;
payload: ScheduledMessagePayload;
status?: ScheduledMessageStatus;
statusReason?: string | null;
createdAt?: Date;
invalidatedAt?: Date | null;
}) {
this.userId = params.userId;
this.id = params.id;
this.channelId = params.channelId;
this.scheduledAt = params.scheduledAt;
this.scheduledLocalAt = params.scheduledLocalAt;
this.timezone = params.timezone;
this.payload = params.payload;
this.status = params.status ?? 'pending';
this.statusReason = params.statusReason ?? null;
this.createdAt = params.createdAt ?? new Date();
this.invalidatedAt = params.invalidatedAt ?? null;
}
static fromRow(row: ScheduledMessageRow): ScheduledMessage {
return new ScheduledMessage({
userId: row.user_id,
id: row.scheduled_message_id,
channelId: row.channel_id,
scheduledAt: row.scheduled_at,
scheduledLocalAt: row.scheduled_local_at,
timezone: row.timezone,
payload: parsePayload(row.payload),
status: toStatus(row.status),
statusReason: row.status_reason,
createdAt: row.created_at,
invalidatedAt: row.invalidated_at,
});
}
toRow(): ScheduledMessageRow {
return {
user_id: this.userId,
scheduled_message_id: this.id,
channel_id: this.channelId,
payload: JSON.stringify(this.payload),
scheduled_at: this.scheduledAt,
scheduled_local_at: this.scheduledLocalAt,
timezone: this.timezone,
status: this.status,
status_reason: this.statusReason,
created_at: this.createdAt,
invalidated_at: this.invalidatedAt,
};
}
toResponse(): ScheduledMessageResponse {
return {
id: this.id.toString(),
channel_id: this.channelId.toString(),
scheduled_at: this.scheduledAt.toISOString(),
scheduled_local_at: this.scheduledLocalAt,
timezone: this.timezone,
status: this.status,
status_reason: this.statusReason,
payload: this.payload,
created_at: this.createdAt.toISOString(),
invalidated_at: this.invalidatedAt?.toISOString() ?? null,
};
}
parseToMessageRequest(): MessageRequest {
return hydrateMessageRequest(this.payload);
}
}
function parsePayload(payload: string): ScheduledMessagePayload {
try {
return JSON.parse(payload) as ScheduledMessagePayload;
} catch (_error) {
return {};
}
}
function toStatus(value: string): ScheduledMessageStatus {
return value === 'invalid' ? 'invalid' : 'pending';
}
function hydrateMessageRequest(payload: ScheduledMessagePayload): MessageRequest {
const message: MessageRequest = {
content: payload.content,
embeds: payload.embeds ?? [],
attachments: payload.attachments,
message_reference: payload.message_reference
? {
...payload.message_reference,
message_id: BigInt(payload.message_reference.message_id),
channel_id: payload.message_reference.channel_id ? BigInt(payload.message_reference.channel_id) : undefined,
guild_id: payload.message_reference.guild_id ? BigInt(payload.message_reference.guild_id) : undefined,
}
: undefined,
allowed_mentions: payload.allowed_mentions
? {
parse: payload.allowed_mentions.parse,
users: payload.allowed_mentions.users?.map((value) => BigInt(value)),
roles: payload.allowed_mentions.roles?.map((value) => BigInt(value)),
replied_user: payload.allowed_mentions.replied_user,
}
: undefined,
flags: payload.flags,
nonce: payload.nonce,
favorite_meme_id: payload.favorite_meme_id ? BigInt(payload.favorite_meme_id) : undefined,
sticker_ids: payload.sticker_ids?.map((value) => BigInt(value)),
tts: payload.tts,
};
return message;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 {StickerID} from '@fluxer/api/src/BrandedTypes';
import type {MessageStickerItem} from '@fluxer/api/src/database/types/MessageTypes';
export class StickerItem {
readonly id: StickerID;
readonly name: string;
readonly animated: boolean;
constructor(sticker: MessageStickerItem) {
this.id = sticker.sticker_id;
this.name = sticker.name;
this.animated = sticker.animated ?? false;
}
toMessageStickerItem(): MessageStickerItem {
return {
sticker_id: this.id,
name: this.name,
animated: this.animated,
};
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {UserRow} from '@fluxer/api/src/database/types/UserTypes';
import {getGlobalLimitConfigSnapshot} from '@fluxer/api/src/limits/LimitConfigService';
import {resolveLimitSafe} from '@fluxer/api/src/limits/LimitConfigUtils';
import {createLimitMatchContext} from '@fluxer/api/src/limits/LimitMatchContextBuilder';
import {checkIsPremium} from '@fluxer/api/src/user/UserHelpers';
import {types} from 'cassandra-driver';
export class User {
readonly id: UserID;
readonly username: string;
readonly discriminator: number;
readonly globalName: string | null;
readonly isBot: boolean;
readonly isSystem: boolean;
readonly email: string | null;
readonly emailVerified: boolean;
readonly emailBounced: boolean;
readonly phone: string | null;
readonly passwordHash: string | null;
readonly passwordLastChangedAt: Date | null;
readonly totpSecret: string | null;
readonly authenticatorTypes: Set<number>;
readonly avatarHash: string | null;
readonly avatarColor: number | null;
readonly bannerHash: string | null;
readonly bannerColor: number | null;
readonly bio: string | null;
readonly pronouns: string | null;
readonly accentColor: number | null;
readonly dateOfBirth: string | null;
readonly locale: string | null;
readonly flags: bigint;
readonly premiumType: number | null;
readonly premiumSince: Date | null;
readonly premiumUntil: Date | null;
readonly premiumWillCancel: boolean;
readonly premiumBillingCycle: string | null;
readonly premiumLifetimeSequence: number | null;
readonly stripeSubscriptionId: string | null;
readonly stripeCustomerId: string | null;
readonly hasEverPurchased: boolean;
readonly suspiciousActivityFlags: number;
readonly termsAgreedAt: Date | null;
readonly privacyAgreedAt: Date | null;
readonly lastActiveAt: Date | null;
readonly lastActiveIp: string | null;
readonly tempBannedUntil: Date | null;
readonly pendingBulkMessageDeletionAt: Date | null;
readonly pendingBulkMessageDeletionChannelCount: number | null;
readonly pendingBulkMessageDeletionMessageCount: number | null;
readonly pendingDeletionAt: Date | null;
readonly deletionReasonCode: number | null;
readonly deletionPublicReason: string | null;
readonly deletionAuditLogReason: string | null;
readonly acls: Set<string>;
private readonly _traits: Set<string>;
readonly firstRefundAt: Date | null;
readonly giftInventoryServerSeq: number | null;
readonly giftInventoryClientSeq: number | null;
readonly premiumOnboardingDismissedAt: Date | null;
readonly version: number;
constructor(row: UserRow) {
this.id = row.user_id;
this.username = row.username;
this.discriminator = row.discriminator;
this.globalName = row.global_name ?? null;
this.isBot = row.bot ?? false;
this.isSystem = row.system ?? false;
this.email = row.email ?? null;
this.emailVerified = row.email_verified ?? false;
this.emailBounced = row.email_bounced ?? false;
this.phone = row.phone ?? null;
this.passwordHash = row.password_hash ?? null;
this.passwordLastChangedAt = row.password_last_changed_at ?? null;
this.totpSecret = row.totp_secret ?? null;
this.authenticatorTypes = row.authenticator_types ?? new Set();
this.avatarHash = row.avatar_hash ?? null;
this.avatarColor = row.avatar_color ?? null;
this.bannerHash = row.banner_hash ?? null;
this.bannerColor = row.banner_color ?? null;
this.bio = row.bio ?? null;
this.pronouns = row.pronouns ?? null;
this.accentColor = row.accent_color ?? null;
this.dateOfBirth = row.date_of_birth ? row.date_of_birth.toString() : null;
this.locale = row.locale ?? null;
this.flags = row.flags ?? 0n;
this.premiumType = row.premium_type ?? null;
this.premiumSince = row.premium_since ?? null;
this.premiumUntil = row.premium_until ?? null;
this.premiumWillCancel = row.premium_will_cancel ?? false;
this.premiumBillingCycle = row.premium_billing_cycle ?? null;
this.premiumLifetimeSequence = row.premium_lifetime_sequence ?? null;
this.stripeSubscriptionId = row.stripe_subscription_id ?? null;
this.stripeCustomerId = row.stripe_customer_id ?? null;
this.hasEverPurchased = row.has_ever_purchased ?? false;
this.suspiciousActivityFlags = row.suspicious_activity_flags ?? 0;
this.termsAgreedAt = row.terms_agreed_at ?? null;
this.privacyAgreedAt = row.privacy_agreed_at ?? null;
this.lastActiveAt = row.last_active_at ?? null;
this.lastActiveIp = row.last_active_ip ?? null;
this.tempBannedUntil = row.temp_banned_until ?? null;
this.pendingBulkMessageDeletionAt = row.pending_bulk_message_deletion_at ?? null;
this.pendingBulkMessageDeletionChannelCount = row.pending_bulk_message_deletion_channel_count ?? null;
this.pendingBulkMessageDeletionMessageCount = row.pending_bulk_message_deletion_message_count ?? null;
this.pendingDeletionAt = row.pending_deletion_at ?? null;
this.deletionReasonCode = row.deletion_reason_code ?? null;
this.deletionPublicReason = row.deletion_public_reason ?? null;
this.deletionAuditLogReason = row.deletion_audit_log_reason ?? null;
this.acls = row.acls ?? new Set();
this._traits = row.traits ?? new Set();
this.firstRefundAt = row.first_refund_at ?? null;
this.giftInventoryServerSeq = row.gift_inventory_server_seq ?? null;
this.giftInventoryClientSeq = row.gift_inventory_client_seq ?? null;
this.premiumOnboardingDismissedAt = row.premium_onboarding_dismissed_at ?? null;
this.version = row.version;
}
get traits(): Set<string> {
return new Set(this._traits);
}
isPremium(): boolean {
return checkIsPremium(this);
}
isUnclaimedAccount(): boolean {
return this.passwordHash === null && !this.isBot;
}
canUseGlobalExpressions(): boolean {
if (this.isBot) {
return true;
}
const ctx = createLimitMatchContext({user: this});
const snapshot = getGlobalLimitConfigSnapshot();
const hasGlobalExpressions = resolveLimitSafe(snapshot, ctx, 'feature_global_expressions', 0);
return hasGlobalExpressions > 0;
}
toRow(): UserRow {
return {
user_id: this.id,
username: this.username,
discriminator: this.discriminator,
global_name: this.globalName,
bot: this.isBot,
system: this.isSystem,
email: this.email,
email_verified: this.emailVerified,
email_bounced: this.emailBounced,
phone: this.phone,
password_hash: this.passwordHash,
password_last_changed_at: this.passwordLastChangedAt,
totp_secret: this.totpSecret,
authenticator_types: this.authenticatorTypes.size > 0 ? this.authenticatorTypes : null,
avatar_hash: this.avatarHash,
avatar_color: this.avatarColor,
banner_hash: this.bannerHash,
banner_color: this.bannerColor,
bio: this.bio,
pronouns: this.pronouns,
accent_color: this.accentColor,
date_of_birth: this.dateOfBirth ? types.LocalDate.fromString(this.dateOfBirth) : null,
locale: this.locale,
flags: this.flags,
premium_type: this.premiumType,
premium_since: this.premiumSince,
premium_until: this.premiumUntil,
premium_will_cancel: this.premiumWillCancel,
premium_billing_cycle: this.premiumBillingCycle,
premium_lifetime_sequence: this.premiumLifetimeSequence,
stripe_subscription_id: this.stripeSubscriptionId,
stripe_customer_id: this.stripeCustomerId,
has_ever_purchased: this.hasEverPurchased,
suspicious_activity_flags: this.suspiciousActivityFlags,
terms_agreed_at: this.termsAgreedAt,
privacy_agreed_at: this.privacyAgreedAt,
last_active_at: this.lastActiveAt,
last_active_ip: this.lastActiveIp,
temp_banned_until: this.tempBannedUntil,
pending_bulk_message_deletion_at: this.pendingBulkMessageDeletionAt,
pending_bulk_message_deletion_channel_count: this.pendingBulkMessageDeletionChannelCount,
pending_bulk_message_deletion_message_count: this.pendingBulkMessageDeletionMessageCount,
pending_deletion_at: this.pendingDeletionAt,
deletion_reason_code: this.deletionReasonCode,
deletion_public_reason: this.deletionPublicReason,
deletion_audit_log_reason: this.deletionAuditLogReason,
acls: this.acls.size > 0 ? this.acls : null,
traits: this._traits.size > 0 ? this._traits : null,
first_refund_at: this.firstRefundAt,
gift_inventory_server_seq: this.giftInventoryServerSeq,
gift_inventory_client_seq: this.giftInventoryClientSeq,
premium_onboarding_dismissed_at: this.premiumOnboardingDismissedAt,
version: this.version,
};
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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 {EmojiID} from '@fluxer/api/src/BrandedTypes';
import type {CustomStatus} from '@fluxer/api/src/database/types/UserTypes';
export class UserCustomStatus {
readonly text: string | null;
readonly emojiId: EmojiID | null;
readonly emojiName: string | null;
readonly emojiAnimated: boolean;
readonly expiresAt: Date | null;
constructor(status: CustomStatus) {
this.text = status.text ?? null;
this.emojiId = status.emoji_id ?? null;
this.emojiName = status.emoji_name ?? null;
this.emojiAnimated = status.emoji_animated ?? false;
this.expiresAt = status.expires_at ?? null;
}
toCustomStatus(): CustomStatus {
return {
text: this.text,
emoji_id: this.emojiId,
emoji_name: this.emojiName,
emoji_animated: this.emojiAnimated,
expires_at: this.expiresAt,
};
}
isExpired(referenceTime: Date = new Date()): boolean {
return this.expiresAt !== null && this.expiresAt.getTime() <= referenceTime.getTime();
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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 {GuildID} from '@fluxer/api/src/BrandedTypes';
import type {GuildFolder} from '@fluxer/api/src/database/types/UserTypes';
import {DEFAULT_GUILD_FOLDER_ICON, type GuildFolderIcon} from '@fluxer/constants/src/UserConstants';
export class UserGuildFolder {
readonly folderId: number;
readonly name: string | null;
readonly color: number | null;
readonly flags: number;
readonly icon: GuildFolderIcon;
readonly guildIds: Array<GuildID>;
constructor(folder: GuildFolder) {
this.folderId = folder.folder_id;
this.name = folder.name ?? null;
this.color = folder.color ?? null;
this.flags = folder.flags ?? 0;
this.icon = folder.icon ?? DEFAULT_GUILD_FOLDER_ICON;
this.guildIds = folder.guild_ids ?? [];
}
toGuildFolder(): GuildFolder {
return {
folder_id: this.folderId,
name: this.name,
color: this.color,
flags: this.flags,
icon: this.icon,
guild_ids: this.guildIds.length > 0 ? this.guildIds : null,
};
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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 {ChannelID, GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
import type {ChannelOverride, UserGuildSettingsRow} from '@fluxer/api/src/database/types/UserTypes';
import {GuildChannelOverride} from '@fluxer/api/src/models/GuildChannelOverride';
import {MuteConfiguration} from '@fluxer/api/src/models/MuteConfiguration';
export class UserGuildSettings {
readonly userId: UserID;
readonly guildId: GuildID;
readonly messageNotifications: number | null;
readonly muted: boolean;
readonly muteConfig: MuteConfiguration | null;
readonly mobilePush: boolean;
readonly suppressEveryone: boolean;
readonly suppressRoles: boolean;
readonly hideMutedChannels: boolean;
readonly channelOverrides: Map<ChannelID, GuildChannelOverride>;
readonly version: number;
constructor(row: UserGuildSettingsRow) {
this.userId = row.user_id;
this.guildId = row.guild_id;
this.messageNotifications = row.message_notifications ?? null;
this.muted = row.muted ?? false;
this.muteConfig = row.mute_config ? new MuteConfiguration(row.mute_config) : null;
this.mobilePush = row.mobile_push ?? false;
this.suppressEveryone = row.suppress_everyone ?? false;
this.suppressRoles = row.suppress_roles ?? false;
this.hideMutedChannels = row.hide_muted_channels ?? false;
this.channelOverrides = new Map();
if (row.channel_overrides) {
for (const [channelId, override] of row.channel_overrides) {
this.channelOverrides.set(channelId, new GuildChannelOverride(override));
}
}
this.version = row.version;
}
toRow(): UserGuildSettingsRow {
const channelOverridesMap: Map<ChannelID, ChannelOverride> | null =
this.channelOverrides.size > 0
? new Map(
Array.from(this.channelOverrides.entries()).map(([id, override]) => [id, override.toChannelOverride()]),
)
: null;
return {
user_id: this.userId,
guild_id: this.guildId,
message_notifications: this.messageNotifications,
muted: this.muted,
mute_config: this.muteConfig?.toMuteConfig() ?? null,
mobile_push: this.mobilePush,
suppress_everyone: this.suppressEveryone,
suppress_roles: this.suppressRoles,
hide_muted_channels: this.hideMutedChannels,
channel_overrides: channelOverridesMap,
version: this.version,
};
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {NoteRow} from '@fluxer/api/src/database/types/UserTypes';
export class UserNote {
readonly sourceUserId: UserID;
readonly targetUserId: UserID;
readonly note: string;
readonly version: number;
constructor(row: NoteRow) {
this.sourceUserId = row.source_user_id;
this.targetUserId = row.target_user_id;
this.note = row.note;
this.version = row.version;
}
toRow(): NoteRow {
return {
source_user_id: this.sourceUserId,
target_user_id: this.targetUserId,
note: this.note,
version: this.version,
};
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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 {GuildID, UserID} from '@fluxer/api/src/BrandedTypes';
import {Config} from '@fluxer/api/src/Config';
import type {UserSettingsRow} from '@fluxer/api/src/database/types/UserTypes';
import {UserCustomStatus} from '@fluxer/api/src/models/UserCustomStatus';
import {UserGuildFolder} from '@fluxer/api/src/models/UserGuildFolder';
import type {LocaleCode} from '@fluxer/constants/src/Locales';
import {
DEFAULT_GUILD_FOLDER_ICON,
FriendSourceFlags,
GroupDmAddPermissionFlags,
IncomingCallFlags,
RenderSpoilers,
StickerAnimationOptions,
ThemeTypes,
UNCATEGORIZED_FOLDER_ID,
UserExplicitContentFilterTypes,
} from '@fluxer/constants/src/UserConstants';
export class UserSettings {
readonly userId: UserID;
readonly locale: LocaleCode;
readonly theme: string;
readonly status: string;
readonly statusResetsAt: Date | null;
readonly statusResetsTo: string | null;
readonly customStatus: UserCustomStatus | null;
readonly developerMode: boolean;
readonly compactMessageDisplay: boolean;
readonly animateEmoji: boolean;
readonly animateStickers: number;
readonly gifAutoPlay: boolean;
readonly renderEmbeds: boolean;
readonly renderReactions: boolean;
readonly renderSpoilers: number;
readonly inlineAttachmentMedia: boolean;
readonly inlineEmbedMedia: boolean;
readonly explicitContentFilter: number;
readonly friendSourceFlags: number;
readonly incomingCallFlags: number;
readonly groupDmAddPermissionFlags: number;
readonly defaultGuildsRestricted: boolean;
readonly botDefaultGuildsRestricted: boolean;
readonly restrictedGuilds: Set<GuildID>;
readonly botRestrictedGuilds: Set<GuildID>;
readonly guildPositions: Array<GuildID>;
readonly guildFolders: Array<UserGuildFolder>;
readonly afkTimeout: number;
readonly timeFormat: number;
readonly trustedDomains: Set<string>;
readonly defaultHideMutedChannels: boolean;
readonly version: number;
constructor(row: UserSettingsRow) {
this.userId = row.user_id;
this.locale = row.locale;
this.theme = row.theme;
this.status = row.status;
this.statusResetsAt = row.status_resets_at ?? null;
this.statusResetsTo = row.status_resets_to ?? null;
this.customStatus = row.custom_status ? new UserCustomStatus(row.custom_status) : null;
this.developerMode = row.developer_mode ?? false;
this.compactMessageDisplay = row.message_display_compact ?? false;
this.animateEmoji = row.animate_emoji ?? false;
this.animateStickers = row.animate_stickers ?? 0;
this.gifAutoPlay = row.gif_auto_play ?? false;
this.renderEmbeds = row.render_embeds ?? false;
this.renderReactions = row.render_reactions ?? false;
this.renderSpoilers = row.render_spoilers ?? 0;
this.inlineAttachmentMedia = row.inline_attachment_media ?? false;
this.inlineEmbedMedia = row.inline_embed_media ?? false;
this.explicitContentFilter = row.explicit_content_filter ?? 0;
this.friendSourceFlags = row.friend_source_flags ?? 0;
this.incomingCallFlags = row.incoming_call_flags ?? 0;
this.groupDmAddPermissionFlags = row.group_dm_add_permission_flags ?? 0;
this.defaultGuildsRestricted = row.default_guilds_restricted ?? false;
this.botDefaultGuildsRestricted = row.bot_default_guilds_restricted ?? false;
this.restrictedGuilds = row.restricted_guilds ?? new Set();
this.botRestrictedGuilds = row.bot_restricted_guilds ?? new Set();
this.guildPositions = row.guild_positions ?? [];
this.guildFolders = (row.guild_folders ?? []).map((folder) => new UserGuildFolder(folder));
this.afkTimeout = row.afk_timeout ?? 600;
this.timeFormat = row.time_format ?? 0;
this.trustedDomains = row.trusted_domains ?? new Set();
this.defaultHideMutedChannels = row.default_hide_muted_channels ?? false;
this.version = row.version;
}
getUncategorizedFolder(): UserGuildFolder | null {
return this.guildFolders.find((folder) => folder.folderId === UNCATEGORIZED_FOLDER_ID) ?? null;
}
getOrderedGuildIds(): Array<GuildID> {
return this.guildFolders.flatMap((folder) => folder.guildIds);
}
toRow(): UserSettingsRow {
return {
user_id: this.userId,
locale: this.locale,
theme: this.theme,
status: this.status,
status_resets_at: this.statusResetsAt,
status_resets_to: this.statusResetsTo,
custom_status: this.customStatus?.toCustomStatus() ?? null,
developer_mode: this.developerMode,
message_display_compact: this.compactMessageDisplay,
animate_emoji: this.animateEmoji,
animate_stickers: this.animateStickers,
gif_auto_play: this.gifAutoPlay,
render_embeds: this.renderEmbeds,
render_reactions: this.renderReactions,
render_spoilers: this.renderSpoilers,
inline_attachment_media: this.inlineAttachmentMedia,
inline_embed_media: this.inlineEmbedMedia,
explicit_content_filter: this.explicitContentFilter,
friend_source_flags: this.friendSourceFlags,
incoming_call_flags: this.incomingCallFlags,
group_dm_add_permission_flags: this.groupDmAddPermissionFlags,
default_guilds_restricted: this.defaultGuildsRestricted,
bot_default_guilds_restricted: this.botDefaultGuildsRestricted,
restricted_guilds: this.restrictedGuilds.size > 0 ? this.restrictedGuilds : null,
bot_restricted_guilds: this.botRestrictedGuilds.size > 0 ? this.botRestrictedGuilds : null,
guild_positions: this.guildPositions.length > 0 ? this.guildPositions : null,
guild_folders: this.guildFolders.length > 0 ? this.guildFolders.map((folder) => folder.toGuildFolder()) : null,
afk_timeout: this.afkTimeout,
time_format: this.timeFormat,
trusted_domains: this.trustedDomains.size > 0 ? this.trustedDomains : null,
default_hide_muted_channels: this.defaultHideMutedChannels,
version: this.version,
};
}
static getDefaultUserSettings({
userId,
locale,
isAdult,
}: {
userId: UserID;
locale: LocaleCode;
isAdult: boolean;
}): UserSettingsRow {
let explicitContentFilter: number = UserExplicitContentFilterTypes.NON_FRIENDS;
if (isAdult) {
explicitContentFilter = UserExplicitContentFilterTypes.FRIENDS_AND_NON_FRIENDS;
}
let friendSourceFlags: number = FriendSourceFlags.MUTUAL_FRIENDS | FriendSourceFlags.MUTUAL_GUILDS;
if (isAdult) {
friendSourceFlags |= FriendSourceFlags.NO_RELATION;
}
return {
user_id: userId,
locale,
theme: ThemeTypes.SYSTEM,
status: 'online',
status_resets_at: null,
status_resets_to: null,
custom_status: null,
developer_mode: Config.nodeEnv === 'development',
message_display_compact: false,
animate_emoji: true,
animate_stickers: StickerAnimationOptions.ALWAYS_ANIMATE,
gif_auto_play: true,
render_embeds: true,
render_reactions: true,
render_spoilers: RenderSpoilers.ON_CLICK,
inline_attachment_media: true,
inline_embed_media: true,
explicit_content_filter: explicitContentFilter,
friend_source_flags: friendSourceFlags,
incoming_call_flags: IncomingCallFlags.FRIENDS_ONLY,
group_dm_add_permission_flags: GroupDmAddPermissionFlags.FRIENDS_ONLY,
default_guilds_restricted: false,
bot_default_guilds_restricted: false,
restricted_guilds: new Set(),
bot_restricted_guilds: new Set(),
guild_positions: [],
guild_folders: [
{
folder_id: UNCATEGORIZED_FOLDER_ID,
name: null,
color: null,
flags: 0,
icon: DEFAULT_GUILD_FOLDER_ICON,
guild_ids: [],
},
],
afk_timeout: 600,
time_format: 0,
trusted_domains: new Set(),
default_hide_muted_channels: false,
version: 1,
};
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {VisionarySlotRow} from '@fluxer/api/src/database/types/PaymentTypes';
export class VisionarySlot {
readonly slotIndex: number;
readonly userId: UserID | null;
constructor(row: VisionarySlotRow) {
this.slotIndex = row.slot_index;
this.userId = row.user_id;
}
toRow(): VisionarySlotRow {
return {
slot_index: this.slotIndex,
user_id: this.userId,
};
}
isReserved(): boolean {
return this.userId !== null;
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 {UserID} from '@fluxer/api/src/BrandedTypes';
import type {WebAuthnCredentialRow} from '@fluxer/api/src/database/types/AuthTypes';
export class WebAuthnCredential {
readonly credentialId: string;
readonly publicKey: Buffer;
readonly counter: bigint;
readonly transports: Set<string> | null;
readonly name: string;
readonly createdAt: Date;
readonly lastUsedAt: Date | null;
readonly version: number;
constructor(row: WebAuthnCredentialRow) {
this.credentialId = row.credential_id;
this.publicKey = row.public_key;
this.counter = row.counter;
this.transports = row.transports ?? null;
this.name = row.name;
this.createdAt = row.created_at;
this.lastUsedAt = row.last_used_at ?? null;
this.version = row.version;
}
toRow(userId: UserID): WebAuthnCredentialRow {
return {
user_id: userId,
credential_id: this.credentialId,
public_key: this.publicKey,
counter: this.counter,
transports: this.transports,
name: this.name,
created_at: this.createdAt,
last_used_at: this.lastUsedAt,
version: this.version,
};
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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 {ChannelID, GuildID, UserID, WebhookID, WebhookToken} from '@fluxer/api/src/BrandedTypes';
import type {WebhookRow} from '@fluxer/api/src/database/types/ChannelTypes';
export class Webhook {
readonly id: WebhookID;
readonly token: WebhookToken;
readonly type: number;
readonly guildId: GuildID | null;
readonly channelId: ChannelID | null;
readonly creatorId: UserID | null;
readonly name: string;
readonly avatarHash: string | null;
readonly version: number;
constructor(row: WebhookRow) {
this.id = row.webhook_id;
this.token = row.webhook_token;
this.type = row.type;
this.guildId = row.guild_id ?? null;
this.channelId = row.channel_id ?? null;
this.creatorId = row.creator_id ?? null;
this.name = row.name;
this.avatarHash = row.avatar_hash ?? null;
this.version = row.version;
}
toRow(): WebhookRow {
return {
webhook_id: this.id,
webhook_token: this.token,
type: this.type,
guild_id: this.guildId,
channel_id: this.channelId,
creator_id: this.creatorId,
name: this.name,
avatar_hash: this.avatarHash,
version: this.version,
};
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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 {Embed} from '@fluxer/api/src/models/Embed';
import {EmbedAuthor} from '@fluxer/api/src/models/EmbedAuthor';
import {EmbedFooter} from '@fluxer/api/src/models/EmbedFooter';
import {EmbedProvider} from '@fluxer/api/src/models/EmbedProvider';
import {describe, expect, it} from 'vitest';
describe('EmbedAuthor icon URL sanitisation', () => {
it('normalises empty icon_url values to null', () => {
const author = new EmbedAuthor({
name: 'Alice',
url: 'https://remote.example/@alice',
icon_url: '',
});
expect(author.iconUrl).toBeNull();
expect(author.toMessageEmbedAuthor().icon_url).toBeNull();
});
it('normalises invalid icon_url values to null', () => {
const author = new EmbedAuthor({
name: 'Alice',
url: 'https://remote.example/@alice',
icon_url: 'not-a-valid-url',
});
expect(author.iconUrl).toBeNull();
expect(author.toMessageEmbedAuthor().icon_url).toBeNull();
});
it('keeps valid icon_url values', () => {
const author = new EmbedAuthor({
name: 'Alice',
url: ' https://remote.example/@alice ',
icon_url: ' https://remote.example/avatar.png ',
});
expect(author.url).toBe('https://remote.example/@alice');
expect(author.iconUrl).toBe('https://remote.example/avatar.png');
expect(author.toMessageEmbedAuthor().url).toBe('https://remote.example/@alice');
expect(author.toMessageEmbedAuthor().icon_url).toBe('https://remote.example/avatar.png');
});
it('normalises invalid author url values to null', () => {
const author = new EmbedAuthor({
name: 'Alice',
url: 'not-a-valid-url',
icon_url: 'https://remote.example/avatar.png',
});
expect(author.url).toBeNull();
expect(author.toMessageEmbedAuthor().url).toBeNull();
});
});
describe('EmbedFooter icon URL sanitisation', () => {
it('normalises empty icon_url values to null', () => {
const footer = new EmbedFooter({
text: 'Example Server',
icon_url: '',
});
expect(footer.iconUrl).toBeNull();
expect(footer.toMessageEmbedFooter().icon_url).toBeNull();
});
it('normalises invalid icon_url values to null', () => {
const footer = new EmbedFooter({
text: 'Example Server',
icon_url: 'not-a-valid-url',
});
expect(footer.iconUrl).toBeNull();
expect(footer.toMessageEmbedFooter().icon_url).toBeNull();
});
it('keeps valid icon_url values', () => {
const footer = new EmbedFooter({
text: 'Example Server',
icon_url: ' https://remote.example/server-icon.png ',
});
expect(footer.iconUrl).toBe('https://remote.example/server-icon.png');
expect(footer.toMessageEmbedFooter().icon_url).toBe('https://remote.example/server-icon.png');
});
});
describe('Embed URL sanitisation', () => {
it('normalises invalid embed url values to null', () => {
const embed = new Embed({
type: 'rich',
title: null,
description: null,
url: 'not-a-valid-url',
timestamp: null,
color: null,
author: null,
provider: null,
thumbnail: null,
image: null,
video: null,
footer: null,
fields: null,
nsfw: null,
children: null,
});
expect(embed.url).toBeNull();
expect(embed.toMessageEmbed().url).toBeNull();
});
it('keeps valid embed urls', () => {
const embed = new Embed({
type: 'rich',
title: null,
description: null,
url: ' https://remote.example/post/1 ',
timestamp: null,
color: null,
author: null,
provider: null,
thumbnail: null,
image: null,
video: null,
footer: null,
fields: null,
nsfw: null,
children: null,
});
expect(embed.url).toBe('https://remote.example/post/1');
expect(embed.toMessageEmbed().url).toBe('https://remote.example/post/1');
});
});
describe('EmbedProvider URL sanitisation', () => {
it('normalises invalid provider url values to null', () => {
const provider = new EmbedProvider({
name: 'Example',
url: 'not-a-valid-url',
});
expect(provider.url).toBeNull();
expect(provider.toMessageEmbedProvider().url).toBeNull();
});
it('keeps valid provider urls', () => {
const provider = new EmbedProvider({
name: 'Example',
url: ' https://remote.example ',
});
expect(provider.url).toBe('https://remote.example/');
expect(provider.toMessageEmbedProvider().url).toBe('https://remote.example/');
});
});