diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 63fee9b9..37734397 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -135,7 +135,7 @@ router.put( embeds, channel_id, attachments, - edited_timestamp: undefined, + edited_timestamp: null, timestamp: new Date(snowflake.timestamp), }); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 849968f9..f3c3503f 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -13,7 +13,8 @@ import { MessageCreateEvent, Snowflake, uploadFile, - Member + Member, + Role, } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { handleMessage, postHandleMessage, route } from "@fosscord/api"; @@ -146,11 +147,11 @@ router.get("/", async (req: Request, res: Response) => { Some clients ( discord.js ) only check if a property exists within the response, which causes erorrs when, say, the `application` property is `null`. **/ - - for (var curr in x) { - if (x[curr] === null) - delete x[curr]; - } + + // for (var curr in x) { + // if (x[curr] === null) + // delete x[curr]; + // } return x; }) @@ -217,7 +218,7 @@ router.post( embeds, channel_id, attachments, - edited_timestamp: undefined, + edited_timestamp: null, timestamp: new Date() }); @@ -244,8 +245,12 @@ router.post( ); } - //Fix for the client bug - delete message.member + const member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] }); + member.roles = member.roles.filter((role: Role) => { + return role.id !== role.guild_id; + }).map((role: Role) => { + return role.id; + }) as any; await Promise.all([ message.save(), diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index b22b72fb..50a00cac 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -6,6 +6,14 @@ const router = Router(); export interface UserSettingsSchema extends Partial {} +router.get("/", route({}), async (req: Request, res: Response) => { + const user = await User.findOneOrFail( + { id: req.user_id }, + { relations: ["settings"] } + ) + return res.json(user.settings); +}); + router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => { const body = req.body as UserSettings; if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale diff --git a/api/src/util/handlers/Message.ts b/api/src/util/handlers/Message.ts index a6754bd1..8980b4cc 100644 --- a/api/src/util/handlers/Message.ts +++ b/api/src/util/handlers/Message.ts @@ -55,7 +55,8 @@ export async function handleMessage(opts: MessageOptions): Promise { attachments: opts.attachments || [], embeds: opts.embeds || [], reactions: /*opts.reactions ||*/[], - type: opts.type ?? 0 + type: opts.type ?? 0, + edited_timestamp: null }); if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { @@ -278,6 +279,6 @@ interface MessageOptions extends MessageCreateSchema { embeds?: Embed[]; channel_id?: string; attachments?: Attachment[]; - edited_timestamp?: Date; + edited_timestamp: Date | null; timestamp?: Date; } diff --git a/bundle/.vscode/launch.json b/bundle/.vscode/launch.json index d7129ed8..6e1a34b2 100644 --- a/bundle/.vscode/launch.json +++ b/bundle/.vscode/launch.json @@ -23,6 +23,7 @@ }, { "sourceMaps": true, + "resolveSourceMapLocations": null, /* allow breakpoints in modules other than bundle */ "type": "node", "request": "launch", "name": "Launch Server", diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 14b94933..4046e7b6 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 3b79962d..b8678f81 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -40,7 +40,7 @@ "@types/jsonwebtoken": "^8.5.0", "@types/morgan": "^1.9.3", "@types/multer": "^1.4.7", - "@types/node": "^14.17.9", + "@types/node": "^14.18.24", "@types/node-fetch": "^2.5.12", "@types/node-os-utils": "^1.2.0", "@types/supertest": "^2.0.11", @@ -112,6 +112,7 @@ "typescript-cached-transpile": "^0.0.6", "typescript-json-schema": "^0.50.1", "ws": "^7.4.2", - "sharp": "^0.30.7" + "sharp": "^0.30.7", + "fast-zlib": "^2.0.1" } } \ No newline at end of file diff --git a/gateway/package.json b/gateway/package.json index 0524cc40..205cd9c1 100644 --- a/gateway/package.json +++ b/gateway/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@types/amqplib": "^0.8.1", "@types/jsonwebtoken": "^8.5.0", - "@types/node": "^14.17.9", + "@types/node": "^14.18.24", "@types/node-fetch": "^2.5.12", "@types/ws": "^7.4.0", "@zerollup/ts-transform-paths": "^1.7.18", @@ -29,6 +29,7 @@ "@fosscord/util": "file:../util", "amqplib": "^0.8.0", "dotenv": "^8.2.0", + "fast-zlib": "^2.0.1", "jsonwebtoken": "^8.5.1", "lambert-server": "^1.2.11", "missing-native-js-functions": "^1.2.18", diff --git a/gateway/src/events/Close.ts b/gateway/src/events/Close.ts index 5b7c512c..40d9a6f7 100644 --- a/gateway/src/events/Close.ts +++ b/gateway/src/events/Close.ts @@ -13,6 +13,7 @@ export async function Close(this: WebSocket, code: number, reason: string) { if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); if (this.readyTimeout) clearTimeout(this.readyTimeout); this.deflate?.close(); + this.inflate?.close(); this.removeAllListeners(); if (this.session_id) { diff --git a/gateway/src/events/Connection.ts b/gateway/src/events/Connection.ts index 2d41b7a3..af897ed7 100644 --- a/gateway/src/events/Connection.ts +++ b/gateway/src/events/Connection.ts @@ -6,7 +6,7 @@ import { setHeartbeat } from "../util/Heartbeat"; import { IncomingMessage } from "http"; import { Close } from "./Close"; import { Message } from "./Message"; -import { createDeflate } from "zlib"; +import { Deflate, Inflate } from "fast-zlib"; import { URL } from "url"; import { Config } from "@fosscord/util"; var erlpack: any; @@ -45,7 +45,6 @@ export async function Connection( return socket.close(CLOSECODES.Decode_error); } - // @ts-ignore socket.version = Number(searchParams.get("version")) || 8; if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); @@ -55,8 +54,8 @@ export async function Connection( if (socket.compress) { if (socket.compress !== "zlib-stream") return socket.close(CLOSECODES.Decode_error); - socket.deflate = createDeflate({ chunkSize: 65535 }); - socket.deflate.on("data", (chunk) => socket.send(chunk)); + socket.deflate = new Deflate(); + socket.inflate = new Inflate(); } socket.events = {}; diff --git a/gateway/src/events/Message.ts b/gateway/src/events/Message.ts index b675afcd..83917690 100644 --- a/gateway/src/events/Message.ts +++ b/gateway/src/events/Message.ts @@ -3,7 +3,7 @@ import { WebSocket, Payload } from "@fosscord/gateway"; var erlpack: any; try { erlpack = require("@yukikaze-bot/erlpack"); -} catch (error) {} +} catch (error) { } import OPCodeHandlers from "../opcodes"; import { Tuple } from "lambert-server"; import { check } from "../opcodes/instanceOf"; @@ -22,8 +22,19 @@ export async function Message(this: WebSocket, buffer: WS.Data) { if (this.encoding === "etf" && buffer instanceof Buffer) data = erlpack.unpack(buffer); - else if (this.encoding === "json" && typeof buffer === "string") - data = JSON.parse(buffer); + else if (this.encoding === "json" && buffer instanceof Buffer) { + if (this.inflate) { + try { + buffer = this.inflate.process(buffer) as any; + } catch { + buffer = buffer.toString() as any; + } + } + data = JSON.parse(buffer as string); + } + else if (typeof buffer == "string") { + data = JSON.parse(buffer as string); + } else return; check.call(this, PayloadSchema, data); @@ -42,6 +53,6 @@ export async function Message(this: WebSocket, buffer: WS.Data) { } catch (error) { console.error(error); // if (!this.CLOSED && this.CLOSING) - return this.close(CLOSECODES.Unknown_error); + return this.close(CLOSECODES.Unknown_error); } } diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 301f714d..041512c5 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -175,7 +175,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { avatar: related_user.avatar, bot: related_user.bot, bio: related_user.bio, - premium_since: user.premium_since + premium_since: user.premium_since, + accent_color: related_user.accent_color, }; users.push(public_related_user); } @@ -222,10 +223,12 @@ export async function onIdentify(this: WebSocket, data: Payload) { premium: user.premium, premium_type: user.premium_type, public_flags: user.public_flags, + premium_usage_flags: user.premium_usage_flags, + purchased_flags: user.purchased_flags, username: user.username, verified: user.verified, bot: user.bot, - accent_color: user.accent_color || 0, + accent_color: user.accent_color, banner: user.banner, bio: user.bio, premium_since: user.premium_since @@ -274,6 +277,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { users: users.filter((x) => x).unique(), merged_members: merged_members, // shard // TODO: only for user sharding + sessions: [], // TODO: + presences: [], // TODO: }; // TODO: send real proper data structure diff --git a/gateway/src/schema/Identify.ts b/gateway/src/schema/Identify.ts index 6aa93ce7..6f68b515 100644 --- a/gateway/src/schema/Identify.ts +++ b/gateway/src/schema/Identify.ts @@ -42,7 +42,14 @@ export const IdentifySchema = { $read_state_version: Number, $user_guild_settings_version: Number, $user_settings_version: undefined, - $useruser_guild_settings_version: Number, + $useruser_guild_settings_version: undefined, + }, + $clientState: { + $guildHashes: Object, + $highestLastMessageId: String || Number, + $readStateVersion: Number, + $useruserGuildSettingsVersion: undefined, + $userGuildSettingsVersion: undefined, }, $v: Number, $version: Number, @@ -76,6 +83,7 @@ export interface IdentifySchema { presence?: ActivitySchema; compress?: boolean; large_threshold?: number; + largeThreshold?: number; shard?: [bigint, bigint]; guild_subscriptions?: boolean; capabilities?: number; @@ -87,5 +95,12 @@ export interface IdentifySchema { user_settings_version?: number; useruser_guild_settings_version?: number; }; + clientState?: { + guildHashes?: any; + highestLastMessageId?: string | number; + readStateVersion?: number; + userGuildSettingsVersion?: number; + useruserGuildSettingsVersion?: number; + }; v?: number; } diff --git a/gateway/src/util/Send.ts b/gateway/src/util/Send.ts index c4202b21..2a4aa5ef 100644 --- a/gateway/src/util/Send.ts +++ b/gateway/src/util/Send.ts @@ -14,9 +14,7 @@ export function Send(socket: WebSocket, data: Payload) { else return; // TODO: compression if (socket.deflate) { - socket.deflate.write(buffer); - socket.deflate.flush(); - return; + buffer = socket.deflate.process(buffer) as Buffer; } return new Promise((res, rej) => { diff --git a/gateway/src/util/WebSocket.ts b/gateway/src/util/WebSocket.ts index e3313f40..1ca90340 100644 --- a/gateway/src/util/WebSocket.ts +++ b/gateway/src/util/WebSocket.ts @@ -1,6 +1,6 @@ import { Intents, Permissions } from "@fosscord/util"; import WS from "ws"; -import { Deflate } from "zlib"; +import { Deflate, Inflate } from "fast-zlib"; export interface WebSocket extends WS { version: number; @@ -11,6 +11,7 @@ export interface WebSocket extends WS { shard_count?: bigint; shard_id?: bigint; deflate?: Deflate; + inflate?: Inflate; heartbeatTimeout: NodeJS.Timeout; readyTimeout: NodeJS.Timeout; intents: Intents; diff --git a/package-lock.json b/package-lock.json index 9a887d06..ad152edd 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts index 3e8cd5ef..10fa03ff 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts @@ -108,8 +108,8 @@ export class Channel extends BaseClass { @Column({ nullable: true }) user_limit?: number; - @Column({ nullable: true }) - nsfw?: boolean; + @Column() + nsfw: boolean = false; @Column({ nullable: true }) rate_limit_per_user?: number; @@ -291,6 +291,7 @@ export class Channel extends BaseClass { (x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) }) ), + nsfw: false, }).save(); } diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index a1fef058..ac1af7f3 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -444,38 +444,4 @@ export const DefaultConfigOptions: ConfigValue = { traceSampleRate: 1.0, environment: hostname() } -}; - - -console.log(( - Rights.FLAGS.MANAGE_GUILDS + - Rights.FLAGS.MANAGE_MESSAGES + - Rights.FLAGS.MANAGE_TICKETS + - Rights.FLAGS.MANAGE_USERS + - Rights.FLAGS.CREATE_CHANNELS + - Rights.FLAGS.CREATE_DMS + - Rights.FLAGS.CREATE_DM_GROUPS + - Rights.FLAGS.CREATE_GUILDS + - Rights.FLAGS.CREATE_INVITES + - Rights.FLAGS.CREATE_ROLES + - Rights.FLAGS.CREATE_TEMPLATES + - Rights.FLAGS.CREATE_WEBHOOKS + - Rights.FLAGS.JOIN_GUILDS + - Rights.FLAGS.PIN_MESSAGES + - Rights.FLAGS.SELF_ADD_REACTIONS + - Rights.FLAGS.SELF_DELETE_MESSAGES + - Rights.FLAGS.SELF_EDIT_MESSAGES + - Rights.FLAGS.SELF_EDIT_NAME + - Rights.FLAGS.SEND_MESSAGES + - Rights.FLAGS.USE_ACTIVITIES + - Rights.FLAGS.USE_VIDEO + - Rights.FLAGS.USE_VOICE + - Rights.FLAGS.INVITE_USERS + - Rights.FLAGS.SELF_DELETE_DISABLE + - Rights.FLAGS.DEBTABLE + - Rights.FLAGS.KICK_BAN_MEMBERS + - Rights.FLAGS.SELF_LEAVE_GROUPS + - Rights.FLAGS.SELF_ADD_DISCOVERABLE + - Rights.FLAGS.USE_ACHIEVEMENTS + - Rights.FLAGS.USE_MASS_INVITES -).toString()) \ No newline at end of file +}; \ No newline at end of file diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index a5b732e8..143cb542 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -267,8 +267,8 @@ export class Guild extends BaseClass { @Column({ nullable: true }) nsfw_level?: number; - @Column({ nullable: true }) - nsfw?: boolean; + @Column() + nsfw: boolean; // TODO: nested guilds @Column({ nullable: true }) @@ -335,7 +335,7 @@ export class Guild extends BaseClass { unicode_emoji: null }).save(); - if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; + if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general", nsfw: false }]; const ids = new Map(); diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts index 013e92a9..83607ed4 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts @@ -122,7 +122,7 @@ export class Message extends BaseClass { timestamp: Date; @Column({ nullable: true }) - edited_timestamp?: Date; + edited_timestamp: Date; @Column({ nullable: true }) tts?: boolean; diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index a8f7f0c3..cac2784a 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -30,6 +30,8 @@ export enum PrivateUserEnum { nsfw_allowed, premium, premium_type, + purchased_flags, + premium_usage_flags, disabled, settings, // locale @@ -53,8 +55,6 @@ export interface UserPrivate extends Pick { locale: string; } -// TODO: add purchased_flags, premium_usage_flags - @Entity("users") export class User extends BaseClass { @Column() @@ -139,6 +139,12 @@ export class User extends BaseClass { @Column() public_flags: number; + @Column() + purchased_flags: number; + + @Column() + premium_usage_flags: number; + @Column({ type: "bigint" }) rights: string; // Rights @@ -281,6 +287,8 @@ export class User extends BaseClass { valid_tokens_since: new Date(), }, settings: { ...defaultSettings, locale: language }, + purchased_flags: 5, // TODO: idk what the values for this are + premium_usage_flags: 2, // TODO: idk what the values for this are extended_settings: {}, fingerprints: [], notes: {}, @@ -332,6 +340,11 @@ export const defaultSettings: UserSettings = { stream_notifications_enabled: false, theme: "dark", timezone_offset: 0, // TODO: timezone from request + + banner_color: null, + friend_discovery_flags: 0, + view_nsfw_guilds: true, + passwordless: false, }; export interface UserSettings { @@ -377,6 +390,10 @@ export interface UserSettings { stream_notifications_enabled: boolean; theme: "dark" | "white"; // dark timezone_offset: number; // e.g -60 + banner_color: string | null; + friend_discovery_flags: number; + view_nsfw_guilds: boolean; + passwordless: boolean; } export const CUSTOM_USER_FLAG_OFFSET = BigInt(1) << BigInt(32); diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index 416082ed..59f995db 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -98,6 +98,7 @@ export interface ReadyEventData { merged_members?: PublicMember[][]; // probably all users who the user is in contact with users?: PublicUser[]; + sessions: any[]; } export interface ReadyEvent extends Event { diff --git a/util/src/migrations/1660678870706-opencordFixes.ts b/util/src/migrations/1660678870706-opencordFixes.ts new file mode 100644 index 00000000..1f10c212 --- /dev/null +++ b/util/src/migrations/1660678870706-opencordFixes.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class opencordFixes1660678870706 implements MigrationInterface { + name = 'opencordFixes1660678870706' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE \`users\` + ADD \`purchased_flags\` int NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`users\` + ADD \`premium_usage_flags\` int NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` + ADD \`friend_discovery_flags\` int NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` + ADD \`view_nsfw_guilds\` tinyint NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` + ADD \`passwordless\` tinyint NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NOT NULL + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NULL + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` DROP COLUMN \`passwordless\` + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` DROP COLUMN \`view_nsfw_guilds\` + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` DROP COLUMN \`friend_discovery_flags\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` DROP COLUMN \`premium_usage_flags\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` DROP COLUMN \`purchased_flags\` + `); + } + +} \ No newline at end of file diff --git a/util/src/migrations/1660689892073-mobileFixes2.ts b/util/src/migrations/1660689892073-mobileFixes2.ts new file mode 100644 index 00000000..34328966 --- /dev/null +++ b/util/src/migrations/1660689892073-mobileFixes2.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class mobileFixes21660689892073 implements MigrationInterface { + name = 'mobileFixes21660689892073' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE \`user_settings\` + ADD \`banner_color\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`channels\` CHANGE \`nsfw\` \`nsfw\` tinyint NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` CHANGE \`nsfw\` \`nsfw\` tinyint NOT NULL + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE \`guilds\` CHANGE \`nsfw\` \`nsfw\` tinyint NULL + `); + await queryRunner.query(` + ALTER TABLE \`channels\` CHANGE \`nsfw\` \`nsfw\` tinyint NULL + `); + await queryRunner.query(` + ALTER TABLE \`user_settings\` DROP COLUMN \`banner_color\` + `); + } + +} \ No newline at end of file diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts index 9ab5d14c..2973e114 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts @@ -29,7 +29,7 @@ export function initDatabase(): Promise { url: isSqlite ? undefined : dbConnectionString, database: isSqlite ? dbConnectionString : undefined, // @ts-ignore - entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.name), + entities: Object.values(Models).filter((x) => x?.constructor?.name !== "Object" && x?.name), synchronize: type !== "mongodb", logging: false, cache: {