mirror of
https://github.com/spacebarchat/server.git
synced 2026-11-04 06:57:36 +00:00
Resolve some TODOs
This commit is contained in:
1 parent
340c2efac5
commit
0cd981e500
41 files changed
+628
-152
No files matched your search
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
@@ -18,8 +18,6 @@
|
||||
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
|
||||
// TODO: config settings
|
||||
|
||||
export function CORS(req: Request, res: Response, next: NextFunction) {
|
||||
res.set("Access-Control-Allow-Credentials", "true");
|
||||
res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Application, ApplicationCommand } from "@spacebar/database";
|
||||
import { FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { Config, FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { ApplicationCommandCreateSchema, ApplicationCommandSchema } from "@spacebar/schemas";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
@@ -68,12 +68,11 @@ router.patch(
|
||||
body.type = 1;
|
||||
}
|
||||
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > 32) {
|
||||
// TODO: configurable?
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > Config.get().limits.application.commandLimits.maxNameLength) {
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
code: "BASE_TYPE_BAD_LENGTH",
|
||||
message: `Must be between 1 and 32 in length.`,
|
||||
message: `Must be between 1 and ${Config.get().limits.application.commandLimits.maxNameLength} in length.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IsNull } from "typeorm";
|
||||
import { ApplicationCommandCreateSchema, ApplicationCommandSchema } from "@spacebar/schemas";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Application, ApplicationCommand } from "@spacebar/database";
|
||||
import { FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { Config, FieldErrors, Snowflake } from "@spacebar/util";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -56,12 +56,11 @@ router.post(
|
||||
body.type = 1;
|
||||
}
|
||||
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > 32) {
|
||||
// TODO: configurable?
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > Config.get().limits.application.commandLimits.maxNameLength) {
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
code: "BASE_TYPE_BAD_LENGTH",
|
||||
message: `Must be between 1 and 32 in length.`,
|
||||
message: `Must be between 1 and ${Config.get().limits.application.commandLimits.maxNameLength} in length.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -127,12 +126,11 @@ router.put(
|
||||
command.type = 1;
|
||||
}
|
||||
|
||||
if (command.name.trim().length < 1 || command.name.trim().length > 32) {
|
||||
// TODO: configurable?
|
||||
if (command.name.trim().length < 1 || command.name.trim().length > Config.get().limits.application.commandLimits.maxNameLength) {
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
code: "BASE_TYPE_BAD_LENGTH",
|
||||
message: `Must be between 1 and 32 in length.`,
|
||||
message: `Must be between 1 and ${Config.get().limits.application.commandLimits.maxNameLength} in length.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+3
-4
@@ -19,7 +19,7 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Application, ApplicationCommand, Guild, Member } from "@spacebar/database";
|
||||
import { FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { Config, FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { ApplicationCommandCreateSchema, ApplicationCommandSchema } from "@spacebar/schemas";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
@@ -87,12 +87,11 @@ router.patch(
|
||||
body.type = 1;
|
||||
}
|
||||
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > 32) {
|
||||
// TODO: configurable?
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > Config.get().limits.application.commandLimits.maxNameLength) {
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
code: "BASE_TYPE_BAD_LENGTH",
|
||||
message: `Must be between 1 and 32 in length.`,
|
||||
message: `Must be between 1 and ${Config.get().limits.application.commandLimits.maxNameLength} in length.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Application, ApplicationCommand, Guild, Member } from "@spacebar/database";
|
||||
import { FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { Config, FieldErrors, Snowflake } from "@spacebar/util";
|
||||
import { ApplicationCommandCreateSchema, ApplicationCommandSchema } from "@spacebar/schemas";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
@@ -79,12 +79,11 @@ router.post(
|
||||
body.type = 1;
|
||||
}
|
||||
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > 32) {
|
||||
// TODO: configurable?
|
||||
if (body.name.trim().length < 1 || body.name.trim().length > Config.get().limits.application.commandLimits.maxNameLength) {
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
code: "BASE_TYPE_BAD_LENGTH",
|
||||
message: `Must be between 1 and 32 in length.`,
|
||||
message: `Must be between 1 and ${Config.get().limits.application.commandLimits.maxNameLength} in length.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -165,12 +164,11 @@ router.put(
|
||||
command.type = 1;
|
||||
}
|
||||
|
||||
if (command.name.trim().length < 1 || command.name.trim().length > 32) {
|
||||
// TODO: configurable?
|
||||
if (command.name.trim().length < 1 || command.name.trim().length > Config.get().limits.application.commandLimits.maxNameLength) {
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
code: "BASE_TYPE_BAD_LENGTH",
|
||||
message: `Must be between 1 and 32 in length.`,
|
||||
message: `Must be between 1 and ${Config.get().limits.application.commandLimits.maxNameLength} in length.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ router.post(
|
||||
spacebarOnly: false, // Not part of public openapi
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
// TODO:
|
||||
res.sendStatus(204);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -21,9 +21,9 @@ import { HTTPError } from "lambert-server/HTTPError";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Channel, Guild, Invite, PublicInviteRelation, User } from "@spacebar/database";
|
||||
import { InviteCreateEvent, emitEvent } from "@spacebar/util";
|
||||
import { InviteCreateSchema, isTextChannel } from "@spacebar/schemas";
|
||||
import { InviteCreateSchema, isTextChannel, PublicUserProjection } from "@spacebar/schemas";
|
||||
import { Random } from "@spacebar/extensions";
|
||||
import { InviteListResponse } from "@spacebar/schemas/api/guilds/Invite";
|
||||
import { InviteListResponse, PublicInvite } from "@spacebar/schemas/api/guilds/Invite";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
@@ -35,7 +35,7 @@ router.post(
|
||||
right: "CREATE_INVITES",
|
||||
responses: {
|
||||
201: {
|
||||
body: "Invite",
|
||||
body: "PublicInvite",
|
||||
},
|
||||
404: {},
|
||||
400: {
|
||||
@@ -71,21 +71,20 @@ router.post(
|
||||
guild_id,
|
||||
channel_id: channel_id,
|
||||
inviter_id: user_id,
|
||||
inviter: await User.findOneOrFail({
|
||||
where: { id: req.user_id },
|
||||
select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), // TODO: clean up
|
||||
}),
|
||||
flags: body.flags ?? 0,
|
||||
}).save();
|
||||
|
||||
const data = invite.toJSON();
|
||||
data.inviter = await User.getPublicUser(req.user_id);
|
||||
data.guild = await Guild.findOne({ where: { id: guild_id } });
|
||||
data.channel = channel;
|
||||
|
||||
await emitEvent({
|
||||
event: "INVITE_CREATE",
|
||||
data,
|
||||
data: invite.toGatewayInviteCreateData(),
|
||||
guild_id,
|
||||
} satisfies InviteCreateEvent);
|
||||
|
||||
res.status(201).send(data);
|
||||
res.status(201).send(invite.toPublicJSON());
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import { handleMessage, postHandleMessage } from "@spacebar/api/util";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Attachment, Channel, Member, Message, ReadState, Relationship, User, ThreadMember, ThreadMemberFlags } from "@spacebar/database";
|
||||
import { Attachment, Channel, Member, Message, ReadState, Relationship, User, ThreadMember } from "@spacebar/database";
|
||||
import {
|
||||
Config,
|
||||
DiscordApiErrors,
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
Reaction,
|
||||
ReadStateType,
|
||||
RelationshipType,
|
||||
ThreadMemberFlags,
|
||||
} from "@spacebar/schemas";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
@@ -21,7 +21,7 @@ import { messageUpload } from "./messages";
|
||||
import { In } from "typeorm";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Channel, Member, Message } from "@spacebar/database";
|
||||
import { PostDataSchema, PublicMessage } from "@spacebar/schemas";
|
||||
import { PostDataResponse, PostDataSchema, PublicMessage } from "@spacebar/schemas";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -43,7 +43,9 @@ router.post(
|
||||
requestBody: "PostDataSchema",
|
||||
permission: "VIEW_CHANNEL",
|
||||
responses: {
|
||||
200: {},
|
||||
200: {
|
||||
body: "PostDataResponse",
|
||||
},
|
||||
403: {},
|
||||
},
|
||||
}),
|
||||
@@ -54,6 +56,7 @@ router.post(
|
||||
id: In(body),
|
||||
},
|
||||
});
|
||||
|
||||
const [messages, members] = await Promise.all([
|
||||
Message.find({
|
||||
where: {
|
||||
@@ -81,17 +84,18 @@ router.post(
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
await Message.fillReplies(messages);
|
||||
const objRet: { threads: Record<string, { first_message: null | PublicMessage; owner: null | Member }> } = { threads: {} };
|
||||
const threadPostData: PostDataResponse = { threads: {} };
|
||||
for (const thread of threads) {
|
||||
const owner = members.find(({ id }) => id === thread.owner_id)?.toJSON() || null;
|
||||
const first_message = messages.find(({ channel_id }) => channel_id === thread.id)?.toJSON() || null;
|
||||
objRet.threads[thread.id] = {
|
||||
const owner = members.find(({ id }) => id === thread.owner_id)?.toPublicMember() ?? null;
|
||||
const first_message = messages.find(({ channel_id }) => channel_id === thread.id)?.toJSON() ?? null;
|
||||
threadPostData.threads[thread.id] = {
|
||||
owner,
|
||||
first_message,
|
||||
};
|
||||
}
|
||||
return res.json(objRet);
|
||||
return res.json(threadPostData);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
import { MoreThan } from "typeorm";
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Channel, Member, ThreadMember, ThreadMemberFlags } from "@spacebar/database";
|
||||
import { Channel, Member, ThreadMember } from "@spacebar/database";
|
||||
import { emitEvent, Permissions, ThreadCreateEvent, ThreadDeleteEvent, ThreadMembersUpdateEvent } from "@spacebar/util";
|
||||
import { ChannelType, Snowflake } from "@spacebar/schemas";
|
||||
import { ChannelType, Snowflake, ThreadMemberFlags } from "@spacebar/schemas";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -29,7 +29,9 @@ router.get(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: {},
|
||||
200: {
|
||||
body: "PublicThreadMembersResponse",
|
||||
},
|
||||
403: {},
|
||||
},
|
||||
permission: "VIEW_CHANNEL",
|
||||
@@ -51,12 +53,14 @@ router.get(
|
||||
}
|
||||
|
||||
return res.send(
|
||||
await ThreadMember.find({
|
||||
where: { channel: { id: channel_id }, ...(after ? { user_id: MoreThan(after) } : {}) },
|
||||
take: limit ? parseInt(limit) : 50,
|
||||
order: { member_idx: "ASC" },
|
||||
relations: { ...(with_member ? { member: true } : {}) },
|
||||
}),
|
||||
(
|
||||
await ThreadMember.find({
|
||||
where: { channel: { id: channel_id }, ...(after ? { user_id: MoreThan(after) } : {}) },
|
||||
take: limit ? parseInt(limit) : 50,
|
||||
order: { member_idx: "ASC" },
|
||||
relations: { ...(with_member ? { member: true } : {}) },
|
||||
})
|
||||
).map((m) => m.toJSON()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -64,10 +64,11 @@ router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
guild_id: command.guild_id,
|
||||
name: command.name,
|
||||
name_localizations: command.name_localizations,
|
||||
// name_localized: // TODO: make this work
|
||||
name_localized: (command.name_localizations && req.user.settings?.locale ? command.name_localizations[req.user.settings.locale] : null) ?? command.name,
|
||||
description: command.description,
|
||||
description_localizations: command.description_localizations,
|
||||
// description_localized: // TODO: make this work
|
||||
description_localized:
|
||||
(command.description_localizations && req.user.settings?.locale ? command.description_localizations[req.user.settings.locale] : null) ?? command.description,
|
||||
options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined,
|
||||
default_member_permissions: command.default_member_permissions,
|
||||
dm_permission: command.dm_permission,
|
||||
|
||||
@@ -26,6 +26,7 @@ import { BanCreateSchema, BanRegistrySchema, GuildBanResponse, GuildBansResponse
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
/* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */
|
||||
// Emma [it/its] @ Rory& - 2026-11-01 - what secrets?
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
|
||||
@@ -109,7 +109,6 @@ router.patch(
|
||||
if (body.features) {
|
||||
const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x)));
|
||||
|
||||
// TODO move these
|
||||
const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
|
||||
|
||||
for (const feature of diff) {
|
||||
|
||||
@@ -38,7 +38,6 @@ router.get(
|
||||
async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params as { [key: string]: string };
|
||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||
//TODO we should use an enum for guild's features and not hardcoded strings
|
||||
return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS")));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -24,8 +24,6 @@ import { GuildCreateSchema } from "@spacebar/schemas";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
//TODO: create default channel
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
route({
|
||||
|
||||
@@ -37,8 +37,6 @@ router.post(
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as AckBulkSchema;
|
||||
|
||||
// TODO: what is read_state_type ?
|
||||
|
||||
await Promise.all([
|
||||
// for every new state
|
||||
...body.read_states.map(async (x) => {
|
||||
|
||||
@@ -30,7 +30,6 @@ router.post(
|
||||
authentication: "never",
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
// TODO:
|
||||
res.sendStatus(204);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -84,7 +84,7 @@ router.post(
|
||||
role: TeamMemberRole.ADMIN,
|
||||
}).save();
|
||||
|
||||
res.json(team.toJSON() satisfies Team);
|
||||
res.json(team.toJSON());
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -47,34 +47,63 @@ const example = {
|
||||
default: false,
|
||||
};
|
||||
|
||||
router.get("/", route({}), (req: Request, res: Response) => {
|
||||
// TODO: schema
|
||||
res.json([example]).status(200);
|
||||
});
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: { body: "PaymentSourceListResponse" },
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
res.json([example]).status(200);
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/", route({}), (req: Request, res: Response) => {
|
||||
// TODO: schema
|
||||
res.json([example]).status(200);
|
||||
});
|
||||
router.post(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: { body: "PaymentSourceListResponse" },
|
||||
},
|
||||
}),
|
||||
// TODO: request schema
|
||||
(req: Request, res: Response) => {
|
||||
res.json([example]).status(200);
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/:payment_source_id", route({}), (req: Request, res: Response) => {
|
||||
// TODO: schema
|
||||
res.json({
|
||||
...example,
|
||||
id: req.route.payment_source_id,
|
||||
}).status(200);
|
||||
});
|
||||
router.get(
|
||||
"/:payment_source_id",
|
||||
route({
|
||||
responses: {
|
||||
200: { body: "PaymentSource" },
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
res.json({
|
||||
...example,
|
||||
id: req.route.payment_source_id,
|
||||
}).status(200);
|
||||
},
|
||||
);
|
||||
|
||||
router.patch("/:payment_source_id", route({}), (req: Request, res: Response) => {
|
||||
// TODO: schema
|
||||
res.json({
|
||||
...example,
|
||||
id: req.route.payment_source_id,
|
||||
}).status(200);
|
||||
});
|
||||
router.patch(
|
||||
"/:payment_source_id",
|
||||
route({
|
||||
responses: {
|
||||
200: { body: "PaymentSourceListResponse" },
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
// TODO: request schema
|
||||
res.json({
|
||||
...example,
|
||||
id: req.route.payment_source_id,
|
||||
}).status(200);
|
||||
},
|
||||
);
|
||||
|
||||
router.delete("/:payment_source_id", route({}), (req: Request, res: Response) => {
|
||||
// TODO: schema
|
||||
res.status(204);
|
||||
});
|
||||
|
||||
|
||||
@@ -52,8 +52,6 @@ router.post(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: decrement guild member count
|
||||
|
||||
if (correctpass) {
|
||||
// Check if the user owns any guilds.
|
||||
const ownedGuilds = await Guild.findOne({ where: { owner_id: req.user_id } });
|
||||
|
||||
@@ -21,6 +21,8 @@ import { route } from "@spacebar/api/middlewares";
|
||||
import { Channel, Member } from "@spacebar/database";
|
||||
import { OrmUtils } from "@spacebar/util";
|
||||
import { UserGuildSettingsSchema } from "@spacebar/schemas";
|
||||
import { In } from "typeorm";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -60,10 +62,8 @@ router.patch(
|
||||
const body = req.body as UserGuildSettingsSchema;
|
||||
|
||||
if (body.channel_overrides) {
|
||||
// TODO: rewrite to a single query?
|
||||
for (const channel in body.channel_overrides) {
|
||||
await Channel.findOneOrFail({ where: { id: channel } });
|
||||
}
|
||||
const channels = await Channel.find({ where: { id: In(Object.keys(body.channel_overrides)), guild_id: req.params.guild_id as string } });
|
||||
if (channels.length != Object.keys(body.channel_overrides).length) throw new HTTPError("Could not find one or more channels.", 404);
|
||||
}
|
||||
|
||||
const user = await Member.findOneOrFail({
|
||||
|
||||
@@ -153,7 +153,6 @@ router.patch(
|
||||
}
|
||||
|
||||
if (body.discriminator) {
|
||||
// TODO: HACK - maybe make this optional?
|
||||
if (!/^\d{4}$/.test(body.discriminator)) {
|
||||
throw FieldErrors({
|
||||
discriminator: {
|
||||
|
||||
@@ -51,12 +51,11 @@ router.post(
|
||||
select: { id: true, data: true, email: true },
|
||||
});
|
||||
|
||||
// TODO: Are guests allowed to enable 2fa?
|
||||
if (user.data.hash) {
|
||||
if (!(await bcrypt.compare(body.password, user.data.hash))) {
|
||||
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
|
||||
}
|
||||
}
|
||||
} else throw new HTTPError("Guests cannot enable 2FA, please set a password first!", 403);
|
||||
|
||||
if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
|
||||
|
||||
|
||||
@@ -558,7 +558,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
return message;
|
||||
}
|
||||
|
||||
// TODO: cache link result in db
|
||||
export async function postHandleMessage(message: Message) {
|
||||
message.clean_data();
|
||||
|
||||
@@ -773,25 +772,21 @@ async function handleMessageMentionsAsync(message: Message) {
|
||||
mention_roles: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (referencedMessage && referencedMessage.author_id !== message.author_id) {
|
||||
message.mentions.push(
|
||||
// @ts-expect-error it does not like the .toPublicUser() lol
|
||||
(await User.findOne({ where: { id: referencedMessage.author_id } }))!.toPublicUser(),
|
||||
);
|
||||
const user = await User.findOne({ where: { id: referencedMessage.author_id } });
|
||||
if (user) message.mentions.push(user);
|
||||
}
|
||||
|
||||
if (message.embeds[0]?.type === EmbedType.poll_result) {
|
||||
message.mentions.push(
|
||||
// @ts-expect-error it does not like the .toPublicUser() lol
|
||||
(await User.findOne({ where: { id: message.author_id } }))!.toPublicUser(),
|
||||
);
|
||||
const user = await User.findOne({ where: { id: message.author_id } });
|
||||
if (user) message.mentions.push(user);
|
||||
}
|
||||
|
||||
if (message.message_reference.type === MessageReferenceType.FORWARD) {
|
||||
message.type = MessageType.DEFAULT;
|
||||
|
||||
if (message.referenced_message) {
|
||||
// TODO: mention_roles and mentions arrays - not needed it seems, but discord still returns that
|
||||
message.message_snapshots = [message.referenced_message.toSnapshot()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +76,7 @@ export class BaseClassWithoutId extends BaseEntity {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: fix eslint
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
toJSON(): any {
|
||||
toJSON(): unknown {
|
||||
this.clean_data();
|
||||
return Object.fromEntries(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
@@ -21,6 +21,8 @@ import { arrayRemove } from "@spacebar/extensions";
|
||||
import { Config, handleFile, Snowflake } from "@spacebar/util";
|
||||
import {
|
||||
DiscoverableGuild,
|
||||
DiscoveryCategory,
|
||||
EmojiResponse,
|
||||
GuildNsfwLevel,
|
||||
GuildPremiumTier,
|
||||
GuildProfileResponse,
|
||||
@@ -28,6 +30,7 @@ import {
|
||||
GuildVisibilityLevel,
|
||||
GuildWelcomeScreen,
|
||||
IntegrationGuild,
|
||||
StickerResponse,
|
||||
} from "@spacebar/schemas";
|
||||
import { Ban } from "./Ban";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
@@ -342,9 +345,9 @@ export class Guild extends BaseClass {
|
||||
},
|
||||
},
|
||||
}),*/
|
||||
emojis: this.emojis?.map((e) => e.toJSON()) ?? undefined,
|
||||
emojis: (this.emojis?.map((e) => e.toJSON()) as EmojiResponse[]) ?? undefined,
|
||||
emoji_count: this.emojis ? this.emojis.length : undefined,
|
||||
stickers: this.stickers?.map((s) => s.toJSON()) ?? undefined,
|
||||
stickers: (this.stickers?.map((s) => s.toJSON()) as StickerResponse[]) ?? undefined,
|
||||
sticker_count: this.stickers ? this.stickers.length : undefined,
|
||||
auto_removed: false,
|
||||
primary_category_id: this.primary_category_id!,
|
||||
@@ -353,13 +356,13 @@ export class Guild extends BaseClass {
|
||||
reasons_to_join: [],
|
||||
created_at: new Date(Snowflake.deconstruct(this.id).timestamp).toISOString(), // TODO: make column
|
||||
primary_category: this.primary_category_id
|
||||
? (
|
||||
? ((
|
||||
await Categories.findOneOrFail({
|
||||
where: {
|
||||
id: this.primary_category_id!,
|
||||
},
|
||||
})
|
||||
).toJSON()
|
||||
).toJSON() as DiscoveryCategory)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Channel } from "./Channel";
|
||||
import { Guild } from "./Guild";
|
||||
import { Member } from "./Member";
|
||||
import { User } from "./User";
|
||||
import { InviteType, PublicInvite } from "@spacebar/schemas/api/guilds/Invite";
|
||||
import { InviteCreateEventData, InviteType, PublicInvite } from "@spacebar/schemas/api/guilds/Invite";
|
||||
|
||||
export const PublicInviteRelation = ["inviter", "guild", "channel"];
|
||||
|
||||
@@ -105,6 +105,7 @@ export class Invite extends BaseClassWithoutId {
|
||||
if (this.max_uses !== 0 && this.uses >= this.max_uses) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
toPublicJSON(): PublicInvite {
|
||||
return {
|
||||
code: this.code,
|
||||
@@ -128,6 +129,26 @@ export class Invite extends BaseClassWithoutId {
|
||||
};
|
||||
}
|
||||
|
||||
toGatewayInviteCreateData(): InviteCreateEventData {
|
||||
return {
|
||||
code: this.code,
|
||||
type: InviteType.GUILD, // TODO: support other invite types
|
||||
channel_id: this.channel_id ?? this.channel.id,
|
||||
guild_id: this.guild_id ?? this.channel.guild_id,
|
||||
inviter: this.inviter.toPartialUser(),
|
||||
expires_at: this.expires_at?.toString() ?? null,
|
||||
target_type: undefined, // TODO
|
||||
target_user: undefined, // TODO
|
||||
created_at: this.created_at.toISOString(),
|
||||
max_age: this.max_age,
|
||||
max_uses: this.max_uses,
|
||||
temporary: this.temporary,
|
||||
uses: this.uses,
|
||||
role_ids: [], // TODO
|
||||
target_application: undefined, // TODO
|
||||
} satisfies InviteCreateEventData;
|
||||
}
|
||||
|
||||
static async joinGuild(user_id: string, code: string) {
|
||||
const invite = await Invite.findOneOrFail({ where: { code } });
|
||||
if (invite.isExpired()) {
|
||||
|
||||
@@ -390,7 +390,7 @@ export class Message extends BaseClass {
|
||||
embeds: this.embeds,
|
||||
flags: this.flags,
|
||||
mention_roles: this.mention_roles?.map((x) => x.id),
|
||||
mentions: this.mentions.map((x) => x.toPublicUser() as unknown as PartialUser), // TODO: write a proper method for this
|
||||
mentions: this.mentions.map((x) => x.toPartialUser()),
|
||||
timestamp: this.timestamp,
|
||||
type: this.type,
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
|
||||
import { Tag as Tag_ } from "@spacebar/schemas";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
|
||||
@@ -45,13 +46,13 @@ export class Tag extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
emoji_name?: string;
|
||||
|
||||
toJSON() {
|
||||
toJSON(): Tag_ {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
moderated: this.moderated,
|
||||
emoji_id: this.emoji_id,
|
||||
emoji_name: this.emoji_name,
|
||||
emoji_id: this.emoji_id ?? null,
|
||||
emoji_name: this.emoji_name ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -18,27 +18,13 @@
|
||||
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, RelationId } from "typeorm";
|
||||
import { ThreadMembersUpdateEvent } from "../../util/interfaces";
|
||||
import { HTTPError } from "lambert-server/HTTPError";
|
||||
import { emitEvent } from "@spacebar/util/util";
|
||||
import { MuteConfig, PublicThreadMember, ThreadMemberFlags } from "@spacebar/schemas";
|
||||
import { BaseClassWithoutId } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { HTTPError } from "lambert-server/HTTPError";
|
||||
import { Member } from "./Member";
|
||||
|
||||
// TODO: move
|
||||
interface ThreadMemberMuteConfig {
|
||||
end_time?: Date;
|
||||
selected_time_window?: number;
|
||||
}
|
||||
|
||||
// TODO: move
|
||||
export enum ThreadMemberFlags {
|
||||
NONE = 0,
|
||||
HAS_INTERACTED = 1 << 0,
|
||||
ALL_MESSAGES = 1 << 1,
|
||||
ONLY_MENTIONS = 1 << 2,
|
||||
NO_MESSAGES = 1 << 3,
|
||||
}
|
||||
|
||||
@Entity("thread_members")
|
||||
@Index(["id", "member_idx"], { unique: true })
|
||||
export class ThreadMember extends BaseClassWithoutId {
|
||||
@@ -72,7 +58,7 @@ export class ThreadMember extends BaseClassWithoutId {
|
||||
muted: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "jsonb" })
|
||||
mute_config?: ThreadMemberMuteConfig;
|
||||
mute_config?: MuteConfig;
|
||||
|
||||
@Column()
|
||||
flags: ThreadMemberFlags;
|
||||
@@ -93,20 +79,18 @@ export class ThreadMember extends BaseClassWithoutId {
|
||||
}))
|
||||
)
|
||||
throw new HTTPError("You are not member of this thread", 403);
|
||||
// // use promise all to execute all promises at the same time -> save time
|
||||
// TODO: check for bugs
|
||||
|
||||
if (channel.member_count) channel.member_count--;
|
||||
return Promise.all([
|
||||
ThreadMember.delete({
|
||||
id: thread_id,
|
||||
member_idx: member_id,
|
||||
}),
|
||||
// //Guild.decrement({ id: guild_id }, "member_count", -1),
|
||||
|
||||
emitEvent({
|
||||
event: "THREAD_MEMBERS_UPDATE",
|
||||
data: {
|
||||
guild_id: channel.guild_id!, // TODO: is this the right fix?
|
||||
guild_id: channel.guild_id!,
|
||||
id: channel.id,
|
||||
member_count: channel.member_count ?? 0,
|
||||
removed_member_ids: [member_id],
|
||||
@@ -116,6 +100,18 @@ export class ThreadMember extends BaseClassWithoutId {
|
||||
]);
|
||||
}
|
||||
|
||||
toJSON(): PublicThreadMember {
|
||||
return {
|
||||
id: this.id,
|
||||
flags: this.flags,
|
||||
join_timestamp: this.join_timestamp.toISOString(),
|
||||
member: this.member.toPublicMember(),
|
||||
mute_config: this.mute_config,
|
||||
muted: this.muted,
|
||||
user_id: this.member.id,
|
||||
} satisfies PublicThreadMember;
|
||||
}
|
||||
|
||||
// static async addRole(user_id: string, guild_id: string, role_id: string) {
|
||||
// const [member, role] = await Promise.all([
|
||||
// // @ts-ignore
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
*/
|
||||
|
||||
// TODO: remove entity imports
|
||||
import { Channel, Recipient, Tag, ThreadMember } from "@spacebar/database";
|
||||
import { Channel, Recipient } from "@spacebar/database";
|
||||
import { HTTPError } from "lambert-server/HTTPError";
|
||||
import { Snowflake } from "../../Identifiers";
|
||||
import { PartialUser, PublicMember } from "../users";
|
||||
import { MuteConfig, PartialUser, PublicMember } from "../users";
|
||||
|
||||
export enum ChannelType {
|
||||
GUILD_TEXT = 0, // a text channel within a guild
|
||||
@@ -127,7 +127,7 @@ export interface PublicChannel {
|
||||
member_count?: number;
|
||||
member_ids_preview?: Snowflake[];
|
||||
thread_metadata?: ThreadMetadata;
|
||||
member?: ThreadMember;
|
||||
member?: PublicThreadMember;
|
||||
default_auto_archive_duration?: number | null;
|
||||
default_thread_rate_limit_per_user?: number;
|
||||
permissions?: string;
|
||||
@@ -150,3 +150,30 @@ export interface PublicChannel {
|
||||
is_linkable?: boolean;
|
||||
is_viewable_and_writeable_by_all_members?: boolean;
|
||||
}
|
||||
|
||||
export type PublicThreadMembersResponse = PublicThreadMember[];
|
||||
export interface PublicThreadMember {
|
||||
id?: Snowflake;
|
||||
user_id?: Snowflake;
|
||||
join_timestamp: string;
|
||||
flags: ThreadMemberFlags;
|
||||
muted?: boolean;
|
||||
mute_config?: MuteConfig | null;
|
||||
member?: PublicMember;
|
||||
}
|
||||
|
||||
export enum ThreadMemberFlags {
|
||||
NONE = 0,
|
||||
HAS_INTERACTED = 1 << 0,
|
||||
ALL_MESSAGES = 1 << 1,
|
||||
ONLY_MENTIONS = 1 << 2,
|
||||
NO_MESSAGES = 1 << 3,
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: Snowflake;
|
||||
name: string;
|
||||
moderated: boolean;
|
||||
emoji_id: Snowflake | null;
|
||||
emoji_name: string | null;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
Copyright (C) 2026 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
|
||||
@@ -16,6 +16,8 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplicationSku, PartialUser, Snowflake, Team } from "@spacebar/schemas";
|
||||
|
||||
export interface ApplicationCommand {
|
||||
id: string;
|
||||
application_id: string;
|
||||
@@ -60,3 +62,285 @@ export interface ApplicationCommandInteractionDataOption {
|
||||
value?: unknown;
|
||||
options?: ApplicationCommandInteractionDataOption[];
|
||||
}
|
||||
|
||||
export interface Application {
|
||||
id: Snowflake;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string | null;
|
||||
cover_image?: string;
|
||||
splash?: string;
|
||||
type: ApplicationType | null;
|
||||
flags: number; // ApplicationFlags
|
||||
flags_new: string; // ApplicationFlags
|
||||
primary_sku_id?: Snowflake;
|
||||
verify_key: string;
|
||||
guild_id?: Snowflake;
|
||||
eula_id?: Snowflake;
|
||||
slug?: string;
|
||||
aliases?: string[];
|
||||
executables?: ApplicationExecutable[];
|
||||
third_party_skus?: ApplicationSku[];
|
||||
hook: boolean;
|
||||
overlay?: boolean;
|
||||
overlay_methods?: number; // OverlayMethodFlags
|
||||
overlay_warn?: boolean;
|
||||
overlay_compatibility_hook?: boolean;
|
||||
bot?: PartialUser;
|
||||
owner: PartialUser;
|
||||
team?: Team | null;
|
||||
developers?: Company[];
|
||||
publishers?: Company[];
|
||||
rpc_origins?: string[];
|
||||
redirect_uris: string[];
|
||||
deeplink_uri?: string;
|
||||
integration_public: boolean;
|
||||
integration_require_code_grant: boolean;
|
||||
bot_public?: boolean;
|
||||
bot_require_code_grant?: boolean;
|
||||
bot_disabled?: boolean;
|
||||
bot_quarantined?: boolean;
|
||||
bot_approximate_guild_count?: number;
|
||||
approximate_guild_count?: number;
|
||||
approximate_user_install_count: number;
|
||||
approximate_user_authorization_count: number;
|
||||
internal_guild_restriction: InternalGuildRestriction;
|
||||
terms_of_service_url?: string;
|
||||
privacy_policy_url?: string;
|
||||
role_connections_verification_url: string | null;
|
||||
interactions_endpoint_url: string;
|
||||
interactions_version: number;
|
||||
interactions_event_types: string[];
|
||||
event_webhooks_status?: EventWebhooksStatus;
|
||||
event_webhooks_url?: string;
|
||||
event_webhooks_types?: string[];
|
||||
explicit_content_filter: number;
|
||||
tags?: string[];
|
||||
install_params?: ApplicationInstallParams;
|
||||
custom_install_url?: string;
|
||||
integration_types_config?: Record<number, ApplicationIntegrationTypeConfiguration | null>;
|
||||
connection_entrypoint_url?: string;
|
||||
is_verified: boolean;
|
||||
verification_state: ApplicationVerificationState;
|
||||
store_application_state: StoreApplicationState;
|
||||
rpc_application_state: RpcApplicationState;
|
||||
creator_monetization_state: number; // unknown enum
|
||||
is_discoverable: boolean;
|
||||
discoverability_state: ApplicationDiscoverabilityState;
|
||||
discovery_eligibility_flags: number; // ApplicationDiscoveryEligibilityFlags
|
||||
is_monetized: boolean;
|
||||
storefront_available: boolean;
|
||||
monetization_state: ApplicationMonetizationState;
|
||||
monetization_eligibility_flags?: number; // ApplicationMonetizationEligibilityFlags
|
||||
max_participants?: number;
|
||||
embedded_activity_config?: EmbeddedActivityConfig;
|
||||
approved_consoles: ApprovableConsoleType[];
|
||||
pricing_localization_strategy: string;
|
||||
}
|
||||
|
||||
export enum ApplicationType {
|
||||
DEPRECATED_GAME = 1,
|
||||
TICKETED_EVENTS = 3,
|
||||
CREATOR_MONETIZATION = 4,
|
||||
GAME = 5,
|
||||
}
|
||||
const OverlayMethodFlags = {
|
||||
OUT_OF_PROCESS: 1n << 0n,
|
||||
} as const;
|
||||
|
||||
export enum InternalGuildRestriction {
|
||||
JOIN_ALL = 1,
|
||||
JOIN_EXTERNAL_ONLY = 2,
|
||||
JOIN_INTERNAL_ONLY = 3,
|
||||
}
|
||||
export enum EventWebhooksStatus {
|
||||
DISABLED = 1,
|
||||
ENABLED = 2,
|
||||
}
|
||||
|
||||
export enum ApplicationVerificationState {
|
||||
INELIGIBLE = 1,
|
||||
UNSUBMITTED = 2,
|
||||
SUBMITTED = 3,
|
||||
APPROVED_MANUALLY = 4,
|
||||
BLOCKED = 5,
|
||||
APPROVED_AUTOMATICALLY = 6,
|
||||
}
|
||||
|
||||
export enum StoreApplicationState {
|
||||
NONE = 1,
|
||||
PAID = 2,
|
||||
SUBMITTED = 3,
|
||||
APPROVED = 4,
|
||||
REJECTED = 5,
|
||||
}
|
||||
|
||||
export enum RpcApplicationState {
|
||||
DISABLED = 0,
|
||||
UNSUBMITTED = 1,
|
||||
SUBMITTED = 2,
|
||||
APPROVED = 3,
|
||||
REJECTED = 4,
|
||||
}
|
||||
|
||||
export enum ApplicationDiscoverabilityState {
|
||||
INELIGIBLE = 1,
|
||||
NOT_DISCOVERABLE = 2,
|
||||
DISCOVERABLE = 3,
|
||||
FEATUREABLE = 4,
|
||||
BLOCKED = 5,
|
||||
}
|
||||
|
||||
const ApplicationDiscoveryEligibilityFlags = {
|
||||
VERIFIED: 1n << 0n,
|
||||
TAG: 1n << 1n,
|
||||
DESCRIPTION: 1n << 2n,
|
||||
TERMS_OF_SERVICE: 1n << 3n,
|
||||
PRIVACY_POLICY: 1n << 4n,
|
||||
INSTALL_PARAMS: 1n << 5n,
|
||||
SAFE_NAME: 1n << 6n,
|
||||
SAFE_DESCRIPTION: 1n << 7n,
|
||||
APPROVED_COMMANDS: 1n << 8n,
|
||||
SUPPORT_GUILD: 1n << 9n,
|
||||
SAFE_COMMANDS: 1n << 10n,
|
||||
MFA: 1n << 11n,
|
||||
SAFE_DIRECTORY_OVERVIEW: 1n << 12n,
|
||||
SUPPORTED_LOCALES: 1n << 13n,
|
||||
SAFE_SHORT_DESCRIPTION: 1n << 14n,
|
||||
SAFE_ROLE_CONNECTIONS: 1n << 15n,
|
||||
} as const;
|
||||
|
||||
export enum ApplicationMonetizationState {
|
||||
NONE = 1,
|
||||
ENABLED = 2,
|
||||
BLOCKED = 3,
|
||||
}
|
||||
|
||||
const ApplicationMonetizationEligibilityFlags = {
|
||||
VERIFIED: 1n << 0n,
|
||||
HAS_TEAM: 1n << 1n,
|
||||
APPROVED_COMMANDS: 1n << 2n,
|
||||
TERMS_OF_SERVICE: 1n << 3n,
|
||||
PRIVACY_POLICY: 1n << 4n,
|
||||
SAFE_NAME: 1n << 5n,
|
||||
SAFE_DESCRIPTION: 1n << 6n,
|
||||
SAFE_ROLE_CONNECTIONS: 1n << 7n,
|
||||
USER_IS_TEAM_OWNER: 1n << 8n,
|
||||
NOT_QUARANTINED: 1n << 9n,
|
||||
USER_LOCALE_SUPPORTED: 1n << 10n,
|
||||
USER_AGE_SUPPORTED: 1n << 11n,
|
||||
USER_DATE_OF_BIRTH_DEFINED: 1n << 12n,
|
||||
USER_MFA_ENABLED: 1n << 13n,
|
||||
USER_EMAIL_VERIFIED: 1n << 14n,
|
||||
TEAM_MEMBERS_EMAIL_VERIFIED: 1n << 15n,
|
||||
TEAM_MEMBERS_MFA_ENABLED: 1n << 16n,
|
||||
NO_BLOCKING_ISSUES: 1n << 17n,
|
||||
VALID_PAYOUT_STATUS: 1n << 18n,
|
||||
} as const;
|
||||
|
||||
export enum ApprovableConsoleType {
|
||||
XBOX = 1,
|
||||
PLAYSTATION_5 = 2,
|
||||
PLAYSTATION_4 = 3,
|
||||
}
|
||||
|
||||
const ApplicationFlags = {
|
||||
EMBEDDED_RELEASED: 1n << 1n,
|
||||
MANAGED_EMOJI: 1n << 2n,
|
||||
EMBEDDED_IAP: 1n << 3n,
|
||||
GROUP_DM_CREATE: 1n << 4n,
|
||||
RPC_PRIVATE_BETA: 1n << 5n,
|
||||
AUTO_MODERATION_RULE_CREATE_BADGE: 1n << 6n,
|
||||
GAME_PROFILE_DISABLED: 1n << 7n,
|
||||
PUBLIC_OAUTH2_CLIENT: 1n << 8n,
|
||||
CONTEXTLESS_ACTIVITY: 1n << 9n,
|
||||
SOCIAL_LAYER_INTEGRATION_LIMITED: 1n << 10n,
|
||||
CLOUD_GAMING_DEMO: 1n << 11n,
|
||||
GATEWAY_PRESENCE: 1n << 12n,
|
||||
GATEWAY_PRESENCE_LIMITED: 1n << 13n,
|
||||
GATEWAY_GUILD_MEMBERS: 1n << 14n,
|
||||
GATEWAY_GUILD_MEMBERS_LIMITED: 1n << 15n,
|
||||
VERIFICATION_PENDING_GUILD_LIMIT: 1n << 16n,
|
||||
EMBEDDED: 1n << 17n,
|
||||
GATEWAY_MESSAGE_CONTENT: 1n << 18n,
|
||||
GATEWAY_MESSAGE_CONTENT_LIMITED: 1n << 19n,
|
||||
EMBEDDED_FIRST_PARTY: 1n << 20n,
|
||||
APPLICATION_COMMAND_MIGRATED: 1n << 21n,
|
||||
APPLICATION_COMMAND_BADGE: 1n << 23n,
|
||||
ACTIVE: 1n << 24n,
|
||||
ACTIVE_GRACE_PERIOD: 1n << 25n,
|
||||
IFRAME_MODAL: 1n << 26n,
|
||||
SOCIAL_LAYER_INTEGRATION: 1n << 27n,
|
||||
PROMOTED: 1n << 29n,
|
||||
PARTNER: 1n << 30n,
|
||||
PARENT: 1n << 33n,
|
||||
DISABLE_RELATIONSHIP_ACCESS: 1n << 34n,
|
||||
STOREFRONT_ELIGIBLE: 1n << 35n,
|
||||
} as const;
|
||||
|
||||
export interface ApplicationExecutable {
|
||||
os: string;
|
||||
name: string;
|
||||
is_launcher: boolean;
|
||||
}
|
||||
|
||||
export interface Company {
|
||||
id: Snowflake;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ApplicationInstallParams {
|
||||
scopes: string[];
|
||||
permissions: string;
|
||||
}
|
||||
|
||||
export interface ApplicationIntegrationTypeConfiguration {
|
||||
oauth2_install_params?: ApplicationInstallParams;
|
||||
}
|
||||
|
||||
export interface EmbeddedActivityConfig {
|
||||
application_id?: Snowflake;
|
||||
activity_preview_video_asset_id: Snowflake | null;
|
||||
supported_platforms: string[];
|
||||
default_orientation_lock_state: EmbeddedActivityOrientationLockStateType;
|
||||
tablet_default_orientation_lock_state: EmbeddedActivityOrientationLockStateType;
|
||||
requires_age_gate: boolean;
|
||||
legacy_responsive_aspect_ratio: boolean;
|
||||
premium_tier_requirement: number | null;
|
||||
free_period_starts_at: string | null;
|
||||
free_period_ends_at: string | null;
|
||||
client_platform_config: Record<string, EmbeddedActivityPlatformConfig>;
|
||||
shelf_rank: number;
|
||||
has_csp_exception: boolean;
|
||||
displays_advertisements: boolean;
|
||||
supported_locales: string[];
|
||||
blocked_locales: string[];
|
||||
}
|
||||
|
||||
export enum EmbeddedActivityOrientationLockStateType {
|
||||
UNLOCKED = 1,
|
||||
PORTRAIT = 2,
|
||||
LANDSCAPE = 3,
|
||||
}
|
||||
|
||||
export interface EmbeddedActivityPlatformConfig {
|
||||
label_type: EmbeddedActivityPlatformConfigLabelType;
|
||||
label_until?: string | null;
|
||||
release_phase: EmbeddedActivityReleasePhase;
|
||||
omit_badge_from_surfaces: string[];
|
||||
}
|
||||
|
||||
export enum EmbeddedActivityPlatformConfigLabelType {
|
||||
NONE = 0,
|
||||
NEW = 1,
|
||||
UPDATED = 2,
|
||||
}
|
||||
|
||||
export enum EmbeddedActivityReleasePhase {
|
||||
IN_DEVELOPMENT = "in_development",
|
||||
ACTIVITIES_TEAM = "activities_team",
|
||||
EMPLOYEE_RELEASE = "employee_release",
|
||||
SOFT_LAUNCH = "soft_launch",
|
||||
SOFT_LAUNCH_MULTI_GEO = "soft_launch_multi_geo",
|
||||
GLOBAL_LAUNCH = "global_launch",
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
import { Snowflake } from "../../Identifiers";
|
||||
import { PartialUser } from "../users";
|
||||
import { GuildNsfwLevel, GuildPremiumTier, GuildVerificationLevel } from "./GuildSchema";
|
||||
import { GuildProfileResponse, PublicChannel, RoleColors } from "@spacebar/schemas";
|
||||
import { Application, GuildProfileResponse, PublicChannel, RoleColors } from "@spacebar/schemas";
|
||||
|
||||
export type InviteListResponse = PublicInvite[];
|
||||
|
||||
@@ -100,3 +100,21 @@ export interface InviteRole {
|
||||
icon?: string | null;
|
||||
unicode_emoji?: string | null;
|
||||
}
|
||||
|
||||
export interface InviteCreateEventData {
|
||||
code: string;
|
||||
type: InviteType;
|
||||
channel_id: Snowflake;
|
||||
guild_id: Snowflake;
|
||||
inviter?: PartialUser;
|
||||
target_type?: number;
|
||||
target_user?: PartialUser;
|
||||
target_application?: Application; // PartialApplication
|
||||
role_ids?: Snowflake[];
|
||||
expires_at: string | null;
|
||||
created_at: string;
|
||||
uses: number;
|
||||
max_uses: number;
|
||||
max_age: number;
|
||||
temporary: boolean;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2026 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 { Snowflake } from "@spacebar/schemas";
|
||||
|
||||
export type PaymentSourceListResponse = PaymentSource[];
|
||||
export interface PaymentSource {
|
||||
id: Snowflake;
|
||||
brand?: string;
|
||||
country?: string;
|
||||
last_4?: string;
|
||||
billing_address: BillingAddress;
|
||||
type: PaymentSourceType;
|
||||
payment_gateway: PaymentGateway;
|
||||
payment_gateway_source_id?: string;
|
||||
default: boolean;
|
||||
invalid: boolean;
|
||||
flags: PaymentSourceFlags;
|
||||
deleted_at: string | null;
|
||||
expires_month?: number;
|
||||
expires_year?: number;
|
||||
email?: string;
|
||||
bank?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface BillingAddress {
|
||||
line_1: string;
|
||||
line_2?: string | null;
|
||||
name: string;
|
||||
postal_code?: string;
|
||||
city: string;
|
||||
state?: string;
|
||||
country: string;
|
||||
}
|
||||
export enum PaymentSourceType {
|
||||
CARD = 1,
|
||||
PAYPAL = 2,
|
||||
GIROPAY = 3,
|
||||
SOFORT = 4,
|
||||
PRZELEWY24 = 5,
|
||||
SEPA_DEBIT = 6,
|
||||
PAYSAFE_CARD = 7,
|
||||
GCASH = 8,
|
||||
GRABPAY_MY = 9,
|
||||
MOMO_WALLET = 10,
|
||||
VENMO = 11,
|
||||
GOPAY_WALLET = 12,
|
||||
KAKAOPAY = 13,
|
||||
BANCONTACT = 14,
|
||||
EPS = 15,
|
||||
IDEAL = 16,
|
||||
CASH_APP = 17,
|
||||
APPLE = 18,
|
||||
TDS_WALLET = 19,
|
||||
}
|
||||
|
||||
export enum PaymentGateway {
|
||||
STRIPE = 1,
|
||||
BRAINTREE = 2,
|
||||
APPLE = 3,
|
||||
GOOGLE = 4,
|
||||
ADYEN = 5,
|
||||
APPLE_PARTNER = 6,
|
||||
AMAZON = 7,
|
||||
VIRTUAL_CURRENCY = 8,
|
||||
APPLE_ADVANCED_COMMERCE = 9,
|
||||
TDS = 10,
|
||||
}
|
||||
|
||||
export enum PaymentSourceFlags {
|
||||
NEW = 1 << 0,
|
||||
SUCCESSFUL_PAYMENT = 1 << 1,
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
export * from "./ConnectedAccount";
|
||||
export * from "./InstanceUserDeleteSchema";
|
||||
export * from "./Member";
|
||||
export * from "./PaymentSource";
|
||||
export * from "./RelationshipSchemas";
|
||||
export * from "./SessionsSchemas";
|
||||
export * from "./Status";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||
Copyright (C) 2026 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
|
||||
@@ -16,6 +16,16 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PublicMember, PublicMessage, Snowflake } from "@spacebar/schemas";
|
||||
|
||||
export interface PostDataSchema {
|
||||
thread_ids: string[];
|
||||
}
|
||||
|
||||
export interface PostDataResponse {
|
||||
threads: Record<Snowflake, PostData>;
|
||||
}
|
||||
export interface PostData {
|
||||
owner: PublicMember | null;
|
||||
first_message: PublicMessage | null;
|
||||
}
|
||||
@@ -17,5 +17,10 @@
|
||||
*/
|
||||
|
||||
export class ApplicationLimits {
|
||||
maxEmojis: number = 100;
|
||||
maxEmojis: number = 1000;
|
||||
commandLimits: CommandLimits = new CommandLimits();
|
||||
}
|
||||
|
||||
export class CommandLimits {
|
||||
maxNameLength: number = 32;
|
||||
}
|
||||
@@ -30,12 +30,14 @@ import {
|
||||
PublicChannel,
|
||||
PublicMember,
|
||||
PublicMessage,
|
||||
PublicThreadMember,
|
||||
PublicUser,
|
||||
PublicVoiceState,
|
||||
RelationshipSchema,
|
||||
RelationshipType,
|
||||
UserPrivate,
|
||||
} from "@spacebar/schemas";
|
||||
import { InviteCreateEventData } from "@spacebar/schemas/api/guilds/Invite";
|
||||
|
||||
export interface Event {
|
||||
guild_id?: string;
|
||||
@@ -320,10 +322,7 @@ export interface GuildRoleDeleteEvent extends Event {
|
||||
|
||||
export interface InviteCreateEvent extends Event {
|
||||
event: "INVITE_CREATE";
|
||||
data: Omit<Invite, "guild" | "channel"> & {
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
};
|
||||
data: InviteCreateEventData;
|
||||
}
|
||||
|
||||
export interface InviteDeleteEvent extends Event {
|
||||
@@ -665,7 +664,7 @@ export interface ThreadMembersUpdateEvent extends Event {
|
||||
id: string;
|
||||
guild_id: string;
|
||||
member_count: number;
|
||||
added_members?: (ThreadMember & { user_id: string })[];
|
||||
added_members?: PublicThreadMember[];
|
||||
removed_member_ids?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export class UnixSocketWriter extends BaseEventWriter {
|
||||
delete this.clients[fullPath];
|
||||
}
|
||||
|
||||
console.error("[UnixSocketWriter] Unix socket client error on", fullPath, ":", err);
|
||||
console.error("[UnixSocketWriter] Unix socket client error on", fullPath, ":", summariseError(err));
|
||||
rej(err);
|
||||
});
|
||||
|
||||
@@ -127,7 +127,7 @@ export class UnixSocketWriter extends BaseEventWriter {
|
||||
|
||||
let tries = 0;
|
||||
let lastError: Error | undefined = undefined;
|
||||
while (tries++ < 10) {
|
||||
while (tries++ < 9) {
|
||||
if (tries > 1) {
|
||||
const delay = Math.pow(tries, 3.5);
|
||||
console.warn("[UnixSocketWriter] Attemption connects to", fullPath, "have been failing... Trying again in", delay, "millis...");
|
||||
@@ -143,7 +143,7 @@ export class UnixSocketWriter extends BaseEventWriter {
|
||||
}
|
||||
|
||||
if (tries >= 10 && lastError !== undefined) {
|
||||
console.error("[UnixSocketWriter] Exhausted all", tries, "attempts at connecting to", fullPath, "- Error:", lastError);
|
||||
console.error("[UnixSocketWriter] Exhausted all", tries, "attempts at connecting to", fullPath, "-", summariseError(lastError));
|
||||
if ("code" in lastError && lastError.code == "ECONNREFUSED") {
|
||||
console.error("[UnixSocketWriter] Got ECONNREFUSED for socket", fullPath, " - assuming it is orphaned...");
|
||||
fs.unlinkSync(fullPath);
|
||||
@@ -286,3 +286,13 @@ function getPidCmdline(pid: number): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function summariseError(err: Error) {
|
||||
// we dont care about these:
|
||||
if ("syscall" in err && err.syscall == "connect") {
|
||||
if ("code" in err && err.code == "ECONNREFUSED") return "Error: " + err.message;
|
||||
if ("code" in err && err.code == "ENOENT") return "Error: " + err.message;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
Reference in new issue
Block a user