mirror of
https://github.com/spacebarchat/server.git
synced 2026-05-16 20:15:23 +00:00
Some further work
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 165 KiB |
Binary file not shown.
@@ -18,7 +18,8 @@
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { CollectiblesShopResponse } from "@spacebar/schemas";
|
||||
import { Config } from "@spacebar/util";
|
||||
import { CollectiblesCategoryItem, CollectiblesShopResponse, ItemRowShopBlock } from "@spacebar/schemas";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -33,10 +34,34 @@ router.get(
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
res.send({
|
||||
shop_blocks: [],
|
||||
categories: [],
|
||||
} as CollectiblesShopResponse);
|
||||
const { endpointPublic: publicCdnEndpoint } = Config.get().cdn;
|
||||
res.send({ shop_blocks: [], categories: [] });
|
||||
// res.send({
|
||||
// shop_blocks: [
|
||||
// {
|
||||
// type: 0,
|
||||
// banner_asset: {
|
||||
// animated: null,
|
||||
// static: `${publicCdnEndpoint}/content/store/banners/main-store-banner.png`,
|
||||
// },
|
||||
// summary: "Welcome! Don't go alone, take this! :)",
|
||||
// category_sku_id: "spacebarshop",
|
||||
// name: "Spacebar",
|
||||
// category_store_listing_id: "a",
|
||||
// logo_url: "",
|
||||
// unpublished_at: null,
|
||||
// ranked_sku_ids: [],
|
||||
// },
|
||||
// ],
|
||||
// categories: [
|
||||
// {
|
||||
// sku_id: "spacebarshop",
|
||||
// name: "Spacebar shop category",
|
||||
// summary: "Spacebar shop category items",
|
||||
//
|
||||
// }
|
||||
// ],
|
||||
// } as CollectiblesShopResponse);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Router, Response, Request } from "express";
|
||||
import { Config, Snowflake } from "@spacebar/util";
|
||||
import { storage } from "../util/Storage";
|
||||
import FileType from "file-type";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import crypto from "crypto";
|
||||
import { multer } from "../util/multer";
|
||||
|
||||
const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"];
|
||||
const STATIC_MIME_TYPES = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/svg",
|
||||
];
|
||||
const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES];
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/:asset_id", async (req: Request, res: Response) => {
|
||||
let { asset_id } = req.params;
|
||||
const path = `avatar-decoration-presets/${asset_id}`;
|
||||
|
||||
const file = await storage.get(path);
|
||||
if (!file) throw new HTTPError("not found", 404);
|
||||
const type = await FileType.fromBuffer(file);
|
||||
|
||||
res.set("Content-Type", type?.mime);
|
||||
res.set("Cache-Control", "public, max-age=31536000");
|
||||
|
||||
return res.send(file);
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
@@ -25,12 +25,12 @@ export interface CollectiblesShopResponse {
|
||||
|
||||
export type AnyShopBlock = ItemRowShopBlock | BundleTileRowShopBlock | ItemCollectionShopBlock;
|
||||
|
||||
export interface BaseShopBlock {
|
||||
export class BaseShopBlock {
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface ItemRowShopBlock extends BaseShopBlock {
|
||||
type: 0;
|
||||
export class ItemRowShopBlock extends BaseShopBlock {
|
||||
declare type: 0;
|
||||
category_sku_id: string;
|
||||
name: string;
|
||||
category_store_listing_id: string;
|
||||
|
||||
@@ -20,7 +20,17 @@ import { HTTPError } from "lambert-server";
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
|
||||
import { DmChannelDTO } from "../dtos";
|
||||
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
|
||||
import { InvisibleCharacters, Snowflake, emitEvent, getPermission, trimSpecial, Permissions, BitField } from "../util";
|
||||
import {
|
||||
InvisibleCharacters,
|
||||
Snowflake,
|
||||
containsAll,
|
||||
emitEvent,
|
||||
getPermission,
|
||||
trimSpecial,
|
||||
DiscordApiErrors,
|
||||
Permissions,
|
||||
BitField
|
||||
} from "../util";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { Invite } from "./Invite";
|
||||
@@ -263,7 +273,7 @@ export class Channel extends BaseClass {
|
||||
if (otherRecipientsUsers.length !== recipients.length) {
|
||||
throw new HTTPError("Recipient/s not found");
|
||||
}
|
||||
**/
|
||||
**/
|
||||
|
||||
const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM;
|
||||
|
||||
@@ -377,7 +387,6 @@ export class Channel extends BaseClass {
|
||||
|
||||
static async deleteChannel(channel: Channel) {
|
||||
// TODO Delete attachments from the CDN for messages in the channel
|
||||
await Channel.delete({ id: channel.id });
|
||||
|
||||
if (channel.guild_id) {
|
||||
const guild = await Guild.findOneOrFail({
|
||||
@@ -385,9 +394,42 @@ export class Channel extends BaseClass {
|
||||
select: { channel_ordering: true },
|
||||
});
|
||||
|
||||
const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id);
|
||||
await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering });
|
||||
}
|
||||
if (guild.features.includes("COMMUNITY")) {
|
||||
if (
|
||||
[
|
||||
guild.afk_channel_id,
|
||||
guild.system_channel_id,
|
||||
guild.rules_channel_id,
|
||||
guild.public_updates_channel_id,
|
||||
].includes(channel.id)
|
||||
) {
|
||||
throw DiscordApiErrors.CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (guild.afk_channel_id === channel.id) {
|
||||
guild.afk_channel_id = null;
|
||||
}
|
||||
if (guild.system_channel_id === channel.id) {
|
||||
guild.system_channel_id = null;
|
||||
}
|
||||
if (guild.rules_channel_id === channel.id) {
|
||||
guild.rules_channel_id = null;
|
||||
}
|
||||
if (guild.public_updates_channel_id === channel.id) {
|
||||
guild.public_updates_channel_id = null;
|
||||
}
|
||||
}
|
||||
|
||||
await Channel.delete({ id: channel.id });
|
||||
|
||||
const updatedOrdering = guild.channel_ordering.filter(
|
||||
(id) => id != channel.id,
|
||||
);
|
||||
await Guild.update(
|
||||
{ id: channel.guild_id },
|
||||
{ channel_ordering: updatedOrdering },
|
||||
);
|
||||
}
|
||||
|
||||
static async calculatePosition(channel_id: string, guild_id: string, guild?: Guild) {
|
||||
|
||||
@@ -19,7 +19,22 @@
|
||||
import { Column, Entity, JoinColumn, OneToOne } from "typeorm";
|
||||
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
import { FrecencyUserSettings, PreloadedUserSettings } from "discord-protos";
|
||||
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",
|
||||
@@ -45,40 +60,13 @@ export class UserSettingsProtos extends BaseClassWithoutId {
|
||||
// @Column({nullable: true, type: "simple-json"})
|
||||
// testSettings: {};
|
||||
|
||||
bigintReplacer(_key: string, value: unknown): unknown {
|
||||
if (typeof value === "bigint") {
|
||||
return (value as bigint).toString();
|
||||
} else if (value instanceof Uint8Array) {
|
||||
return {
|
||||
__type: "Uint8Array",
|
||||
data: Array.from(value as Uint8Array)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join(""),
|
||||
};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
bigintReviver(_key: string, value: unknown): unknown {
|
||||
if (typeof value === "string" && /^\d+n$/.test(value)) {
|
||||
return BigInt((value as string).slice(0, -1));
|
||||
} else if (typeof value === "object" && value !== null && "__type" in value) {
|
||||
if (value.__type === "Uint8Array" && "data" in value) {
|
||||
return new Uint8Array((value.data as string).match(/.{1,2}/g)!.map((byte: string) => parseInt(byte, 16)));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get userSettings(): PreloadedUserSettings | undefined {
|
||||
if (!this._userSettings) return undefined;
|
||||
return PreloadedUserSettings.fromJson(JSON.parse(this._userSettings, this.bigintReviver));
|
||||
return PreloadedUserSettings.fromJsonString(this._userSettings);
|
||||
}
|
||||
|
||||
set userSettings(value: PreloadedUserSettings | undefined) {
|
||||
if (value) {
|
||||
// this._userSettings = JSON.stringify(value, this.bigintReplacer);
|
||||
this._userSettings = PreloadedUserSettings.toJsonString(value);
|
||||
} else {
|
||||
this._userSettings = undefined;
|
||||
@@ -87,33 +75,32 @@ export class UserSettingsProtos extends BaseClassWithoutId {
|
||||
|
||||
get frecencySettings(): FrecencyUserSettings | undefined {
|
||||
if (!this._frecencySettings) return undefined;
|
||||
return FrecencyUserSettings.fromJson(JSON.parse(this._frecencySettings, this.bigintReviver));
|
||||
return FrecencyUserSettings.fromJsonString(this._frecencySettings);
|
||||
}
|
||||
|
||||
set frecencySettings(value: FrecencyUserSettings | undefined) {
|
||||
if (value) {
|
||||
this._frecencySettings = JSON.stringify(value, this.bigintReplacer);
|
||||
this._frecencySettings = FrecencyUserSettings.toJsonString(value);
|
||||
} else {
|
||||
this._frecencySettings = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static async getOrDefault(user_id: string, save: boolean = false): Promise<UserSettingsProtos> {
|
||||
const user = await User.findOneOrFail({
|
||||
where: { id: user_id },
|
||||
select: { settings: true },
|
||||
});
|
||||
static async getOrCreate(user_id: string, save: boolean = false): Promise<UserSettingsProtos> {
|
||||
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) {
|
||||
@@ -150,8 +137,73 @@ export class UserSettingsProtos extends BaseClassWithoutId {
|
||||
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<UserSettingsProtos> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user