Bunch of fixes and improvements, everything appears to work now

This commit is contained in:
TheArcaneBrony
2022-08-08 04:27:28 +02:00
parent c70d15b564
commit d52d9c62fc
40 changed files with 490 additions and 81 deletions

View File

@@ -5,6 +5,7 @@ import fetch, { Response as FetchResponse, Headers } from "node-fetch";
import ProxyAgent from 'proxy-agent';
import { Config } from "@fosscord/util";
import { AssetCacheItem } from "../util/entities/AssetCacheItem"
import { green } from "picocolors";
export default function TestClient(app: Application) {
const agent = new ProxyAgent();
@@ -18,11 +19,16 @@ export default function TestClient(app: Application) {
//load asset cache
let newAssetCache: Map<string, AssetCacheItem> = new Map<string, AssetCacheItem>();
if(!fs.existsSync(path.join(__dirname, "..", "..", "assets", "cache"))) {
fs.mkdirSync(path.join(__dirname, "..", "..", "assets", "cache"));
let assetCacheDir = path.join(__dirname, "..", "..", "assets", "cache");
if(process.env.ASSET_CACHE_DIR)
assetCacheDir = process.env.ASSET_CACHE_DIR
console.log(`[TestClient] ${green(`Using asset cache path: ${assetCacheDir}`)}`)
if(!fs.existsSync(assetCacheDir)) {
fs.mkdirSync(assetCacheDir);
}
if(fs.existsSync(path.join(__dirname, "..", "..", "assets", "cache", "index.json"))) {
let rawdata = fs.readFileSync(path.join(__dirname, "..", "..", "assets", "cache", "index.json"));
if(fs.existsSync(path.join(assetCacheDir, "index.json"))) {
let rawdata = fs.readFileSync(path.join(assetCacheDir, "index.json"));
newAssetCache = new Map<string, AssetCacheItem>(Object.entries(JSON.parse(rawdata.toString())));
}
@@ -39,6 +45,7 @@ export default function TestClient(app: Application) {
});
}
else {
console.log(`[TestClient] Downloading file not yet cached! Asset file: ${req.params.file}`);
response = await fetch(`https://discord.com/assets/${req.params.file}`, {
agent,
// @ts-ignore
@@ -49,11 +56,11 @@ export default function TestClient(app: Application) {
//set cache info
assetCacheItem.Headers = Object.fromEntries(stripHeaders(response.headers));
assetCacheItem.FilePath = path.join(__dirname, "..", "..", "assets", "cache", req.params.file);
assetCacheItem.FilePath = path.join(assetCacheDir, req.params.file);
assetCacheItem.Key = req.params.file;
//add to cache and save
newAssetCache.set(req.params.file, assetCacheItem);
fs.writeFileSync(path.join(__dirname, "..", "..", "assets", "cache", "index.json"), JSON.stringify(Object.fromEntries(newAssetCache), null, 4));
fs.writeFileSync(path.join(assetCacheDir, "index.json"), JSON.stringify(Object.fromEntries(newAssetCache), null, 4));
//download file
fs.writeFileSync(assetCacheItem.FilePath, await response.buffer());
}

View File

@@ -77,8 +77,8 @@ router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANN
const { channel_id } = req.params;
if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
channel.assign(payload);
let channel = await Channel.findOneOrFail({ where: { id: channel_id } });
channel = Object.assign(channel, payload);
await Promise.all([
channel.save(),

View File

@@ -33,7 +33,7 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
const expires_at = new Date(req.body.max_age * 1000 + Date.now());
const invite = await new Invite({
const invite = await Object.assign(new Invite(),{
code: random(),
temporary: req.body.temporary,
uses: 0,

View File

@@ -20,7 +20,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques
permission.hasThrow("VIEW_CHANNEL");
let read_state = await ReadState.findOne({ where: { user_id: req.user_id, channel_id } });
if (!read_state) read_state = new ReadState({ where: { user_id: req.user_id, channel_id } });
if (!read_state) read_state = Object.assign(new ReadState(), { user_id: req.user_id, channel_id });
read_state.last_message_id = message_id;
await read_state.save();

View File

@@ -28,7 +28,7 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
}
channel.recipients!.push(new Recipient({ channel_id, user_id: user_id }));
channel.recipients!.push(Object.assign(new Recipient(), { channel_id, user_id: user_id }));
await channel.save();
await emitEvent({

View File

@@ -90,7 +90,7 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER
const banned_user = await User.getPublicUser(banned_user_id);
const ban = new Ban({
const ban = Object.assign(new Ban(),{
user_id: banned_user_id,
guild_id: guild_id,
ip: getIpAdress(req),
@@ -122,7 +122,7 @@ router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res:
if (req.permission!.cache.guild?.owner_id === req.params.user_id)
throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
const ban = new Ban({
const ban = Object.assign(new Ban(), {
user_id: req.params.user_id,
guild_id: guild_id,
ip: getIpAdress(req),

View File

@@ -50,7 +50,7 @@ router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_A
const user = await User.findOneOrFail({ where: { id: req.user_id } });
body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
const emoji = await new Emoji({
const emoji = await Object.assign(new Emoji(), {
id: id,
guild_id: guild_id,
...body,
@@ -80,7 +80,7 @@ router.patch(
const { emoji_id, guild_id } = req.params;
const body = req.body as EmojiModifySchema;
const emoji = await new Emoji({ ...body, id: emoji_id, guild_id: guild_id }).save();
const emoji = await Object.assign(new Emoji(), { ...body, id: emoji_id, guild_id: guild_id }).save();
await emitEvent({
event: "GUILD_EMOJIS_UPDATE",

View File

@@ -59,7 +59,7 @@ router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res:
relations: ["emojis", "roles", "stickers"]
});
// TODO: check if body ids are valid
guild.assign(body);
guild = Object.assign(guild, body);
//TODO: check this, removed toJSON call
const data = JSON.parse(JSON.stringify(guild));

View File

@@ -31,7 +31,7 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re
permission.hasThrow("MANAGE_ROLES");
if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
member.roles = body.roles.map((x) => new Role({ id: x })); // foreign key constraint will fail if role doesn't exist
member.roles = body.roles.map((x) => Object.assign(new Role(), { id: x })); // foreign key constraint will fail if role doesn't exist
}
await member.save();

View File

@@ -43,7 +43,7 @@ router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }
if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
const role = new Role({
const role = Object.assign(new Role(), {
...body,
id: role_id,
guild_id,

View File

@@ -51,7 +51,7 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" })
if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
const role = new Role({
let role: Role = Object.assign(new Role(),{
// values before ...body are default and can be overriden
position: 0,
hoist: false,

View File

@@ -43,7 +43,7 @@ router.post(
const id = Snowflake.generate();
const [sticker] = await Promise.all([
new Sticker({
Object.assign(new Sticker(), {
...body,
guild_id,
id,
@@ -105,7 +105,7 @@ router.patch(
const { guild_id, sticker_id } = req.params;
const body = req.body as ModifyGuildStickerSchema;
const sticker = await new Sticker({ ...body, guild_id, id: sticker_id }).save();
const sticker = await Object.assign(new Sticker(), { ...body, guild_id, id: sticker_id }).save();
await sendStickerUpdateEvent(guild_id);
return res.json(sticker);

View File

@@ -47,7 +47,7 @@ router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD
const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => {});
if (exists) throw new HTTPError("Template already exists", 400);
const template = await new Template({
const template = await Object.assign(new Template(), {
...req.body,
code: generateCode(),
creator_id: req.user_id,
@@ -75,7 +75,7 @@ router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request,
const { code, guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
const template = await new Template({ code, serialized_source_guild: guild }).save();
const template = await Object.assign(new Template(), { code, serialized_source_guild: guild }).save();
res.json(template);
});
@@ -84,7 +84,7 @@ router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE
const { code, guild_id } = req.params;
const { name, description } = req.body;
const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save();
const template = await Object.assign(new Template(), { code, name: name, description: description, source_guild_id: guild_id }).save();
res.json(template);
});

View File

@@ -47,7 +47,7 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" })
const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } });
await new Invite({
await Object.assign(new Invite(), {
vanity_url: true,
code: code,
temporary: false,

View File

@@ -33,7 +33,7 @@ router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request
if (!body.suppress) body.request_to_speak_timestamp = new Date();
if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
const voice_state = await VoiceState.findOne({
let voice_state = await VoiceState.findOne({
where: {
guild_id,
channel_id: body.channel_id,
@@ -42,7 +42,7 @@ router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request
});
if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
voice_state.assign(body);
voice_state = Object.assign(voice_state, body);
const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } });
if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;

View File

@@ -57,13 +57,13 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req:
const guild_id = Snowflake.generate();
const [guild, role] = await Promise.all([
new Guild({
Object.assign(new Guild(), {
...body,
...template.serialized_source_guild,
id: guild_id,
owner_id: req.user_id
}).save(),
new Role({
(Object.assign(new Role(), {
id: guild_id,
guild_id: guild_id,
color: 0,
@@ -74,7 +74,7 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req:
permissions: BigInt("2251804225"),
position: 0,
tags: null
}).save()
}) as Role).save()
]);
await Member.addToGuild(req.user_id, guild_id);

View File

@@ -32,9 +32,7 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
const body = req.body as UserModifySchema;
if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
if (body.password) {
if (user.data?.hash) {
@@ -65,7 +63,7 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
}
}
user.assign(body);
user = Object.assign(user, body);
await user.save();
// @ts-ignore

View File

@@ -140,7 +140,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
relationship.type = RelationshipType.blocked;
await relationship.save();
} else {
relationship = await new Relationship({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save();
relationship = await Object.assign(new Relationship(), { to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save();
}
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
@@ -166,8 +166,8 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
const { maxFriends } = Config.get().limits.user;
if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
let incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
let outgoing_relationship = new Relationship({
let incoming_relationship = Object.assign(new Relationship(), { nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
let outgoing_relationship = Object.assign(new Relationship(), {
nickname: undefined,
type: RelationshipType.outgoing,
to: friend,
@@ -178,7 +178,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
// accept friend request
incoming_relationship = friendRequest;
incoming_relationship = friendRequest as any; //TODO: checkme, any cast
incoming_relationship.type = RelationshipType.friends;
}
@@ -186,7 +186,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
outgoing_relationship = relationship;
outgoing_relationship = relationship as any; //TODO: checkme, any cast
outgoing_relationship.type = RelationshipType.friends;
}

View File

@@ -47,7 +47,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] });
if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
const message = new Message({
const message = Object.assign(new Message(), {
...opts,
sticker_items: opts.sticker_ids?.map((x) => ({ id: x })),
guild_id: channel.guild_id,
@@ -132,9 +132,9 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
}
}
message.mention_channels = mention_channel_ids.map((x) => new Channel({ id: x }));
message.mention_roles = mention_role_ids.map((x) => new Role({ id: x }));
message.mentions = mention_user_ids.map((x) => new User({ id: x }));
message.mention_channels = mention_channel_ids.map((x) => Object.assign(new Channel(), { id: x }));
message.mention_roles = mention_role_ids.map((x) => Object.assign(new Role(), { id: x }));
message.mentions = mention_user_ids.map((x) => Object.assign(new User(), { id: x }));
message.mention_everyone = mention_everyone;
// TODO: check and put it all in the body

BIN
bundle/package-lock.json generated

Binary file not shown.

View File

@@ -9,8 +9,10 @@
"depcheck": "node scripts/depcheck.js",
"syncdeps": "node scripts/install.js",
"build": "node scripts/build.js",
"start": "node scripts/build.js && node dist/bundle/src/start.js",
"start:bundle": "node dist/bundle/src/start.js",
"start": "node scripts/build.js && node --enable-source-maps dist/bundle/src/start.js",
"start:bundle": "node --enable-source-maps dist/bundle/src/start.js",
"start:bundle:dbg": "node --enable-source-maps --inspect dist/bundle/src/start.js",
"start:bundle:vscode-dbg": "npm run build clean logerrors pretty-errors && node --enable-source-maps --inspect dist/bundle/src/start.js",
"test": "echo \"Error: no test specified\" && exit 1",
"migrate": "cd ../util/ && npm i && node --require ts-node/register node_modules/typeorm/cli.js -f ../util/ormconfig.json migration:run",
"tsnode": "npx ts-node --transpile-only -P tsnode.tsconfig.json src/start.ts"
@@ -27,28 +29,73 @@
},
"homepage": "https://fosscord.com",
"devDependencies": {
"@babel/core": "^7.18.9",
"@babel/preset-env": "^7.18.9",
"@babel/preset-typescript": "^7.15.0",
"@types/amqplib": "^0.8.1",
"@types/bcrypt": "^5.0.0",
"@types/body-parser": "^1.19.0",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.12",
"@types/i18next-node-fs-backend": "^2.1.0",
"@types/jsonwebtoken": "^8.5.8",
"@types/morgan": "^1.9.3",
"@types/multer": "^1.4.7",
"@types/node": "^18.0.6",
"@types/node-os-utils": "^1.3.0",
"@types/ws": "^8.5.3",
"jest": "^28.1.3",
"jest-expect-message": "^1.0.2",
"ts-node": "^10.2.1",
"ts-node-dev": "^2.0.0",
"ts-patch": "^2.0.1",
"typescript": "^4.2.3"
"typescript": "^4.2.3",
"typescript-json-schema": "^0.54.0"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.137.0",
"@babel/preset-typescript": "^7.15.0",
"@fosscord/api": "file:../api",
"@fosscord/cdn": "file:../cdn",
"@fosscord/gateway": "file:../gateway",
"@ovos-media/ts-transform-paths": "^1.7.18-1",
"@sentry/node": "^7.7.0",
"@sentry/tracing": "^7.7.0",
"@types/node-fetch": "^2.6.2",
"ajv": "8.6.2",
"ajv-formats": "^2.1.1",
"amqplib": "^0.10.1",
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"canvas": "^2.9.3",
"cheerio": "^1.0.0-rc.10",
"dotenv": "^16.0.1",
"exif-be-gone": "^1.3.0",
"express": "^4.17.1",
"file-type": "16.5",
"form-data": "^4.0.0",
"i18next": "^21.8.14",
"i18next-http-middleware": "^3.1.3",
"i18next-node-fs-backend": "^2.1.3",
"image-size": "^1.0.0",
"jest": "^28.1.3",
"jsonwebtoken": "^8.5.1",
"lambert-server": "^1.2.12",
"missing-native-js-functions": "^1.2.18",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3",
"node-2fa": "^2.0.3",
"node-fetch": "^2.6.7",
"node-os-utils": "^1.3.7",
"patch-package": "^6.4.7",
"pg": "^8.7.3",
"picocolors": "^1.0.0",
"proxy-agent": "^5.0.0",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.0.11",
"supertest": "^6.1.6",
"typeorm": "^0.3.7",
"typescript": "^4.1.2",
"ws": "^8.8.1"
}

View File

@@ -17,7 +17,7 @@
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */,
// "sourceMap": true /* Generates corresponding '.map' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist/" /* Redirect output structure to the directory. */,
"rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,

View File

@@ -27,6 +27,21 @@ export async function Connection(
socket.on("close", Close);
// @ts-ignore
socket.on("message", Message);
if(process.env.WS_LOGEVENTS)
[
"close",
"error",
"upgrade",
//"message",
"open",
"ping",
"pong",
"unexpected-response"
].forEach(x=>{
socket.on(x, y => console.log(x, y));
});
console.log(`[Gateway] Connections: ${this.clients.size}`);
const { searchParams } = new URL(`http://localhost${request.url}`);

View File

@@ -15,17 +15,33 @@ const PayloadSchema = {
$t: String,
};
export async function Message(this: WebSocket, buffer: WS.Data) {
export async function Message(this: WebSocket, buffer: WS.RawData) {
// TODO: compression
let data: Payload;
if (this.encoding === "etf" && buffer instanceof Buffer)
data = erlpack.unpack(buffer);
else if (this.encoding === "json" && typeof buffer === "string")
data = JSON.parse(buffer);
else return;
else if (this.encoding === "json")
data = JSON.parse(buffer as unknown as string); //TODO: is this even correct?? seems to work for web clients...
else if(/--debug|--inspect/.test(process.execArgv.join(' '))) {
debugger;
return;
}
else {
console.log("Invalid gateway connection! Use a debugger to inspect!");
return;
}
check.call(this, PayloadSchema, data);
if(process.env.WS_VERBOSE)
console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`);
if(data.op !== 1)
check.call(this, PayloadSchema, data);
else { //custom validation for numbers, because heartbeat
if(data.s || data.t || typeof data.d !== "number") {
console.log("Invalid heartbeat...");
this.close(CLOSECODES.Decode_error);
}
}
// @ts-ignore
const OPCodeHandler = OPCodeHandlers[data.op];

View File

@@ -2,7 +2,7 @@ import { Payload, WebSocket } from "@fosscord/gateway";
import { setHeartbeat } from "../util/Heartbeat";
import { Send } from "../util/Send";
export async function onHeartbeat(this: WebSocket, data: Payload) {
export async function onHeartbeat(this: WebSocket, _data: Payload) {
// TODO: validate payload
setHeartbeat(this);

View File

@@ -83,7 +83,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
// TODO: public user selection
}),
// save the session and delete it when the websocket is closed
new Session({
Object.assign(new Session(),{
user_id: this.user_id,
session_id: session_id,
// TODO: check if status is only one of: online, dnd, offline, idle

View File

@@ -1,4 +1,4 @@
import { getPermission, listenEvent, Member, Role } from "@fosscord/util";
import { getPermission, listenEvent, Member, Role, initDatabase } from "@fosscord/util";
import { LazyRequest } from "../schema/LazyRequest";
import { Send } from "../util/Send";
import { OPCODES } from "../util/Constants";
@@ -15,8 +15,9 @@ async function getMembers(guild_id: string, range: [number, number]) {
throw new Error("range is not a valid array");
}
// TODO: wait for typeorm to implement ordering for .find queries https://github.com/typeorm/typeorm/issues/2620
// TODO: rewrite this, released in 0.3.0
let members = await getRepository(Member)
let members = await (await initDatabase()).getRepository(Member)
.createQueryBuilder("member")
.where("member.guild_id = :guild_id", { guild_id })
.leftJoinAndSelect("member.roles", "role")

View File

@@ -47,9 +47,9 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
//The event send by Discord's client on channel leave has both guild_id and channel_id as null
if (body.guild_id === null) body.guild_id = voiceState.guild_id;
voiceState.assign(body);
voiceState = Object.assign(voiceState, body);
} catch (error) {
voiceState = new VoiceState({
voiceState = Object.assign(new VoiceState(), {
...body,
user_id: this.user_id,
deaf: false,

View File

@@ -7,6 +7,8 @@ try {
import { Payload, WebSocket } from "@fosscord/gateway";
export async function Send(socket: WebSocket, data: Payload) {
if(process.env.WS_VERBOSE)
console.log(`[Websocket] Outgoing message: ${JSON.stringify(data)}`);
let buffer: Buffer | string;
if (socket.encoding === "etf") buffer = erlpack.pack(data);
// TODO: encode circular object

View File

@@ -18,7 +18,7 @@
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */,
// "sourceMap": true /* Generates corresponding '.map' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist/" /* Redirect output structure to the directory. */,
"rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,

View File

@@ -1,11 +1,12 @@
import "reflect-metadata";
import { BaseEntity, EntityMetadata, ObjectIdColumn, PrimaryColumn, FindOptionsWhere } from "typeorm";
import { BaseEntity, EntityMetadata, ObjectIdColumn, PrimaryColumn, FindOptionsWhere, Generated, SaveOptions } from "typeorm";
import { Snowflake } from "../util/Snowflake";
export class BaseClassWithoutId extends BaseEntity {
constructor(props?: any) {
super();
this.assign(props);
if(props != undefined && props != null && Object.keys(props).length > 0)
this.assign(props);
}
assign(props: any = {}) {
@@ -13,8 +14,11 @@ export class BaseClassWithoutId extends BaseEntity {
delete props.opts;
delete props.props;
// will not include relational properties
for (const key in props) {
console.warn("WARNING: BaseClass.assign called! This will probably fail!");
console.warn(this)
Object.assign(this,props);
if(/--debug|--inspect/.test(process.execArgv.join(' '))) debugger;
/*for (const key in props) {
// @ts-ignore
const setter = this[`set${key.capitalize()}`]; // use setter function if it exists
@@ -24,7 +28,7 @@ export class BaseClassWithoutId extends BaseEntity {
// @ts-ignore
this[key] = props[key];
}
}
}*/
}
}
@@ -35,8 +39,13 @@ export class BaseClass extends BaseClassWithoutId {
id: string;
assign(props: any = {}) {
super.assign(props);
super.assign(props);
if (!this.id) this.id = Snowflake.generate();
return this;
}
save(options?: SaveOptions | undefined): Promise<this> {
if (!this.id) this.id = Snowflake.generate();
return super.save(options);
}
}

View File

@@ -222,7 +222,7 @@ export class Channel extends BaseClass {
};
await Promise.all([
new Channel(channel).save(),
Object.assign(new Channel(),channel).save(),
!opts?.skipEventEmit
? emitEvent({
event: "CHANNEL_CREATE",
@@ -263,7 +263,8 @@ export class Channel extends BaseClass {
if (containsAll(re, channelRecipients)) {
if (channel == null) {
channel = ur.channel;
await ur.assign({ closed: false }).save();
ur = Object.assign(ur, { closed: false });
await ur.save();
}
}
}
@@ -272,7 +273,7 @@ export class Channel extends BaseClass {
if (channel == null) {
name = trimSpecial(name);
channel = await new Channel({
channel = await (Object.assign(new Channel(), {
name,
type,
owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server
@@ -280,9 +281,9 @@ export class Channel extends BaseClass {
last_message_id: null,
recipients: channelRecipients.map(
(x) =>
new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })
Object.assign(new Recipient(), { user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })
),
}).save();
}) as Channel).save();
}
const channel_dto = await DmChannelDTO.from(channel);

View File

@@ -129,6 +129,7 @@ export interface ConfigValue {
requireCaptcha: boolean;
};
register: {
defaultRights: string;
email: {
required: boolean;
allowlist: boolean;
@@ -349,6 +350,7 @@ export const DefaultConfigOptions: ConfigValue = {
minSymbols: 0,
},
incrementingDiscriminators: false,
defaultRights: "0"
},
regions: {
default: "fosscord",

View File

@@ -285,7 +285,7 @@ export class Guild extends BaseClass {
}) {
const guild_id = Snowflake.generate();
const guild = await new Guild({
const guild: Guild = Object.assign(new Guild(),{
name: body.name || "Fosscord",
icon: await handleFile(`/icons/${guild_id}`, body.icon as string),
region: Config.get().regions.default,
@@ -316,11 +316,12 @@ export class Guild extends BaseClass {
welcome_channels: [],
},
widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions
}).save();
});
await guild.save();
// we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error
// TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage
await new Role({
let role: Role = Object.assign(new Role(), {
id: guild_id,
guild_id: guild_id,
color: 0,
@@ -333,7 +334,8 @@ export class Guild extends BaseClass {
position: 0,
icon: null,
unicode_emoji: null
}).save();
});
await role.save();
if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }];

View File

@@ -85,8 +85,8 @@ export class Member extends BaseClassWithoutId {
@Column()
joined_at: Date;
@Column({ type: "bigint", nullable: true })
premium_since?: number;
@Column({ nullable: true })
premium_since?: Date;
@Column()
deaf: boolean;
@@ -161,7 +161,7 @@ export class Member extends BaseClassWithoutId {
}),
Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }),
]);
member.roles.push(new Role({ id: role_id }));
member.roles.push(Object.assign(new Role(), { id: role_id }));
await Promise.all([
member.save(),
@@ -264,9 +264,9 @@ export class Member extends BaseClassWithoutId {
//TODO: check for bugs
if(guild.member_count) guild.member_count++;
await Promise.all([
new Member({
Object.assign(new Member(), {
...member,
roles: [new Role({ id: guild_id })],
roles: [Object.assign(new Role(), { id: guild_id })],
// read_state: {},
settings: {
channel_overrides: [],

View File

@@ -255,7 +255,7 @@ export class User extends BaseClass {
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
const language = req.language === "en" ? "en-US" : req.language || "en-US";
const user = new User({
const user = Object.assign(new User(), {
created_at: new Date(),
username: username,
discriminator,
@@ -275,7 +275,7 @@ export class User extends BaseClass {
disabled: false,
deleted: false,
email: email,
rights: "0", // TODO: grant rights correctly, as 0 actually stands for no rights at all
rights: Config.get().register.defaultRights, // TODO: grant rights correctly, as 0 actually stands for no rights at all
nsfw_allowed: true, // TODO: depending on age
public_flags: "0",
flags: "0", // TODO: generate
@@ -289,6 +289,8 @@ export class User extends BaseClass {
notes: {},
});
console.log("new user")
console.log(user);
await user.save();
setImmediate(async () => {

View File

@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class premiumSinceAsDate1659921859145 implements MigrationInterface {
name = 'premiumSinceAsDate1659921859145'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE \`members\` DROP COLUMN \`premium_since\`
`);
await queryRunner.query(`
ALTER TABLE \`members\`
ADD \`premium_since\` datetime NULL
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE \`members\` DROP COLUMN \`premium_since\`
`);
await queryRunner.query(`
ALTER TABLE \`members\`
ADD \`premium_since\` bigint NULL
`);
}
}

View File

@@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class premiumSinceAsDate1659921826567 implements MigrationInterface {
name = 'premiumSinceAsDate1659921826567'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "members" DROP COLUMN "premium_since"
`);
await queryRunner.query(`
ALTER TABLE "members"
ADD "premium_since" TIMESTAMP
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "members" DROP COLUMN "premium_since"
`);
await queryRunner.query(`
ALTER TABLE "members"
ADD "premium_since" bigint
`);
}
}

View File

@@ -0,0 +1,252 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class premiumSinceAsDate1659921722863 implements MigrationInterface {
name = 'premiumSinceAsDate1659921722863'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3"
`);
await queryRunner.query(`
CREATE TABLE "temporary_members" (
"index" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"id" varchar NOT NULL,
"guild_id" varchar NOT NULL,
"nick" varchar,
"joined_at" datetime NOT NULL,
"premium_since" bigint,
"deaf" boolean NOT NULL,
"mute" boolean NOT NULL,
"pending" boolean NOT NULL,
"settings" text NOT NULL,
"last_message_id" varchar,
"joined_by" varchar,
CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION
)
`);
await queryRunner.query(`
INSERT INTO "temporary_members"(
"index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
)
SELECT "index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
FROM "members"
`);
await queryRunner.query(`
DROP TABLE "members"
`);
await queryRunner.query(`
ALTER TABLE "temporary_members"
RENAME TO "members"
`);
await queryRunner.query(`
CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id")
`);
await queryRunner.query(`
DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3"
`);
await queryRunner.query(`
CREATE TABLE "temporary_members" (
"index" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"id" varchar NOT NULL,
"guild_id" varchar NOT NULL,
"nick" varchar,
"joined_at" datetime NOT NULL,
"premium_since" datetime,
"deaf" boolean NOT NULL,
"mute" boolean NOT NULL,
"pending" boolean NOT NULL,
"settings" text NOT NULL,
"last_message_id" varchar,
"joined_by" varchar,
CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION
)
`);
await queryRunner.query(`
INSERT INTO "temporary_members"(
"index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
)
SELECT "index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
FROM "members"
`);
await queryRunner.query(`
DROP TABLE "members"
`);
await queryRunner.query(`
ALTER TABLE "temporary_members"
RENAME TO "members"
`);
await queryRunner.query(`
CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id")
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3"
`);
await queryRunner.query(`
ALTER TABLE "members"
RENAME TO "temporary_members"
`);
await queryRunner.query(`
CREATE TABLE "members" (
"index" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"id" varchar NOT NULL,
"guild_id" varchar NOT NULL,
"nick" varchar,
"joined_at" datetime NOT NULL,
"premium_since" bigint,
"deaf" boolean NOT NULL,
"mute" boolean NOT NULL,
"pending" boolean NOT NULL,
"settings" text NOT NULL,
"last_message_id" varchar,
"joined_by" varchar,
CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION
)
`);
await queryRunner.query(`
INSERT INTO "members"(
"index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
)
SELECT "index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
FROM "temporary_members"
`);
await queryRunner.query(`
DROP TABLE "temporary_members"
`);
await queryRunner.query(`
CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id")
`);
await queryRunner.query(`
DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3"
`);
await queryRunner.query(`
ALTER TABLE "members"
RENAME TO "temporary_members"
`);
await queryRunner.query(`
CREATE TABLE "members" (
"index" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"id" varchar NOT NULL,
"guild_id" varchar NOT NULL,
"nick" varchar,
"joined_at" datetime NOT NULL,
"premium_since" bigint,
"deaf" boolean NOT NULL,
"mute" boolean NOT NULL,
"pending" boolean NOT NULL,
"settings" text NOT NULL,
"last_message_id" varchar,
"joined_by" varchar,
CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION
)
`);
await queryRunner.query(`
INSERT INTO "members"(
"index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
)
SELECT "index",
"id",
"guild_id",
"nick",
"joined_at",
"premium_since",
"deaf",
"mute",
"pending",
"settings",
"last_message_id",
"joined_by"
FROM "temporary_members"
`);
await queryRunner.query(`
DROP TABLE "temporary_members"
`);
await queryRunner.query(`
CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id")
`);
}
}

View File

@@ -138,6 +138,9 @@ export class BitField {
return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0));
}
if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit];
if (bit === "0") return BigInt(0); //special case: 0
if (typeof bit === "string") return BigInt(bit); //last ditch effort...
if(/--debug|--inspect/.test(process.execArgv.join(' '))) debugger; //if you're here, we have an invalid bitfield... if bit is 0, thats fine, I guess...
throw new RangeError("BITFIELD_INVALID: " + bit);
}
}