/* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. Copyright (C) 2025 Spacebar and Spacebar Contributors This program 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. This program 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 this program. If not, see . */ import { Column, Entity, JoinColumn, OneToOne } from "typeorm"; import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; import { User } from "./User"; import { FrecencyUserSettings, PreloadedUserSettings, PreloadedUserSettings_AppearanceSettings, PreloadedUserSettings_CustomStatus, PreloadedUserSettings_LaunchPadMode, PreloadedUserSettings_PrivacySettings, PreloadedUserSettings_StatusSettings, PreloadedUserSettings_SwipeRightToLeftMode, PreloadedUserSettings_TextAndImagesSettings, PreloadedUserSettings_Theme, PreloadedUserSettings_TimestampHourCycle, PreloadedUserSettings_UIDensity, PreloadedUserSettings_VoiceAndVideoSettings, } from "discord-protos"; import { BoolValue, UInt32Value } from "discord-protos/dist/discord_protos/google/protobuf/wrappers"; @Entity({ name: "user_settings_protos", }) export class UserSettingsProtos extends BaseClassWithoutId { @OneToOne(() => User, { cascade: true, orphanedRowAction: "delete", eager: false, }) @JoinColumn({ name: "user_id" }) user: User; @PrimaryIdColumn({ type: "text" }) user_id: string; @Column({ nullable: true, type: String, name: "userSettings" }) _userSettings: string | undefined; @Column({ nullable: true, type: String, name: "frecencySettings" }) _frecencySettings: string | undefined; // @Column({nullable: true, type: "simple-json"}) // testSettings: {}; get userSettings(): PreloadedUserSettings | undefined { if (!this._userSettings) return undefined; return PreloadedUserSettings.fromJsonString(this._userSettings); } set userSettings(value: PreloadedUserSettings | undefined) { if (value) { this._userSettings = PreloadedUserSettings.toJsonString(value); } else { this._userSettings = undefined; } } get frecencySettings(): FrecencyUserSettings | undefined { if (!this._frecencySettings) return undefined; return FrecencyUserSettings.fromJsonString(this._frecencySettings); } set frecencySettings(value: FrecencyUserSettings | undefined) { if (value) { this._frecencySettings = FrecencyUserSettings.toJsonString(value); } else { this._frecencySettings = undefined; } } static async getOrCreate(user_id: string, save: boolean = false): Promise { if (!(await User.existsBy({ id: user_id }))) throw new Error(`User with ID ${user_id} does not exist.`); let userSettings = await UserSettingsProtos.findOne({ where: { user_id }, }); let modified = false; let isNewSettings = false; if (!userSettings) { userSettings = UserSettingsProtos.create({ user_id, }); modified = true; isNewSettings = true; } if (!userSettings.userSettings) { userSettings.userSettings = PreloadedUserSettings.create({ ads: { alwaysDeliver: false, }, appearance: { developerMode: user.settings?.developer_mode ?? true, theme: PreloadedUserSettings_Theme.DARK, mobileRedesignDisabled: true, launchPadMode: PreloadedUserSettings_LaunchPadMode.LAUNCH_PAD_DISABLED, swipeRightToLeftMode: PreloadedUserSettings_SwipeRightToLeftMode.SWIPE_RIGHT_TO_LEFT_REPLY, timestampHourCycle: PreloadedUserSettings_TimestampHourCycle.AUTO, uiDensity: PreloadedUserSettings_UIDensity.UI_DENSITY_COMPACT, }, versions: { dataVersion: 0, clientVersion: 0, serverVersion: 0, }, }); modified = true; } if (!userSettings.frecencySettings) { userSettings.frecencySettings = FrecencyUserSettings.create({ versions: { dataVersion: 0, clientVersion: 0, serverVersion: 0, }, }); modified = true; } if (isNewSettings) userSettings = await this.importLegacySettings(user_id, userSettings); if (modified && save) userSettings = await userSettings.save(); return userSettings; } static async importLegacySettings(user_id: string, settings: UserSettingsProtos): Promise { const user = await User.findOneOrFail({ where: { id: user_id }, select: { settings: true }, }); if (!user) throw new Error(`User with ID ${user_id} does not exist.`); const legacySettings = user.settings; const { frecencySettings, userSettings } = settings; if (userSettings === undefined) { throw new Error("UserSettingsProtos.userSettings is undefined, this should not happen."); } if (frecencySettings === undefined) { throw new Error("UserSettingsProtos.frecencySettings is undefined, this should not happen."); } if (legacySettings) { if (legacySettings.afk_timeout !== null && legacySettings.afk_timeout !== undefined) { userSettings.voiceAndVideo ??= PreloadedUserSettings_VoiceAndVideoSettings.create(); userSettings.voiceAndVideo.afkTimeout = UInt32Value.fromJson(legacySettings.afk_timeout); } if (legacySettings.allow_accessibility_detection !== null && legacySettings.allow_accessibility_detection !== undefined) { userSettings.privacy ??= PreloadedUserSettings_PrivacySettings.create(); userSettings.privacy.allowAccessibilityDetection = legacySettings.allow_accessibility_detection; } if (legacySettings.animate_emoji !== null && legacySettings.animate_emoji !== undefined) { userSettings.textAndImages ??= PreloadedUserSettings_TextAndImagesSettings.create(); userSettings.textAndImages.animateEmoji = BoolValue.fromJson(legacySettings.animate_emoji); } if (legacySettings.animate_stickers !== null && legacySettings.animate_stickers !== undefined) { userSettings.textAndImages ??= PreloadedUserSettings_TextAndImagesSettings.create(); userSettings.textAndImages.animateStickers = UInt32Value.fromJson(legacySettings.animate_stickers); } if (legacySettings.contact_sync_enabled !== null && legacySettings.contact_sync_enabled !== undefined) { userSettings.privacy ??= PreloadedUserSettings_PrivacySettings.create(); userSettings.privacy.contactSyncEnabled = BoolValue.fromJson(legacySettings.contact_sync_enabled); } if (legacySettings.convert_emoticons !== null && legacySettings.convert_emoticons !== undefined) { userSettings.textAndImages ??= PreloadedUserSettings_TextAndImagesSettings.create(); userSettings.textAndImages.convertEmoticons = BoolValue.fromJson(legacySettings.convert_emoticons); } if (legacySettings.custom_status !== null && legacySettings.custom_status !== undefined) { userSettings.status ??= PreloadedUserSettings_StatusSettings.create(); userSettings.status.customStatus = PreloadedUserSettings_CustomStatus.create({ emojiId: legacySettings.custom_status.emoji_id === undefined ? undefined : (BigInt(legacySettings.custom_status.emoji_id) as bigint), emojiName: legacySettings.custom_status.emoji_name, expiresAtMs: legacySettings.custom_status.expires_at === undefined ? undefined : (BigInt(legacySettings.custom_status.expires_at) as bigint), text: legacySettings.custom_status.text, createdAtMs: BigInt(Date.now()) as bigint, }); } } return settings; } }