diff --git a/api/src/middlewares/TestClient.ts b/api/src/middlewares/TestClient.ts index 167b4c47c..4fe631cb5 100644 --- a/api/src/middlewares/TestClient.ts +++ b/api/src/middlewares/TestClient.ts @@ -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 = new Map(); - 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(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()); } diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index a49081efb..4001b8345 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -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(), diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 6155ef7d7..4e97b275e 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -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, diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index c14addbe9..fc2ec4880 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -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(); diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts index 28d5607d4..d6e25599d 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/api/src/routes/channels/#channel_id/recipients.ts @@ -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({ diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index 128ff2501..5ff639581 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -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), diff --git a/api/src/routes/guilds/#guild_id/emojis.ts b/api/src/routes/guilds/#guild_id/emojis.ts index c4bcadf77..3e2ed4c20 100644 --- a/api/src/routes/guilds/#guild_id/emojis.ts +++ b/api/src/routes/guilds/#guild_id/emojis.ts @@ -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", diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 263579e75..643db7ce2 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -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)); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 2717ec2ad..eb31ec1d5 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -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(); diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts index af04fd066..a4b91237e 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -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, diff --git a/api/src/routes/guilds/#guild_id/roles/index.ts b/api/src/routes/guilds/#guild_id/roles/index.ts index d2d14122a..7e588d524 100644 --- a/api/src/routes/guilds/#guild_id/roles/index.ts +++ b/api/src/routes/guilds/#guild_id/roles/index.ts @@ -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, diff --git a/api/src/routes/guilds/#guild_id/stickers.ts b/api/src/routes/guilds/#guild_id/stickers.ts index 7913539be..c6a5037f7 100644 --- a/api/src/routes/guilds/#guild_id/stickers.ts +++ b/api/src/routes/guilds/#guild_id/stickers.ts @@ -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); diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index f2de46e46..edff67171 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -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); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index b4d9f6186..426559bdc 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -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, diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 37ea52b58..5900963e9 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -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; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index e2242ebfd..bb8cc017c 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -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); diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 924996890..6a2456d6b 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -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 diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 0509b0acd..014a5bd20 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -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; } diff --git a/api/src/util/handlers/Message.ts b/api/src/util/handlers/Message.ts index 7035bc5cc..34fb3d05c 100644 --- a/api/src/util/handlers/Message.ts +++ b/api/src/util/handlers/Message.ts @@ -47,7 +47,7 @@ export async function handleMessage(opts: MessageOptions): Promise { 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.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 diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 5c7aa2ba7..f6cbc82b1 100644 Binary files a/bundle/package-lock.json and b/bundle/package-lock.json differ diff --git a/bundle/package.json b/bundle/package.json index 8432e9931..6adb569ce 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -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" } diff --git a/cdn/tsconfig.json b/cdn/tsconfig.json index 94cc7b586..85b35f557 100644 --- a/cdn/tsconfig.json +++ b/cdn/tsconfig.json @@ -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. */, diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index c1bb73b67..508b4741d 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -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}`); diff --git a/gateway/src/events/Message.ts b/gateway/src/events/Message.ts index 313287841..71b6f2069 100644 --- a/gateway/src/events/Message.ts +++ b/gateway/src/events/Message.ts @@ -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]; diff --git a/gateway/src/opcodes/Heartbeat.ts b/gateway/src/opcodes/Heartbeat.ts index 50394130d..42b72d4b7 100644 --- a/gateway/src/opcodes/Heartbeat.ts +++ b/gateway/src/opcodes/Heartbeat.ts @@ -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); diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 13d555597..c6c24fcda 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -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 diff --git a/gateway/src/opcodes/LazyRequest.ts b/gateway/src/opcodes/LazyRequest.ts index 974769e94..0dbdb5261 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/gateway/src/opcodes/LazyRequest.ts @@ -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") diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/gateway/src/opcodes/VoiceStateUpdate.ts index 7f7db9b02..1f7c2f1b5 100644 --- a/gateway/src/opcodes/VoiceStateUpdate.ts +++ b/gateway/src/opcodes/VoiceStateUpdate.ts @@ -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, diff --git a/gateway/src/util/Send.ts b/gateway/src/util/Send.ts index dbe2e7899..2a28d8e01 100644 --- a/gateway/src/util/Send.ts +++ b/gateway/src/util/Send.ts @@ -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 diff --git a/gateway/tsconfig.json b/gateway/tsconfig.json index 70407c6b0..9f855d333 100644 --- a/gateway/tsconfig.json +++ b/gateway/tsconfig.json @@ -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. */, diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 0cb885fae..1ab75a5a4 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -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 { + if (!this.id) this.id = Snowflake.generate(); + return super.save(options); + } } diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 5ccb43ce9..a7ca647b4 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -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); diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index c84ea4aa0..901a5e549 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -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", diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index 328c586ef..058033e8c 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -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" }]; diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts index 7ca50992a..e4aa8331b 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts @@ -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: [], diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 7d5dc5a6e..81017c2d9 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -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 () => { diff --git a/util/src/migrations/mariadb/1659921859145-premium_since_as_date.ts b/util/src/migrations/mariadb/1659921859145-premium_since_as_date.ts new file mode 100644 index 000000000..de173cfe6 --- /dev/null +++ b/util/src/migrations/mariadb/1659921859145-premium_since_as_date.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class premiumSinceAsDate1659921859145 implements MigrationInterface { + name = 'premiumSinceAsDate1659921859145' + + public async up(queryRunner: QueryRunner): Promise { + 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 { + await queryRunner.query(` + ALTER TABLE \`members\` DROP COLUMN \`premium_since\` + `); + await queryRunner.query(` + ALTER TABLE \`members\` + ADD \`premium_since\` bigint NULL + `); + } + +} diff --git a/util/src/migrations/postgres/1659921826567-premium_since_as_date.ts b/util/src/migrations/postgres/1659921826567-premium_since_as_date.ts new file mode 100644 index 000000000..ac1e2edb7 --- /dev/null +++ b/util/src/migrations/postgres/1659921826567-premium_since_as_date.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class premiumSinceAsDate1659921826567 implements MigrationInterface { + name = 'premiumSinceAsDate1659921826567' + + public async up(queryRunner: QueryRunner): Promise { + 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 { + await queryRunner.query(` + ALTER TABLE "members" DROP COLUMN "premium_since" + `); + await queryRunner.query(` + ALTER TABLE "members" + ADD "premium_since" bigint + `); + } + +} diff --git a/util/src/migrations/sqlite/1659921722863-premium_since_as_date.ts b/util/src/migrations/sqlite/1659921722863-premium_since_as_date.ts new file mode 100644 index 000000000..788be6257 --- /dev/null +++ b/util/src/migrations/sqlite/1659921722863-premium_since_as_date.ts @@ -0,0 +1,252 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class premiumSinceAsDate1659921722863 implements MigrationInterface { + name = 'premiumSinceAsDate1659921722863' + + public async up(queryRunner: QueryRunner): Promise { + 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 { + 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") + `); + } + +} diff --git a/util/src/util/BitField.ts b/util/src/util/BitField.ts index fb887e055..9bdbf6d78 100644 --- a/util/src/util/BitField.ts +++ b/util/src/util/BitField.ts @@ -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); } }