Merge branch 'master' into pr/hbjydev/454
This commit is contained in:
		
						commit
						d828a5e03d
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -5,4 +5,6 @@ node_modules | ||||
| api/assets/*.js | ||||
| api/assets/*.css | ||||
| database.db | ||||
| tsconfig.tsbuildinfo | ||||
| tsconfig.tsbuildinfo | ||||
| files/ | ||||
| .env | ||||
| @ -2887,47 +2887,324 @@ | ||||
|         }, | ||||
|         "$schema": "http://json-schema.org/draft-07/schema#" | ||||
|     }, | ||||
|     "EmojiListResponse": { | ||||
|         "type": "array", | ||||
|         "items": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "animated": { | ||||
|                     "type": "boolean" | ||||
|                 }, | ||||
|                 "available": { | ||||
|                     "type": "boolean" | ||||
|                 }, | ||||
|                 "id": { | ||||
|     "EmojiCreateSchema": { | ||||
|         "type": "object", | ||||
|         "properties": { | ||||
|             "name": { | ||||
|                 "type": "string" | ||||
|             }, | ||||
|             "image": { | ||||
|                 "type": "string" | ||||
|             }, | ||||
|             "require_colons": { | ||||
|                 "type": [ | ||||
|                     "null", | ||||
|                     "boolean" | ||||
|                 ] | ||||
|             }, | ||||
|             "roles": { | ||||
|                 "type": "array", | ||||
|                 "items": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "managed": { | ||||
|                     "type": "boolean" | ||||
|                 }, | ||||
|                 "name": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "require_colons": { | ||||
|                     "type": "boolean" | ||||
|                 }, | ||||
|                 "guild_id": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "roles": { | ||||
|                     "type": "array", | ||||
|                     "items": { | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "required": [ | ||||
|             "image" | ||||
|         ], | ||||
|         "definitions": { | ||||
|             "ChannelPermissionOverwriteType": { | ||||
|                 "enum": [ | ||||
|                     0, | ||||
|                     1 | ||||
|                 ], | ||||
|                 "type": "number" | ||||
|             }, | ||||
|             "Embed": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "title": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "type": { | ||||
|                         "enum": [ | ||||
|                             "article", | ||||
|                             "gifv", | ||||
|                             "image", | ||||
|                             "link", | ||||
|                             "rich", | ||||
|                             "video" | ||||
|                         ], | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "description": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "timestamp": { | ||||
|                         "type": "string", | ||||
|                         "format": "date-time" | ||||
|                     }, | ||||
|                     "color": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "footer": { | ||||
|                         "type": "object", | ||||
|                         "properties": { | ||||
|                             "text": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "icon_url": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "proxy_icon_url": { | ||||
|                                 "type": "string" | ||||
|                             } | ||||
|                         }, | ||||
|                         "required": [ | ||||
|                             "text" | ||||
|                         ] | ||||
|                     }, | ||||
|                     "image": { | ||||
|                         "$ref": "#/definitions/EmbedImage" | ||||
|                     }, | ||||
|                     "thumbnail": { | ||||
|                         "$ref": "#/definitions/EmbedImage" | ||||
|                     }, | ||||
|                     "video": { | ||||
|                         "$ref": "#/definitions/EmbedImage" | ||||
|                     }, | ||||
|                     "provider": { | ||||
|                         "type": "object", | ||||
|                         "properties": { | ||||
|                             "name": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "url": { | ||||
|                                 "type": "string" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     "author": { | ||||
|                         "type": "object", | ||||
|                         "properties": { | ||||
|                             "name": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "url": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "icon_url": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "proxy_icon_url": { | ||||
|                                 "type": "string" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     "fields": { | ||||
|                         "type": "array", | ||||
|                         "items": { | ||||
|                             "type": "object", | ||||
|                             "properties": { | ||||
|                                 "name": { | ||||
|                                     "type": "string" | ||||
|                                 }, | ||||
|                                 "value": { | ||||
|                                     "type": "string" | ||||
|                                 }, | ||||
|                                 "inline": { | ||||
|                                     "type": "boolean" | ||||
|                                 } | ||||
|                             }, | ||||
|                             "required": [ | ||||
|                                 "name", | ||||
|                                 "value" | ||||
|                             ] | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "required": [ | ||||
|                 "animated", | ||||
|                 "available", | ||||
|                 "id", | ||||
|                 "managed", | ||||
|                 "name", | ||||
|                 "require_colons" | ||||
|             ] | ||||
|             "EmbedImage": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "proxy_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "height": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "width": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "ChannelModifySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "name": { | ||||
|                         "maxLength": 100, | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "type": { | ||||
|                         "enum": [ | ||||
|                             0, | ||||
|                             1, | ||||
|                             10, | ||||
|                             11, | ||||
|                             12, | ||||
|                             13, | ||||
|                             2, | ||||
|                             3, | ||||
|                             4, | ||||
|                             5, | ||||
|                             6 | ||||
|                         ], | ||||
|                         "type": "number" | ||||
|                     }, | ||||
|                     "topic": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "icon": { | ||||
|                         "type": [ | ||||
|                             "null", | ||||
|                             "string" | ||||
|                         ] | ||||
|                     }, | ||||
|                     "bitrate": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "user_limit": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "rate_limit_per_user": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "position": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "permission_overwrites": { | ||||
|                         "type": "array", | ||||
|                         "items": { | ||||
|                             "type": "object", | ||||
|                             "properties": { | ||||
|                                 "id": { | ||||
|                                     "type": "string" | ||||
|                                 }, | ||||
|                                 "type": { | ||||
|                                     "$ref": "#/definitions/ChannelPermissionOverwriteType" | ||||
|                                 }, | ||||
|                                 "allow": { | ||||
|                                     "type": "string" | ||||
|                                 }, | ||||
|                                 "deny": { | ||||
|                                     "type": "string" | ||||
|                                 } | ||||
|                             }, | ||||
|                             "required": [ | ||||
|                                 "allow", | ||||
|                                 "deny", | ||||
|                                 "id", | ||||
|                                 "type" | ||||
|                             ] | ||||
|                         } | ||||
|                     }, | ||||
|                     "parent_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "nsfw": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "rtc_region": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "default_auto_archive_duration": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "UserPublic": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "username": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "discriminator": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "public_flags": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "avatar": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "accent_color": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "banner": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "bio": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "bot": { | ||||
|                         "type": "boolean" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "bio", | ||||
|                     "bot", | ||||
|                     "discriminator", | ||||
|                     "id", | ||||
|                     "public_flags", | ||||
|                     "username" | ||||
|                 ] | ||||
|             }, | ||||
|             "PublicConnectedAccount": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "name": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "type": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "verifie": { | ||||
|                         "type": "boolean" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "name", | ||||
|                     "type", | ||||
|                     "verifie" | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
|         "$schema": "http://json-schema.org/draft-07/schema#" | ||||
|     }, | ||||
|     "EmojiModifySchema": { | ||||
|         "type": "object", | ||||
|         "properties": { | ||||
|             "name": { | ||||
|                 "type": "string" | ||||
|             }, | ||||
|             "roles": { | ||||
|                 "type": "array", | ||||
|                 "items": { | ||||
|                     "type": "string" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "definitions": { | ||||
|             "ChannelPermissionOverwriteType": { | ||||
| @ -4470,7 +4747,7 @@ | ||||
|                 "type": "string" | ||||
|             }, | ||||
|             "permissions": { | ||||
|                 "type": "bigint" | ||||
|                 "type": "string" | ||||
|             }, | ||||
|             "color": { | ||||
|                 "type": "integer" | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
| 		<title>Discord Test Client</title> | ||||
| 	</head> | ||||
| 
 | ||||
| 	<body> | ||||
| 		<div id="app-mount"></div> | ||||
| 		<script> | ||||
| @ -46,12 +47,52 @@ | ||||
| 			); | ||||
| 
 | ||||
| 			// Auto register guest account: | ||||
| 			const prefix = [ | ||||
| 				"mysterious", | ||||
| 				"adventurous", | ||||
| 				"courageous", | ||||
| 				"precious", | ||||
| 				"cynical", | ||||
| 				"despicable", | ||||
| 				"suspicious", | ||||
| 				"gorgeous", | ||||
| 				"lovely", | ||||
| 				"stunning", | ||||
| 				"based", | ||||
| 				"keyed", | ||||
| 				"ratioed", | ||||
| 				"twink", | ||||
| 				"phoned" | ||||
| 			]; | ||||
| 			const suffix = [ | ||||
| 				"Anonymous", | ||||
| 				"Lurker", | ||||
| 				"User", | ||||
| 				"Enjoyer", | ||||
| 				"Hunk", | ||||
| 				"Top", | ||||
| 				"Bottom", | ||||
| 				"Sub", | ||||
| 				"Coolstar", | ||||
| 				"Wrestling", | ||||
| 				"TylerTheCreator", | ||||
| 				"Ad" | ||||
| 			]; | ||||
| 
 | ||||
| 			Array.prototype.random = function () { | ||||
| 				return this[Math.floor(Math.random() * this.length)]; | ||||
| 			}; | ||||
| 
 | ||||
| 			function _generateName() { | ||||
| 				return `${prefix.random()}${suffix.random()}`; | ||||
| 			} | ||||
| 
 | ||||
| 			const token = JSON.parse(localStorage.getItem("token")); | ||||
| 			if (!token && location.pathname !== "/login" && location.pathname !== "/register") { | ||||
| 				fetch(`${window.GLOBAL_ENV.API_ENDPOINT}/auth/register`, { | ||||
| 					method: "POST", | ||||
| 					headers: { "content-type": "application/json" }, | ||||
| 					body: JSON.stringify({ username: "Anonymous", consent: true }) | ||||
| 					body: JSON.stringify({ username: `${_generateName()}`, consent: true }) //${Date.now().toString().slice(-4)} | ||||
| 				}) | ||||
| 					.then((x) => x.json()) | ||||
| 					.then((x) => { | ||||
| @ -62,6 +103,12 @@ | ||||
| 						} | ||||
| 					}); | ||||
| 			} | ||||
| 
 | ||||
| 			const settings = JSON.parse(localStorage.getItem("UserSettingsStore")); | ||||
| 			if (settings && settings.locale === "en") { | ||||
| 				settings.locale = "en-US"; | ||||
| 				localStorage.setItem("UserSettingsStore", JSON.stringify(settings)); | ||||
| 			} | ||||
| 		</script> | ||||
| 		<script src="/assets/479a2f1e7d625dc134b9.js"></script> | ||||
| 		<script src="/assets/a15fd133a1d2d77a2424.js"></script> | ||||
|  | ||||
							
								
								
									
										10776
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10776
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -10,7 +10,7 @@ | ||||
| 		"test": "npm run build && npm run test:only", | ||||
| 		"test:watch": "jest --watch", | ||||
| 		"start": "npm run build && node dist/start", | ||||
| 		"build": "npx tsc -b .", | ||||
| 		"build": "npx tsc -p .", | ||||
| 		"build-docker": "tsc -p tsconfig-docker.json", | ||||
| 		"dev": "tsnd --respawn src/start.ts", | ||||
| 		"patch": "ts-patch install -s && npx patch-package", | ||||
| @ -38,10 +38,6 @@ | ||||
| 	"homepage": "https://fosscord.com", | ||||
| 	"devDependencies": { | ||||
| 		"@babel/core": "^7.15.5", | ||||
| 		"@babel/preset-env": "^7.15.6", | ||||
| 		"@babel/preset-typescript": "^7.15.0", | ||||
| 		"@swc/cli": "^0.1.51", | ||||
| 		"@swc/core": "^1.2.93", | ||||
| 		"@types/amqplib": "^0.8.1", | ||||
| 		"@types/bcrypt": "^5.0.0", | ||||
| 		"@types/express": "^4.17.9", | ||||
| @ -49,65 +45,46 @@ | ||||
| 		"@types/jest": "^27.0.1", | ||||
| 		"@types/jest-expect-message": "^1.0.3", | ||||
| 		"@types/jsonwebtoken": "^8.5.0", | ||||
| 		"@types/mongodb": "^3.6.9", | ||||
| 		"@types/mongoose": "^5.10.5", | ||||
| 		"@types/mongoose-autopopulate": "^0.10.1", | ||||
| 		"@types/mongoose-lean-virtuals": "^0.5.1", | ||||
| 		"@types/multer": "^1.4.5", | ||||
| 		"@types/node": "^14.17.9", | ||||
| 		"@types/node-fetch": "^2.5.7", | ||||
| 		"@types/supertest": "^2.0.11", | ||||
| 		"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 		"0x": "^4.10.2", | ||||
| 		"babel-jest": "^27.2.0", | ||||
| 		"caxa": "^2.1.0", | ||||
| 		"image-size": "^1.0.0", | ||||
| 		"jest": "^26.6.3", | ||||
| 		"jest-expect-message": "^1.0.2", | ||||
| 		"jest-runtime": "^27.2.1", | ||||
| 		"saslprep": "^1.0.3", | ||||
| 		"ts-node": "^9.1.1", | ||||
| 		"ts-node-dev": "^1.1.6", | ||||
| 		"ts-patch": "^1.4.4", | ||||
| 		"tsup": "^5.4.0", | ||||
| 		"typescript": "^4.4.2", | ||||
| 		"typescript-json-schema": "0.50.1" | ||||
| 		"typescript-json-schema": "0.50.1", | ||||
| 		"@types/morgan": "^1.9.3" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"@fosscord/util": "file:../util", | ||||
| 		"@types/morgan": "^1.9.3", | ||||
| 		"ajv": "8.6.2", | ||||
| 		"ajv-formats": "^2.1.1", | ||||
| 		"amqplib": "^0.8.0", | ||||
| 		"assert": "^1.5.0", | ||||
| 		"atomically": "^1.7.0", | ||||
| 		"bcrypt": "^5.0.1", | ||||
| 		"body-parser": "^1.19.0", | ||||
| 		"cheerio": "^1.0.0-rc.9", | ||||
| 		"dot-prop": "^6.0.1", | ||||
| 		"cheerio": "^1.0.0-rc.10", | ||||
| 		"dotenv": "^8.2.0", | ||||
| 		"env-paths": "^2.2.1", | ||||
| 		"esbuild": "^0.13.4", | ||||
| 		"express": "^4.17.1", | ||||
| 		"express-validator": "^6.9.2", | ||||
| 		"form-data": "^3.0.0", | ||||
| 		"i18next": "^19.9.2", | ||||
| 		"i18next-http-middleware": "^3.1.3", | ||||
| 		"i18next-node-fs-backend": "^2.1.3", | ||||
| 		"image-size": "^1.0.0", | ||||
| 		"jsonwebtoken": "^8.5.1", | ||||
| 		"lambert-server": "^1.2.11", | ||||
| 		"missing-native-js-functions": "^1.2.17", | ||||
| 		"mongoose": "^5.12.3", | ||||
| 		"mongoose-autopopulate": "^0.12.3", | ||||
| 		"mongoose-long": "^0.3.2", | ||||
| 		"morgan": "^1.10.0", | ||||
| 		"multer": "^1.4.2", | ||||
| 		"node-fetch": "^2.6.1", | ||||
| 		"patch-package": "^6.4.7", | ||||
| 		"supertest": "^6.1.6", | ||||
| 		"tsconfig-paths": "^3.11.0", | ||||
| 		"typeorm": "^0.2.37", | ||||
| 		"wsc": "^0.3.0" | ||||
| 		"typeorm": "^0.2.37" | ||||
| 	}, | ||||
| 	"jest": { | ||||
| 		"setupFiles": [ | ||||
|  | ||||
| @ -1,19 +1,17 @@ | ||||
| import { OptionsJson } from "body-parser"; | ||||
| import "missing-native-js-functions"; | ||||
| import { Connection } from "mongoose"; | ||||
| import { Server, ServerOptions } from "lambert-server"; | ||||
| import { Authentication, CORS } from "./middlewares/"; | ||||
| import { Config, initDatabase, initEvent } from "@fosscord/util"; | ||||
| import { ErrorHandler } from "./middlewares/ErrorHandler"; | ||||
| import { BodyParser } from "./middlewares/BodyParser"; | ||||
| import { Router, Request, Response, NextFunction } from "express"; | ||||
| import mongoose from "mongoose"; | ||||
| import path from "path"; | ||||
| import { initRateLimits } from "./middlewares/RateLimit"; | ||||
| import TestClient from "./middlewares/TestClient"; | ||||
| import { initTranslation } from "./middlewares/Translation"; | ||||
| import morgan from "morgan"; | ||||
| import { initInstance } from "./util/Instance"; | ||||
| import { registerRoutes } from "@fosscord/util"; | ||||
| 
 | ||||
| export interface FosscordServerOptions extends ServerOptions {} | ||||
| 
 | ||||
| @ -75,7 +73,7 @@ export class FosscordServer extends Server { | ||||
| 		await initRateLimits(api); | ||||
| 		await initTranslation(api); | ||||
| 
 | ||||
| 		this.routes = await this.registerRoutes(path.join(__dirname, "routes", "/")); | ||||
| 		this.routes = await registerRoutes(this, path.join(__dirname, "routes", "/")); | ||||
| 
 | ||||
| 		api.use("*", (error: any, req: Request, res: Response, next: NextFunction) => { | ||||
| 			if (error) return next(error); | ||||
|  | ||||
| @ -2,7 +2,7 @@ import { Router, Request, Response } from "express"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { random } from "@fosscord/api"; | ||||
| import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; | ||||
| import { Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; | ||||
| import { isTextChannel } from "./messages"; | ||||
| 
 | ||||
| const router: Router = Router(); | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; | ||||
| import { emitEvent, getPermission, MessageAckEvent, ReadState, Snowflake } from "@fosscord/util"; | ||||
| import { Request, Response, Router } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| 
 | ||||
| @ -18,7 +18,11 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques | ||||
| 	const permission = await getPermission(req.user_id, undefined, channel_id); | ||||
| 	permission.hasThrow("VIEW_CHANNEL"); | ||||
| 
 | ||||
| 	await ReadState.update({ user_id: req.user_id, channel_id }, { user_id: req.user_id, channel_id, last_message_id: message_id }); | ||||
| 	let read_state = await ReadState.findOne({ user_id: req.user_id, channel_id }); | ||||
| 	if (!read_state) read_state = new ReadState({ user_id: req.user_id, channel_id }); | ||||
| 	read_state.last_message_id = message_id; | ||||
| 
 | ||||
| 	await read_state.save(); | ||||
| 
 | ||||
| 	await emitEvent({ | ||||
| 		event: "MESSAGE_ACK", | ||||
|  | ||||
| @ -22,7 +22,7 @@ const router: Router = Router(); | ||||
| 
 | ||||
| export default router; | ||||
| 
 | ||||
| function isTextChannel(type: ChannelType): boolean { | ||||
| export function isTextChannel(type: ChannelType): boolean { | ||||
| 	switch (type) { | ||||
| 		case ChannelType.GUILD_STORE: | ||||
| 		case ChannelType.GUILD_VOICE: | ||||
| @ -39,7 +39,6 @@ function isTextChannel(type: ChannelType): boolean { | ||||
| 			return true; | ||||
| 	} | ||||
| } | ||||
| module.exports.isTextChannel = isTextChannel; | ||||
| 
 | ||||
| export interface MessageCreateSchema { | ||||
| 	content?: string; | ||||
| @ -103,6 +102,7 @@ router.get("/", async (req: Request, res: Response) => { | ||||
| 	} | ||||
| 
 | ||||
| 	const messages = await Message.find(query); | ||||
| 	const endpoint = Config.get().cdn.endpointPublic; | ||||
| 
 | ||||
| 	return res.json( | ||||
| 		messages.map((x) => { | ||||
| @ -115,7 +115,9 @@ router.get("/", async (req: Request, res: Response) => { | ||||
| 			// @ts-ignore
 | ||||
| 			if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; | ||||
| 			x.attachments?.forEach((x) => { | ||||
| 				x.proxy_url = `${Config.get().cdn.endpointPublic || "http://localhost:3003"}${new URL(x.proxy_url).pathname}`; | ||||
| 				// dynamically set attachment proxy_url in case the endpoint changed
 | ||||
| 				const uri = x.proxy_url.startsWith("http") ? x.proxy_url : `https://example.org${x.proxy_url}`; | ||||
| 				x.proxy_url = `${endpoint == null ? "http://localhost:3003" : endpoint}${new URL(uri).pathname}`; | ||||
| 			}); | ||||
| 
 | ||||
| 			return x; | ||||
|  | ||||
| @ -44,8 +44,8 @@ router.put( | ||||
| 			}; | ||||
| 			channel.permission_overwrites!.push(overwrite); | ||||
| 		} | ||||
| 		overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || 0n)); | ||||
| 		overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || 0n)); | ||||
| 		overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || BigInt("0"))); | ||||
| 		overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || BigInt("0"))); | ||||
| 
 | ||||
| 		await Promise.all([ | ||||
| 			channel.save(), | ||||
|  | ||||
							
								
								
									
										24
									
								
								api/src/routes/gifs/search.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								api/src/routes/gifs/search.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| import { Router, Response, Request } from "express"; | ||||
| import fetch from "node-fetch"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { getGifApiKey, parseGifResult } from "./trending"; | ||||
| 
 | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	// TODO: Custom providers
 | ||||
| 	const { q, media_format, locale } = req.query; | ||||
| 
 | ||||
| 	const apiKey = getGifApiKey(); | ||||
| 
 | ||||
| 	const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, { | ||||
| 		method: "get", | ||||
| 		headers: { "Content-Type": "application/json" } | ||||
| 	}); | ||||
| 
 | ||||
| 	const { results } = await response.json(); | ||||
| 
 | ||||
| 	res.json(results.map(parseGifResult)).status(200); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
							
								
								
									
										24
									
								
								api/src/routes/gifs/trending-gifs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								api/src/routes/gifs/trending-gifs.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| import { Router, Response, Request } from "express"; | ||||
| import fetch from "node-fetch"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { getGifApiKey, parseGifResult } from "./trending"; | ||||
| 
 | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	// TODO: Custom providers
 | ||||
| 	const { media_format, locale } = req.query; | ||||
| 
 | ||||
| 	const apiKey = getGifApiKey(); | ||||
| 
 | ||||
| 	const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, { | ||||
| 		method: "get", | ||||
| 		headers: { "Content-Type": "application/json" } | ||||
| 	}); | ||||
| 
 | ||||
| 	const { results } = await response.json(); | ||||
| 
 | ||||
| 	res.json(results.map(parseGifResult)).status(200); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
							
								
								
									
										57
									
								
								api/src/routes/gifs/trending.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								api/src/routes/gifs/trending.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| import { Router, Response, Request } from "express"; | ||||
| import fetch from "node-fetch"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { Config } from "@fosscord/util"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| 
 | ||||
| const router = Router(); | ||||
| 
 | ||||
| export function parseGifResult(result: any) { | ||||
| 	return { | ||||
| 		id: result.id, | ||||
| 		title: result.title, | ||||
| 		url: result.itemurl, | ||||
| 		src: result.media[0].mp4.url, | ||||
| 		gif_src: result.media[0].gif.url, | ||||
| 		width: result.media[0].mp4.dims[0], | ||||
| 		height: result.media[0].mp4.dims[1], | ||||
| 		preview: result.media[0].mp4.preview | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function getGifApiKey() { | ||||
| 	const { enabled, provider, apiKey } = Config.get().gif; | ||||
| 	if (!enabled) throw new HTTPError(`Gifs are disabled`); | ||||
| 	if (provider !== "tenor" || !apiKey) throw new HTTPError(`${provider} gif provider not supported`); | ||||
| 
 | ||||
| 	return apiKey; | ||||
| } | ||||
| 
 | ||||
| router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	// TODO: Custom providers
 | ||||
| 	// TODO: return gifs as mp4
 | ||||
| 	const { media_format, locale } = req.query; | ||||
| 
 | ||||
| 	const apiKey = getGifApiKey(); | ||||
| 
 | ||||
| 	const [responseSource, trendGifSource] = await Promise.all([ | ||||
| 		fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, { | ||||
| 			method: "get", | ||||
| 			headers: { "Content-Type": "application/json" } | ||||
| 		}), | ||||
| 		fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, { | ||||
| 			method: "get", | ||||
| 			headers: { "Content-Type": "application/json" } | ||||
| 		}) | ||||
| 	]); | ||||
| 
 | ||||
| 	const { tags } = await responseSource.json(); | ||||
| 	const { results } = await trendGifSource.json(); | ||||
| 
 | ||||
| 	res.json({ | ||||
| 		categories: tags.map((x: any) => ({ name: x.searchterm, src: x.image })), | ||||
| 		gifs: [parseGifResult(results[0])] | ||||
| 	}).status(200); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
| @ -31,10 +31,10 @@ router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHAN | ||||
| 
 | ||||
| 	await Promise.all([ | ||||
| 		body.map(async (x) => { | ||||
| 			if (!x.position && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); | ||||
| 			if (x.position == null && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); | ||||
| 
 | ||||
| 			const opts: any = {}; | ||||
| 			if (x.position) opts.position = x.position; | ||||
| 			if (x.position != null) opts.position = x.position; | ||||
| 
 | ||||
| 			if (x.parent_id) { | ||||
| 				opts.parent_id = x.parent_id; | ||||
|  | ||||
							
								
								
									
										121
									
								
								api/src/routes/guilds/#guild_id/emojis.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								api/src/routes/guilds/#guild_id/emojis.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| import { Router, Request, Response } from "express"; | ||||
| import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User } from "@fosscord/util"; | ||||
| import { route } from "@fosscord/api"; | ||||
| 
 | ||||
| const router = Router(); | ||||
| 
 | ||||
| export interface EmojiCreateSchema { | ||||
| 	name?: string; | ||||
| 	image: string; | ||||
| 	require_colons?: boolean | null; | ||||
| 	roles?: string[]; | ||||
| } | ||||
| 
 | ||||
| export interface EmojiModifySchema { | ||||
| 	name?: string; | ||||
| 	roles?: string[]; | ||||
| } | ||||
| 
 | ||||
| router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 
 | ||||
| 	await Member.IsInGuildOrFail(req.user_id, guild_id); | ||||
| 
 | ||||
| 	const emojis = await Emoji.find({ where: { guild_id: guild_id }, relations: ["user"] }); | ||||
| 
 | ||||
| 	return res.json(emojis); | ||||
| }); | ||||
| 
 | ||||
| router.get("/:emoji_id", route({}), async (req: Request, res: Response) => { | ||||
| 	const { guild_id, emoji_id } = req.params; | ||||
| 
 | ||||
| 	await Member.IsInGuildOrFail(req.user_id, guild_id); | ||||
| 
 | ||||
| 	const emoji = await Emoji.findOneOrFail({ where: { guild_id: guild_id, id: emoji_id }, relations: ["user"] }); | ||||
| 
 | ||||
| 	return res.json(emoji); | ||||
| }); | ||||
| 
 | ||||
| router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 	const body = req.body as EmojiCreateSchema; | ||||
| 
 | ||||
| 	const emoji_count = await Emoji.count({ guild_id: guild_id }); | ||||
| 	const { maxEmojis } = Config.get().limits.guild; | ||||
| 
 | ||||
| 	if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); | ||||
| 
 | ||||
| 	const id = Snowflake.generate(); | ||||
| 
 | ||||
| 	if (body.require_colons == null) body.require_colons = true; | ||||
| 
 | ||||
| 	const user = await User.findOneOrFail({ id: req.user_id }); | ||||
| 
 | ||||
| 	body.image = (await handleFile(`/emojis/${id}`, body.image)) as string; | ||||
| 
 | ||||
| 	const emoji = await new Emoji({ | ||||
| 		id: id, | ||||
| 		guild_id: guild_id, | ||||
| 		...body, | ||||
| 		user: user, | ||||
| 		managed: false, | ||||
| 		animated: false, // TODO: Add support animated emojis
 | ||||
| 		available: true, | ||||
| 		roles: [] | ||||
| 	}).save(); | ||||
| 
 | ||||
| 	await emitEvent({ | ||||
| 		event: "GUILD_EMOJIS_UPDATE", | ||||
| 		guild_id: guild_id, | ||||
| 		data: { | ||||
| 			guild_id: guild_id, | ||||
| 			emojis: await Emoji.find({ guild_id: guild_id }) | ||||
| 		} | ||||
| 	} as GuildEmojisUpdateEvent); | ||||
| 
 | ||||
| 	return res.status(201).json(emoji); | ||||
| }); | ||||
| 
 | ||||
| router.patch( | ||||
| 	"/:emoji_id", | ||||
| 	route({ body: "EmojiModifySchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), | ||||
| 	async (req: Request, res: Response) => { | ||||
| 		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(); | ||||
| 
 | ||||
| 		await emitEvent({ | ||||
| 			event: "GUILD_EMOJIS_UPDATE", | ||||
| 			guild_id: guild_id, | ||||
| 			data: { | ||||
| 				guild_id: guild_id, | ||||
| 				emojis: await Emoji.find({ guild_id: guild_id }) | ||||
| 			} | ||||
| 		} as GuildEmojisUpdateEvent); | ||||
| 
 | ||||
| 		return res.json(emoji); | ||||
| 	} | ||||
| ); | ||||
| 
 | ||||
| router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => { | ||||
| 	const { emoji_id, guild_id } = req.params; | ||||
| 
 | ||||
| 	await Emoji.delete({ | ||||
| 		id: emoji_id, | ||||
| 		guild_id: guild_id | ||||
| 	}); | ||||
| 
 | ||||
| 	await emitEvent({ | ||||
| 		event: "GUILD_EMOJIS_UPDATE", | ||||
| 		guild_id: guild_id, | ||||
| 		data: { | ||||
| 			guild_id: guild_id, | ||||
| 			emojis: await Emoji.find({ guild_id: guild_id }) | ||||
| 		} | ||||
| 	} as GuildEmojisUpdateEvent); | ||||
| 
 | ||||
| 	res.sendStatus(204); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
| @ -17,7 +17,7 @@ const router: Router = Router(); | ||||
| 
 | ||||
| export interface RoleModifySchema { | ||||
| 	name?: string; | ||||
| 	permissions?: bigint; | ||||
| 	permissions?: string; | ||||
| 	color?: number; | ||||
| 	hoist?: boolean; // whether the role should be displayed separately in the sidebar
 | ||||
| 	mentionable?: boolean; // whether the role should be mentionable
 | ||||
| @ -57,7 +57,7 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }) | ||||
| 		...body, | ||||
| 		guild_id: guild_id, | ||||
| 		managed: false, | ||||
| 		permissions: String(req.permission!.bitfield & (body.permissions || 0n)), | ||||
| 		permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")), | ||||
| 		tags: undefined | ||||
| 	}); | ||||
| 
 | ||||
| @ -105,7 +105,12 @@ router.patch("/:role_id", route({ body: "RoleModifySchema", permission: "MANAGE_ | ||||
| 	const { role_id, guild_id } = req.params; | ||||
| 	const body = req.body as RoleModifySchema; | ||||
| 
 | ||||
| 	const role = new Role({ ...body, id: role_id, guild_id, permissions: String(req.permission!.bitfield & (body.permissions || 0n)) }); | ||||
| 	const role = new Role({ | ||||
| 		...body, | ||||
| 		id: role_id, | ||||
| 		guild_id, | ||||
| 		permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")) | ||||
| 	}); | ||||
| 
 | ||||
| 	await Promise.all([ | ||||
| 		role.save(), | ||||
|  | ||||
| @ -10,10 +10,10 @@ const InviteRegex = /\W/g; | ||||
| router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 
 | ||||
| 	const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["vanity_url"] }); | ||||
| 	if (!guild.vanity_url) return res.json({ code: null }); | ||||
| 	const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); | ||||
| 	if (!invite) return res.json({ code: null }); | ||||
| 
 | ||||
| 	return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses }); | ||||
| 	return res.json({ code: invite.code, uses: invite.uses }); | ||||
| }); | ||||
| 
 | ||||
| export interface VanityUrlSchema { | ||||
| @ -33,20 +33,9 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }) | ||||
| 	const invite = await Invite.findOne({ code }); | ||||
| 	if (invite) throw new HTTPError("Invite already exists"); | ||||
| 
 | ||||
| 	const guild = await Guild.findOneOrFail({ id: guild_id }); | ||||
| 	const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); | ||||
| 
 | ||||
| 	Promise.all([ | ||||
| 		Guild.update({ id: guild_id }, { vanity_url_code: code }), | ||||
| 		Invite.delete({ code: guild.vanity_url_code }), | ||||
| 		new Invite({ | ||||
| 			code: code, | ||||
| 			uses: 0, | ||||
| 			created_at: new Date(), | ||||
| 			guild_id, | ||||
| 			channel_id: id | ||||
| 		}).save() | ||||
| 	]); | ||||
| 	await Invite.update({ vanity_url: true, guild_id }, { code: code, channel_id: id }); | ||||
| 
 | ||||
| 	return res.json({ code: code }); | ||||
| }); | ||||
|  | ||||
| @ -47,7 +47,7 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: | ||||
| 			managed: true, | ||||
| 			mentionable: true, | ||||
| 			name: "@everyone", | ||||
| 			permissions: 2251804225n, | ||||
| 			permissions: BigInt("2251804225"), | ||||
| 			position: 0, | ||||
| 			tags: null | ||||
| 		}).save() | ||||
|  | ||||
| @ -33,7 +33,6 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => { | ||||
| 
 | ||||
| 	await Promise.all([ | ||||
| 		Invite.delete({ code }), | ||||
| 		Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined }), | ||||
| 		emitEvent({ | ||||
| 			event: "INVITE_DELETE", | ||||
| 			guild_id: guild_id, | ||||
|  | ||||
| @ -1,37 +0,0 @@ | ||||
| const jwa = require("jwa"); | ||||
| 
 | ||||
| var STR64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".split(""); | ||||
| 
 | ||||
| function base64url(string: string, encoding: string) { | ||||
| 	// @ts-ignore
 | ||||
| 	return Buffer.from(string, encoding).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); | ||||
| } | ||||
| 
 | ||||
| function to64String(input: number, current = ""): string { | ||||
| 	if (input < 0 && current.length == 0) { | ||||
| 		input = input * -1; | ||||
| 	} | ||||
| 	var modify = input % 64; | ||||
| 	var remain = Math.floor(input / 64); | ||||
| 	var result = STR64[modify] + current; | ||||
| 	return remain <= 0 ? result : to64String(remain, result); | ||||
| } | ||||
| 
 | ||||
| function to64Parse(input: string) { | ||||
| 	var result = 0; | ||||
| 	var toProc = input.split(""); | ||||
| 	var e; | ||||
| 	for (e in toProc) { | ||||
| 		result = result * 64 + STR64.indexOf(toProc[e]); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| // @ts-ignore
 | ||||
| const start = `${base64url("311129357362135041")}.${to64String(Date.now())}`; | ||||
| const signature = jwa("HS256").sign(start, `test`); | ||||
| const token = `${start}.${signature}`; | ||||
| console.log(token); | ||||
| 
 | ||||
| // MzExMTI5MzU3MzYyMTM1MDQx.XdQb_rA.907VgF60kocnOTl32MSUWGSSzbAytQ0jbt36KjLaxuY
 | ||||
| // MzExMTI5MzU3MzYyMTM1MDQx.XdQbaPy.4vGx4L7IuFJGsRe6IL3BeybLIvbx4Vauvx12pwNsy2U
 | ||||
| @ -1,13 +0,0 @@ | ||||
| import jwt from "jsonwebtoken"; | ||||
| 
 | ||||
| const algorithm = "HS256"; | ||||
| const iat = Math.floor(Date.now() / 1000); | ||||
| 
 | ||||
| // @ts-ignore
 | ||||
| const token = jwt.sign({ id: "311129357362135041" }, "secret", { | ||||
| 	algorithm, | ||||
| }); | ||||
| console.log(token); | ||||
| 
 | ||||
| const decoded = jwt.verify(token, "secret", { algorithms: [algorithm] }); | ||||
| console.log(decoded); | ||||
| @ -1,12 +0,0 @@ | ||||
| import { checkPassword } from "@fosscord/api"; | ||||
| 
 | ||||
| console.log(checkPassword("123456789012345")); | ||||
| // -> 0.25
 | ||||
| console.log(checkPassword("ABCDEFGHIJKLMOPQ")); | ||||
| // -> 0.25
 | ||||
| console.log(checkPassword("ABC123___...123")); | ||||
| // ->
 | ||||
| console.log(checkPassword("")); | ||||
| // ->
 | ||||
| // console.log(checkPassword(""));
 | ||||
| // // ->
 | ||||
| @ -8,11 +8,11 @@ export async function initInstance() { | ||||
| 	// TODO: check if any current user is not part of autoJoinGuilds
 | ||||
| 	const { autoJoin } = Config.get().guild; | ||||
| 
 | ||||
| 	if (autoJoin.enabled && autoJoin.guilds?.length) { | ||||
| 	if (autoJoin.enabled && !autoJoin.guilds?.length) { | ||||
| 		let guild = await Guild.findOne({}); | ||||
| 		if (!guild) guild = await Guild.createGuild({}); | ||||
| 
 | ||||
| 		// @ts-ignore
 | ||||
| 		await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); | ||||
| 		if (guild) { | ||||
| 			// @ts-ignore
 | ||||
| 			await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -25,6 +25,7 @@ import cheerio from "cheerio"; | ||||
| import { MessageCreateSchema } from "../routes/channels/#channel_id/messages"; | ||||
| 
 | ||||
| // TODO: check webhook, application, system author
 | ||||
| // TODO: embed gifs/videos/images
 | ||||
| 
 | ||||
| const LINK_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; | ||||
| 
 | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| { | ||||
| 	"exclude": ["node_modules"], | ||||
| 	"include": ["src/**/*.ts"], | ||||
| 	"compilerOptions": { | ||||
| 		/* Visit https://aka.ms/tsconfig.json to read more about this file */ | ||||
| 
 | ||||
| 		/* Basic Options */ | ||||
| 		// "incremental": true,                   /* Enable incremental compilation */ | ||||
| 		"incremental": true /* Enable incremental compilation */, | ||||
| 		"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, | ||||
| 		"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, | ||||
| 		"lib": ["ES2021"] /* Specify library files to be included in the compilation. */, | ||||
| @ -69,6 +70,7 @@ | ||||
| 			"@fosscord/api": ["src/index"], | ||||
| 			"@fosscord/api/*": ["src/*"] | ||||
| 		}, | ||||
| 		"plugins": [{ "transform": "@zerollup/ts-transform-paths" }] | ||||
| 		"plugins": [{ "transform": "@zerollup/ts-transform-paths" }], | ||||
| 		"experimentalDecorators": true | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										2
									
								
								bundle/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								bundle/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +0,0 @@ | ||||
| files/ | ||||
| .env | ||||
							
								
								
									
										10
									
								
								bundle/.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								bundle/.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @ -8,13 +8,11 @@ | ||||
| 			"sourceMaps": true, | ||||
| 			"type": "node", | ||||
| 			"request": "launch", | ||||
| 			"name": "Launch server bundle", | ||||
| 			"program": "${workspaceFolder}/dist/start.js", | ||||
| 			"runtimeArgs": ["-r", "./tsconfig-paths-bootstrap.js"], | ||||
| 			"name": "Launch Server", | ||||
| 			"program": "${workspaceFolder}/dist/bundle/src/start.js", | ||||
| 			"preLaunchTask": "tsc: build - tsconfig.json", | ||||
| 			"outFiles": ["${workspaceFolder}/dist/**/*.js", "${workspaceFolder}/node_modules/@fosscord/**/*.js"], | ||||
| 			"envFile": "${workspaceFolder}/.env", | ||||
| 			"outDir": "${workspaceFolder}/dist" | ||||
| 			"outFiles": ["${workspaceFolder}/dist/**/*.js"], | ||||
| 			"envFile": "${workspaceFolder}/.env" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
|  | ||||
							
								
								
									
										17144
									
								
								bundle/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17144
									
								
								bundle/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -4,12 +4,12 @@ | ||||
| 	"description": "", | ||||
| 	"main": "src/start.js", | ||||
| 	"scripts": { | ||||
| 		"setup": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i && cd ../bundle/ && npm --production=false i && npm run build", | ||||
| 		"setup": "node scripts/install.js && npm install && ts-patch install -s && patch-package --patch-dir ../api/patches/ && npm run build", | ||||
| 		"build": "node scripts/build.js", | ||||
| 		"build:bundle": "npx tsc -b .", | ||||
| 		"start": "node scripts/build.js && node -r tsconfig-paths/register dist/start.js", | ||||
| 		"start:bundle": "node -r tsconfig-paths/register dist/start.js", | ||||
| 		"test": "echo \"Error: no test specified\" && exit 1" | ||||
| 		"start": "node scripts/build.js && node dist/bundle/src/start.js", | ||||
| 		"start:bundle": "node 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" | ||||
| 	}, | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| @ -23,42 +23,76 @@ | ||||
| 	}, | ||||
| 	"homepage": "https://fosscord.com", | ||||
| 	"devDependencies": { | ||||
| 		"@swc/cli": "^0.1.51", | ||||
| 		"@swc/core": "^1.2.93", | ||||
| 		"@babel/core": "^7.15.5", | ||||
| 		"@babel/preset-env": "^7.15.6", | ||||
| 		"@babel/preset-typescript": "^7.15.0", | ||||
| 		"@types/amqplib": "^0.8.1", | ||||
| 		"@types/async-exit-hook": "^2.0.0", | ||||
| 		"@types/bcrypt": "^5.0.0", | ||||
| 		"@types/express": "^4.17.9", | ||||
| 		"@types/body-parser": "^1.19.0", | ||||
| 		"@types/btoa": "^1.2.3", | ||||
| 		"@types/dotenv": "^8.2.0", | ||||
| 		"@types/express": "^4.17.12", | ||||
| 		"@types/fs-extra": "^9.0.12", | ||||
| 		"@types/i18next-node-fs-backend": "^2.1.0", | ||||
| 		"@types/jest": "^27.0.1", | ||||
| 		"@types/jest-expect-message": "^1.0.3", | ||||
| 		"@types/jsonwebtoken": "^8.5.0", | ||||
| 		"@types/mongodb": "^3.6.9", | ||||
| 		"@types/mongoose-autopopulate": "^0.10.1", | ||||
| 		"@types/mongoose-lean-virtuals": "^0.5.1", | ||||
| 		"@types/multer": "^1.4.5", | ||||
| 		"@types/node": "^14.17.20", | ||||
| 		"@types/node-fetch": "^2.5.7", | ||||
| 		"@types/multer": "^1.4.7", | ||||
| 		"@types/node": "^14.17.9", | ||||
| 		"@types/node-fetch": "^2.5.12", | ||||
| 		"@types/node-os-utils": "^1.2.0", | ||||
| 		"@types/uuid": "^8.3.0", | ||||
| 		"@types/supertest": "^2.0.11", | ||||
| 		"@types/ws": "^7.4.0", | ||||
| 		"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 		"esbuild": "^0.13.4", | ||||
| 		"esbuild-plugin-tsc": "^0.3.0", | ||||
| 		"ts-node": "^10.2.1", | ||||
| 		"jest": "^27.0.6", | ||||
| 		"jest-expect-message": "^1.0.2", | ||||
| 		"jest-runtime": "^27.2.1", | ||||
| 		"ts-node": "^9.1.1", | ||||
| 		"ts-node-dev": "^1.1.6", | ||||
| 		"ts-patch": "^1.4.4", | ||||
| 		"tsconfig-paths": "^3.11.0", | ||||
| 		"typescript": "^4.4.3" | ||||
| 		"typescript": "^4.2.3", | ||||
| 		"typescript-json-schema": "0.50.1", | ||||
| 		"@types/morgan": "^1.9.3" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"@fosscord/api": "file:../api", | ||||
| 		"@fosscord/cdn": "file:../cdn", | ||||
| 		"@fosscord/gateway": "file:../gateway", | ||||
| 		"@fosscord/util": "file:../util", | ||||
| 		"ajv": "8.6.2", | ||||
| 		"ajv-formats": "^2.1.1", | ||||
| 		"amqplib": "^0.8.0", | ||||
| 		"assert": "^1.5.0", | ||||
| 		"async-exit-hook": "^2.0.1", | ||||
| 		"dotenv": "^10.0.0", | ||||
| 		"bcrypt": "^5.0.1", | ||||
| 		"body-parser": "^1.19.0", | ||||
| 		"btoa": "^1.2.1", | ||||
| 		"dotenv": "^8.2.0", | ||||
| 		"exif-be-gone": "^1.2.0", | ||||
| 		"express": "^4.17.1", | ||||
| 		"express-async-errors": "^3.1.1", | ||||
| 		"file-type": "^16.5.0", | ||||
| 		"form-data": "^3.0.0", | ||||
| 		"fs-extra": "^10.0.0", | ||||
| 		"i18next": "^19.9.2", | ||||
| 		"i18next-http-middleware": "^3.1.3", | ||||
| 		"i18next-node-fs-backend": "^2.1.3", | ||||
| 		"image-size": "^1.0.0", | ||||
| 		"jest": "^27.0.6", | ||||
| 		"jsonwebtoken": "^8.5.1", | ||||
| 		"lambert-db": "^1.2.3", | ||||
| 		"lambert-server": "^1.2.11", | ||||
| 		"missing-native-js-functions": "^1.2.17", | ||||
| 		"morgan": "^1.10.0", | ||||
| 		"multer": "^1.4.2", | ||||
| 		"nanocolors": "^0.2.12", | ||||
| 		"node-fetch": "^2.6.1", | ||||
| 		"node-os-utils": "^1.3.5", | ||||
| 		"reflect-metadata": "^0.1.13" | ||||
| 		"patch-package": "^6.4.7", | ||||
| 		"pg": "^8.7.1", | ||||
| 		"reflect-metadata": "^0.1.13", | ||||
| 		"sqlite3": "^5.0.2", | ||||
| 		"supertest": "^6.1.6", | ||||
| 		"typeorm": "^0.2.37", | ||||
| 		"typescript": "^4.1.2", | ||||
| 		"typescript-json-schema": "^0.50.1", | ||||
| 		"ws": "^7.4.2", | ||||
| 		"cheerio": "^1.0.0-rc.10" | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										58
									
								
								bundle/scripts/benchmark/connections.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								bundle/scripts/benchmark/connections.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| const cluster = require("cluster"); | ||||
| const WebSocket = require("ws"); | ||||
| const endpoint = process.env.GATEWAY || "ws://localhost:3001"; | ||||
| const connections = Number(process.env.CONNECTIONS) || 50; | ||||
| const threads = Number(process.env.THREADS) || require("os").cpus().length || 1; | ||||
| const token = process.env.TOKEN; | ||||
| 
 | ||||
| if (!token) { | ||||
| 	console.error("TOKEN env var missing"); | ||||
| 	process.exit(); | ||||
| } | ||||
| 
 | ||||
| if (cluster.isMaster) { | ||||
| 	for (let i = 0; i < threads; i++) { | ||||
| 		cluster.fork(); | ||||
| 	} | ||||
| 
 | ||||
| 	cluster.on("exit", (worker, code, signal) => { | ||||
| 		console.log(`worker ${worker.process.pid} died`); | ||||
| 	}); | ||||
| } else { | ||||
| 	for (let i = 0; i < connections; i++) { | ||||
| 		connect(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function connect() { | ||||
| 	const client = new WebSocket(endpoint); | ||||
| 	client.on("message", (data) => { | ||||
| 		data = JSON.parse(data); | ||||
| 
 | ||||
| 		switch (data.op) { | ||||
| 			case 10: | ||||
| 				client.interval = setInterval(() => { | ||||
| 					client.send(JSON.stringify({ op: 1 })); | ||||
| 				}, data.d.heartbeat_interval); | ||||
| 
 | ||||
| 				client.send( | ||||
| 					JSON.stringify({ | ||||
| 						op: 2, | ||||
| 						d: { | ||||
| 							token, | ||||
| 							properties: {}, | ||||
| 						}, | ||||
| 					}) | ||||
| 				); | ||||
| 
 | ||||
| 				break; | ||||
| 		} | ||||
| 	}); | ||||
| 	client.once("close", (code, reason) => { | ||||
| 		clearInterval(client.interval); | ||||
| 		connect(); | ||||
| 	}); | ||||
| 	client.on("error", (err) => { | ||||
| 		// console.log(err);
 | ||||
| 	}); | ||||
| } | ||||
							
								
								
									
										4
									
								
								bundle/scripts/benchmark/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								bundle/scripts/benchmark/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| require("dotenv").config(); | ||||
| 
 | ||||
| require("./connections"); | ||||
| require("./messages"); | ||||
							
								
								
									
										1
									
								
								bundle/scripts/benchmark/messages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bundle/scripts/benchmark/messages.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| // TODO
 | ||||
| @ -1,103 +1,49 @@ | ||||
| const { spawn } = require("child_process"); | ||||
| const { execSync } = require("child_process"); | ||||
| const path = require("path"); | ||||
| const fs = require("fs"); | ||||
| const { performance } = require("perf_hooks"); | ||||
| const fse = require("fs-extra"); | ||||
| const { getSystemErrorMap } = require("util"); | ||||
| const { argv } = require("process"); | ||||
| 
 | ||||
| let parts = "api,cdn,gateway,bundle".split(","); | ||||
| const tscBin = path.join(__dirname, "..", "..", "util", "node_modules", "typescript", "bin", "tsc"); | ||||
| const swcBin = path.join(__dirname, "..", "..", "util", "node_modules", "@swc", "cli", "bin", "swc"); | ||||
| const dirs = ["api", "util", "cdn", "gateway", "bundle"]; | ||||
| 
 | ||||
| // because npm run is slow we directly get the build script of the package.json script
 | ||||
| const verbose = argv.includes("verbose") || argv.includes("v"); | ||||
| 
 | ||||
| function buildPackage(dir) { | ||||
| 	const element = path.basename(dir); | ||||
| 
 | ||||
| 	return require("esbuild").build({ | ||||
| 		entryPoints: walk(path.join(dir, "src")), | ||||
| 		bundle: false, | ||||
| 		outdir: path.join(dir, "dist"), | ||||
| 		target: "es2021", | ||||
| 		// plugins don't really work because bundle is false
 | ||||
| 		keepNames: false, | ||||
| 		tsconfig: path.join(dir, "tsconfig.json"), | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| const importPart = /import (\* as )?(({[^}]+})|(\w+)) from ("[.\w-/@q]+")/g; | ||||
| const importMod = /import ("[\w-/@q.]+")/g; | ||||
| const exportDefault = /export default/g; | ||||
| const exportAllAs = /export \* from (".+")/g; | ||||
| const exportMod = /export ({[\w, ]+})/g; | ||||
| const exportConst = /export (const|var|let) (\w+)/g; | ||||
| const exportPart = /export ((async )?\w+) (\w+)/g; | ||||
| 
 | ||||
| // resolves tsconfig paths + rewrites es6 imports/exports to require (because esbuild/swc doesn't work properly)
 | ||||
| function transpileFiles() { | ||||
| 	for (const part of ["gateway", "api", "cdn", "bundle"]) { | ||||
| 		const files = walk(path.join(__dirname, "..", "..", part, "dist")); | ||||
| 		for (const file of files) { | ||||
| 			let content = fs.readFileSync(file, { encoding: "utf8" }); | ||||
| 			content = content | ||||
| 				.replace( | ||||
| 					new RegExp(`@fosscord/${part}`), | ||||
| 					path.relative(file, path.join(__dirname, "..", "..", part, "dist")).slice(3) | ||||
| 				) | ||||
| 				.replace(importPart, `const $2 = require($5)`) | ||||
| 				.replace(importMod, `require($1)`) | ||||
| 				.replace(exportDefault, `module.exports =`) | ||||
| 				.replace(exportAllAs, `module.exports = {...(module.exports)||{}, ...require($1)}`) | ||||
| 				.replace(exportMod, "module.exports = $1") | ||||
| 				.replace(exportConst, `let $2 = {};\nmodule.exports.$2 = $2`) | ||||
| 				.replace(exportPart, `module.exports.$3 = $1 $3`); | ||||
| 			fs.writeFileSync(file, content); | ||||
| if (argv.includes("clean")) { | ||||
| 	dirs.forEach((a) => { | ||||
| 		var d = "../" + a + "/dist"; | ||||
| 		if (fse.existsSync(d)) { | ||||
| 			fse.rmSync(d, { recursive: true }); | ||||
| 			if (verbose) console.log(`Deleted ${d}!`); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function util() { | ||||
| 	// const child = spawn("node", `${swcBin}  src --out-dir dist --sync`.split(" "), {
 | ||||
| 	const child = spawn("node", `\"${tscBin}\" -b .`.split(" "), { | ||||
| 		cwd: path.join(__dirname, "..", "..", "util"), | ||||
| 		env: process.env, | ||||
| 		shell: true, | ||||
| 	}); | ||||
| 	function log(data) { | ||||
| 		console.log(`[util] ` + data.toString().slice(0, -1)); | ||||
| 	} | ||||
| 	child.stdout.on("data", log); | ||||
| 	child.stderr.on("data", log); | ||||
| 	child.on("error", (err) => console.error("util", err)); | ||||
| 	return child; | ||||
| } | ||||
| 
 | ||||
| const start = performance.now(); | ||||
| 
 | ||||
| async function main() { | ||||
| 	console.log("[Build] starting ..."); | ||||
| 	util(); | ||||
| 	await Promise.all(parts.map((part) => buildPackage(path.join(__dirname, "..", "..", part)))); | ||||
| 	transpileFiles(); | ||||
| } | ||||
| 
 | ||||
| main(); | ||||
| 
 | ||||
| process.on("exit", () => { | ||||
| 	console.log("[Build] took " + Math.round(performance.now() - start) + "ms"); | ||||
| fse.copySync(path.join(__dirname, "..", "..", "api", "assets"), path.join(__dirname, "..", "dist", "api", "assets")); | ||||
| fse.copySync( | ||||
| 	path.join(__dirname, "..", "..", "api", "client_test"), | ||||
| 	path.join(__dirname, "..", "dist", "api", "client_test") | ||||
| ); | ||||
| fse.copySync(path.join(__dirname, "..", "..", "api", "locales"), path.join(__dirname, "..", "dist", "api", "locales")); | ||||
| dirs.forEach((a) => { | ||||
| 	fse.copySync("../" + a + "/src", "dist/" + a + "/src"); | ||||
| 	if (verbose) console.log(`Copied ${"../" + a + "/dist"} -> ${"dist/" + a + "/src"}!`); | ||||
| }); | ||||
| 
 | ||||
| function walk(dir) { | ||||
| 	var results = []; | ||||
| 	var list = fs.readdirSync(dir); | ||||
| 	list.forEach(function (file) { | ||||
| 		file = dir + "/" + file; | ||||
| 		var stat = fs.statSync(file); | ||||
| 		if (stat && stat.isDirectory()) { | ||||
| 			/* Recurse into a subdirectory */ | ||||
| 			results = results.concat(walk(file)); | ||||
| 		} else if (file.endsWith(".ts") || file.endsWith(".js")) { | ||||
| 			/* Is a file */ | ||||
| 			results.push(file); | ||||
| console.log("Copying src files done"); | ||||
| console.log("Compiling src files ..."); | ||||
| 
 | ||||
| console.log( | ||||
| 	execSync( | ||||
| 		'node "' + | ||||
| 			path.join(__dirname, "..", "node_modules", "typescript", "lib", "tsc.js") + | ||||
| 			'" -p "' + | ||||
| 			path.join(__dirname, "..") + | ||||
| 			'"', | ||||
| 		{ | ||||
| 			cwd: path.join(__dirname, ".."), | ||||
| 			shell: true, | ||||
| 			env: process.env, | ||||
| 			encoding: "utf8", | ||||
| 		} | ||||
| 	}); | ||||
| 	return results; | ||||
| } | ||||
| 	) | ||||
| ); | ||||
|  | ||||
							
								
								
									
										14
									
								
								bundle/scripts/install.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								bundle/scripts/install.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| const path = require("path"); | ||||
| const fs = require("fs"); | ||||
| const parts = ["api", "util", "cdn", "gateway"]; | ||||
| 
 | ||||
| const bundle = require("../package.json"); | ||||
| 
 | ||||
| for (const part of parts) { | ||||
| 	const { devDependencies, dependencies } = require(path.join("..", "..", part, "package.json")); | ||||
| 	bundle.devDependencies = { ...bundle.devDependencies, ...devDependencies }; | ||||
| 	bundle.dependencies = { ...bundle.dependencies, ...dependencies }; | ||||
| 	delete bundle.dependencies["@fosscord/util"]; | ||||
| } | ||||
| 
 | ||||
| fs.writeFileSync(path.join(__dirname, "..", "package.json"), JSON.stringify(bundle, null, "\t"), { encoding: "utf8" }); | ||||
| @ -4,7 +4,7 @@ process.on("uncaughtException", console.error); | ||||
| import http from "http"; | ||||
| import * as Api from "@fosscord/api"; | ||||
| import * as Gateway from "@fosscord/gateway"; | ||||
| import { CDNServer } from "@fosscord/cdn/"; | ||||
| import { CDNServer } from "@fosscord/cdn"; | ||||
| import express from "express"; | ||||
| import { green, bold } from "nanocolors"; | ||||
| import { Config, initDatabase } from "@fosscord/util"; | ||||
| @ -30,9 +30,6 @@ async function main() { | ||||
| 		cdn: { | ||||
| 			endpointClient: "${location.host}", | ||||
| 			endpointPrivate: `http://localhost:${port}`, | ||||
| 			...(!Config.get().cdn.endpointPublic && { | ||||
| 				endpointPublic: `http://localhost:${port}`, | ||||
| 			}), | ||||
| 		}, | ||||
| 		gateway: { | ||||
| 			endpointClient: | ||||
|  | ||||
| @ -1,20 +1,4 @@ | ||||
| // process.env.MONGOMS_DEBUG = "true";
 | ||||
| const tsConfigPaths = require("tsconfig-paths"); | ||||
| const path = require("path"); | ||||
| const baseUrl = path.join(__dirname, ".."); | ||||
| const cleanup = tsConfigPaths.register({ | ||||
| 	baseUrl, | ||||
| 	paths: { | ||||
| 		"@fosscord/api": ["../api/dist/index.js"], | ||||
| 		"@fosscord/api/*": ["../api/dist/*"], | ||||
| 		"@fosscord/gateway": ["../gateway/dist/index.js"], | ||||
| 		"@fosscord/gateway/*": ["../gateway/dist/*"], | ||||
| 		"@fosscord/cdn": ["../cdn/dist/index.js"], | ||||
| 		"@fosscord/cdn/*": ["../cdn/dist/*"], | ||||
| 	}, | ||||
| }); | ||||
| console.log(require("@fosscord/gateway")); | ||||
| 
 | ||||
| import "reflect-metadata"; | ||||
| import cluster from "cluster"; | ||||
| import os from "os"; | ||||
|  | ||||
| @ -1,11 +1,19 @@ | ||||
| import os from "os"; | ||||
| import osu from "node-os-utils"; | ||||
| import { red } from "nanocolors"; | ||||
| 
 | ||||
| export function initStats() { | ||||
| 	console.log(`[Path] running in ${__dirname}`); | ||||
| 	console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`); | ||||
| 	console.log(`[System] ${os.platform()} ${os.arch()}`); | ||||
| 	console.log(`[Process] running with pid: ${process.pid}`); | ||||
| 	if (process.getuid && process.getuid() === 0) { | ||||
| 		console.warn( | ||||
| 			red( | ||||
| 				`[Process] Warning fosscord is running as root, this highly discouraged and might expose your system vulnerable to attackers. Please run fosscord as a user without root privileges.` | ||||
| 			) | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	setInterval(async () => { | ||||
| 		const [cpuUsed, memory, network] = await Promise.all([ | ||||
| @ -23,5 +31,6 @@ export function initStats() { | ||||
| 				process.memoryUsage().rss / 1024 / 1024 | ||||
| 			)}mb/${memory.totalMemMb.toFixed(0)}mb ${networkUsage}` | ||||
| 		); | ||||
| 	}, 1000 * 5); | ||||
| 		// TODO: node-os-utils might have a memory leak, more investigation needed
 | ||||
| 	}, 1000 * 60 * 5); | ||||
| } | ||||
|  | ||||
| @ -1,22 +1,23 @@ | ||||
| { | ||||
| 	"include": ["src/**/*.ts"], | ||||
| 	"include": ["dist/**/*.ts"], | ||||
| 	"exclude": [], | ||||
| 	"compilerOptions": { | ||||
| 		/* Visit https://aka.ms/tsconfig.json to read more about this file */ | ||||
| 
 | ||||
| 		/* Basic Options */ | ||||
| 		"incremental": true /* Enable incremental compilation */, | ||||
| 		"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, | ||||
| 		"incremental": false /* Enable incremental compilation */, | ||||
| 		"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, | ||||
| 		"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, | ||||
| 		"lib": ["ES2021"] /* Specify library files to be included in the compilation. */, | ||||
| 		"allowJs": true /* Allow javascript files to be compiled. */, | ||||
| 		"checkJs": true /* Report errors in .js files. */, | ||||
| 		// "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ | ||||
| 		"declaration": true /* Generates corresponding '.d.ts' file. */, | ||||
| 		"declaration": false /* Generates corresponding '.d.ts' file. */, | ||||
| 		"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, | ||||
| 		"sourceMap": true /* Generates corresponding '.map' file. */, | ||||
| 		"sourceMap": false /* 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. */, | ||||
| 		"rootDir": "./dist/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, | ||||
| 		// "composite": true,                     /* Enable project compilation */ | ||||
| 		// "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */ | ||||
| 		// "removeComments": true,                /* Do not emit comments to output. */ | ||||
| @ -66,6 +67,14 @@ | ||||
| 		"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, | ||||
| 		"emitDecoratorMetadata": true, | ||||
| 		"experimentalDecorators": true, | ||||
| 		"baseUrl": "." | ||||
| 		"resolveJsonModule": true, | ||||
| 		"baseUrl": "./dist/", | ||||
| 		"paths": { | ||||
| 			"@fosscord/api": ["api/src/index"], | ||||
| 			"@fosscord/gateway": ["gateway/src/index"], | ||||
| 			"@fosscord/cdn": ["cdn/src/index"], | ||||
| 			"@fosscord/util": ["util/src/index"] | ||||
| 		}, | ||||
| 		"plugins": [{ "transform": "@zerollup/ts-transform-paths" }] | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										1065
									
								
								cdn/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1065
									
								
								cdn/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -7,7 +7,7 @@ | ||||
| 	"scripts": { | ||||
| 		"postinstall": "ts-patch install -s", | ||||
| 		"test": "npm run build && jest --coverage ./tests", | ||||
| 		"build": "npx tsc -b .", | ||||
| 		"build": "npx tsc -p .", | ||||
| 		"start": "npm run build && node dist/start.js" | ||||
| 	}, | ||||
| 	"repository": { | ||||
| @ -22,8 +22,6 @@ | ||||
| 	}, | ||||
| 	"homepage": "https://github.com/fosscord/fosscord-server#readme", | ||||
| 	"devDependencies": { | ||||
| 		"@swc/cli": "^0.1.51", | ||||
| 		"@swc/core": "^1.2.93", | ||||
| 		"@types/amqplib": "^0.8.1", | ||||
| 		"@types/body-parser": "^1.19.0", | ||||
| 		"@types/btoa": "^1.2.3", | ||||
| @ -31,13 +29,9 @@ | ||||
| 		"@types/express": "^4.17.12", | ||||
| 		"@types/fs-extra": "^9.0.12", | ||||
| 		"@types/jsonwebtoken": "^8.5.0", | ||||
| 		"@types/mongodb": "^3.6.9", | ||||
| 		"@types/mongoose-autopopulate": "^0.10.1", | ||||
| 		"@types/mongoose-lean-virtuals": "^0.5.1", | ||||
| 		"@types/multer": "^1.4.7", | ||||
| 		"@types/node": "^14.17.0", | ||||
| 		"@types/node-fetch": "^2.5.7", | ||||
| 		"@types/uuid": "^8.3.0", | ||||
| 		"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 		"ts-patch": "^1.4.4" | ||||
| 	}, | ||||
| @ -45,7 +39,6 @@ | ||||
| 		"@fosscord/util": "file:../util", | ||||
| 		"body-parser": "^1.19.0", | ||||
| 		"btoa": "^1.2.1", | ||||
| 		"cheerio": "^1.0.0-rc.5", | ||||
| 		"dotenv": "^10.0.0", | ||||
| 		"exif-be-gone": "^1.2.0", | ||||
| 		"express": "^4.17.1", | ||||
| @ -61,8 +54,7 @@ | ||||
| 		"nanocolors": "^0.2.12", | ||||
| 		"node-fetch": "^2.6.1", | ||||
| 		"supertest": "^6.1.6", | ||||
| 		"typescript": "^4.1.2", | ||||
| 		"uuid": "^8.3.2" | ||||
| 		"typescript": "^4.1.2" | ||||
| 	}, | ||||
| 	"jest": { | ||||
| 		"setupFilesAfterEnv": [ | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { Server, ServerOptions } from "lambert-server"; | ||||
| import { Config, initDatabase } from "@fosscord/util"; | ||||
| import { Config, initDatabase, registerRoutes } from "@fosscord/util"; | ||||
| import path from "path"; | ||||
| import avatarsRoute from "./routes/avatars"; | ||||
| import bodyParser from "body-parser"; | ||||
| @ -23,13 +23,19 @@ export class CDNServer extends Server { | ||||
| 				"Content-security-policy", | ||||
| 				"default-src *  data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';" | ||||
| 			); | ||||
| 			res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); | ||||
| 			res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); | ||||
| 			res.set( | ||||
| 				"Access-Control-Allow-Headers", | ||||
| 				req.header("Access-Control-Request-Headers") || "*" | ||||
| 			); | ||||
| 			res.set( | ||||
| 				"Access-Control-Allow-Methods", | ||||
| 				req.header("Access-Control-Request-Methods") || "*" | ||||
| 			); | ||||
| 			next(); | ||||
| 		}); | ||||
| 		this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); | ||||
| 
 | ||||
| 		await this.registerRoutes(path.join(__dirname, "routes/")); | ||||
| 		await registerRoutes(this, path.join(__dirname, "routes/")); | ||||
| 
 | ||||
| 		this.app.use("/icons/", avatarsRoute); | ||||
| 		this.log("verbose", "[Server] Route /icons registered"); | ||||
|  | ||||
| @ -58,6 +58,21 @@ router.post( | ||||
| 	} | ||||
| ); | ||||
| 
 | ||||
| router.get("/:user_id", async (req: Request, res: Response) => { | ||||
| 	var { user_id } = req.params; | ||||
| 	user_id = user_id.split(".")[0]; // remove .file extension
 | ||||
| 	const path = `avatars/${user_id}`; | ||||
| 
 | ||||
| 	const file = await storage.get(path); | ||||
| 	if (!file) throw new HTTPError("not found", 404); | ||||
| 	const type = await FileType.fromBuffer(file); | ||||
| 
 | ||||
| 	res.set("Content-Type", type?.mime); | ||||
| 	res.set("Cache-Control", "public, max-age=31536000"); | ||||
| 
 | ||||
| 	return res.send(file); | ||||
| }); | ||||
| 
 | ||||
| router.get("/:user_id/:hash", async (req: Request, res: Response) => { | ||||
| 	var { user_id, hash } = req.params; | ||||
| 	hash = hash.split(".")[0]; // remove .file extension
 | ||||
|  | ||||
| @ -13,16 +13,24 @@ function getPath(path: string) { | ||||
| 	const root = process.env.STORAGE_LOCATION || "../"; | ||||
| 	var filename = join(root, path); | ||||
| 
 | ||||
| 	if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) throw new Error("invalid path"); | ||||
| 	if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) | ||||
| 		throw new Error("invalid path"); | ||||
| 	return filename; | ||||
| } | ||||
| 
 | ||||
| export class FileStorage implements Storage { | ||||
| 	async get(path: string): Promise<Buffer | null> { | ||||
| 		path = getPath(path); | ||||
| 		try { | ||||
| 			return fs.readFileSync(getPath(path)); | ||||
| 			return fs.readFileSync(path); | ||||
| 		} catch (error) { | ||||
| 			return null; | ||||
| 			try { | ||||
| 				const files = fs.readdirSync(path); | ||||
| 				if (!files.length) return null; | ||||
| 				return fs.readFileSync(join(path, files[0])); | ||||
| 			} catch (error) { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										6
									
								
								dashboard/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								dashboard/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|   "name": "dashboard", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": {} | ||||
| } | ||||
							
								
								
									
										1
									
								
								dashboard/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								dashboard/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| {} | ||||
							
								
								
									
										1454
									
								
								gateway/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1454
									
								
								gateway/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -8,23 +8,17 @@ | ||||
| 		"postinstall": "npx ts-patch install -s", | ||||
| 		"test": "echo \"Error: no test specified\" && exit 1", | ||||
| 		"start": "npm run build && node dist/start.js", | ||||
| 		"build": "npx tsc -b .", | ||||
| 		"build": "npx tsc -p .", | ||||
| 		"dev": "tsnd --respawn src/start.ts" | ||||
| 	}, | ||||
| 	"keywords": [], | ||||
| 	"author": "Fosscord", | ||||
| 	"license": "ISC", | ||||
| 	"devDependencies": { | ||||
| 		"@swc/cli": "^0.1.51", | ||||
| 		"@swc/core": "^1.2.93", | ||||
| 		"@types/amqplib": "^0.8.1", | ||||
| 		"@types/jsonwebtoken": "^8.5.0", | ||||
| 		"@types/mongodb": "^3.6.9", | ||||
| 		"@types/mongoose-autopopulate": "^0.10.1", | ||||
| 		"@types/mongoose-lean-virtuals": "^0.5.1", | ||||
| 		"@types/node": "^14.17.9", | ||||
| 		"@types/node-fetch": "^2.5.12", | ||||
| 		"@types/uuid": "^8.3.0", | ||||
| 		"@types/ws": "^7.4.0", | ||||
| 		"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 		"ts-node-dev": "^1.1.6", | ||||
| @ -33,16 +27,13 @@ | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"@fosscord/util": "file:../util", | ||||
| 		"ajv": "^8.5.0", | ||||
| 		"amqplib": "^0.8.0", | ||||
| 		"dotenv": "^8.2.0", | ||||
| 		"jsonwebtoken": "^8.5.1", | ||||
| 		"lambert-server": "^1.2.11", | ||||
| 		"missing-native-js-functions": "^1.2.17", | ||||
| 		"mongoose-autopopulate": "^0.12.3", | ||||
| 		"node-fetch": "^2.6.1", | ||||
| 		"typeorm": "^0.2.37", | ||||
| 		"uuid": "^8.3.2", | ||||
| 		"ws": "^7.4.2" | ||||
| 	}, | ||||
| 	"optionalDependencies": { | ||||
|  | ||||
| @ -32,7 +32,6 @@ export class Server { | ||||
| 		} | ||||
| 
 | ||||
| 		this.server.on("upgrade", (request, socket, head) => { | ||||
| 			console.log("socket requests upgrade", request.url); | ||||
| 			// @ts-ignore
 | ||||
| 			this.ws.handleUpgrade(request, socket, head, (socket) => { | ||||
| 				this.ws.emit("connection", socket, request); | ||||
|  | ||||
| @ -1,10 +1,13 @@ | ||||
| import { WebSocket } from "@fosscord/gateway"; | ||||
| import { Message } from "./Message"; | ||||
| import { Session } from "@fosscord/util"; | ||||
| 
 | ||||
| export async function Close(this: WebSocket, code: number, reason: string) { | ||||
| 	console.log("[WebSocket] closed", code, reason); | ||||
| 	if (this.session_id) await Session.delete({ session_id: this.session_id }); | ||||
| 	// @ts-ignore
 | ||||
| 	this.off("message", Message); | ||||
| 	if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); | ||||
| 	if (this.readyTimeout) clearTimeout(this.readyTimeout); | ||||
| 
 | ||||
| 	this.deflate?.close(); | ||||
| 
 | ||||
| 	this.removeAllListeners(); | ||||
| } | ||||
|  | ||||
| @ -24,9 +24,11 @@ export async function Connection( | ||||
| 	request: IncomingMessage | ||||
| ) { | ||||
| 	try { | ||||
| 		// @ts-ignore
 | ||||
| 		socket.on("close", Close); | ||||
| 		// @ts-ignore
 | ||||
| 		socket.on("message", Message); | ||||
| 		console.log(`[Gateway] Connections: ${this.clients.size}`); | ||||
| 
 | ||||
| 		const { searchParams } = new URL(`http://localhost${request.url}`); | ||||
| 		// @ts-ignore
 | ||||
| @ -68,12 +70,10 @@ export async function Connection( | ||||
| 		}); | ||||
| 
 | ||||
| 		socket.readyTimeout = setTimeout(() => { | ||||
| 			Session.delete({ session_id: socket.session_id }); //should we await?
 | ||||
| 			return socket.close(CLOSECODES.Session_timed_out); | ||||
| 		}, 1000 * 30); | ||||
| 	} catch (error) { | ||||
| 		console.error(error); | ||||
| 		Session.delete({ session_id: socket.session_id }); //should we await?
 | ||||
| 		return socket.close(CLOSECODES.Unknown_error); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -37,8 +37,6 @@ export async function Message(this: WebSocket, buffer: WS.Data) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	console.log("[Gateway] Opcode " + OPCODES[data.op]); | ||||
| 
 | ||||
| 	try { | ||||
| 		return await OPCodeHandler.call(this, data); | ||||
| 	} catch (error) { | ||||
|  | ||||
| @ -178,7 +178,7 @@ async function consume(this: WebSocket, opts: EventOpts) { | ||||
| 		case "CHANNEL_CREATE": | ||||
| 		case "CHANNEL_DELETE": | ||||
| 		case "CHANNEL_UPDATE": | ||||
| 		case "GUILD_EMOJI_UPDATE": | ||||
| 		case "GUILD_EMOJIS_UPDATE": | ||||
| 		case "READY": // will be sent by the gateway
 | ||||
| 		case "USER_UPDATE": | ||||
| 		case "APPLICATION_COMMAND_CREATE": | ||||
|  | ||||
| @ -11,6 +11,7 @@ import { | ||||
| 	PublicMember, | ||||
| 	PublicUser, | ||||
| 	PrivateUserProjection, | ||||
| 	ReadState, | ||||
| } from "@fosscord/util"; | ||||
| import { Send } from "../util/Send"; | ||||
| import { CLOSECODES, OPCODES } from "../util/Constants"; | ||||
| @ -40,7 +41,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 		return this.close(CLOSECODES.Authentication_failed); | ||||
| 	} | ||||
| 	this.user_id = decoded.id; | ||||
| 	if (!identify.intents) identify.intents = 0b11111111111111n; | ||||
| 	if (!identify.intents) identify.intents = BigInt("0b11111111111111"); | ||||
| 	this.intents = new Intents(identify.intents); | ||||
| 	if (identify.shard) { | ||||
| 		this.shard_id = identify.shard[0]; | ||||
| @ -64,6 +65,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 			"guild", | ||||
| 			"guild.channels", | ||||
| 			"guild.emojis", | ||||
| 			"guild.emojis.user", | ||||
| 			"guild.roles", | ||||
| 			"guild.stickers", | ||||
| 			"user", | ||||
| @ -92,7 +94,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 		// @ts-ignore
 | ||||
| 		x.channel.recipients = x.channel.recipients?.map((x) => x.user); | ||||
| 		//TODO is this needed? check if users in group dm that are not friends are sent in the READY event
 | ||||
| 		//users = users.concat(x.channel.recipients);
 | ||||
| 		users = users.concat(x.channel.recipients as unknown as User[]); | ||||
| 		if (x.channel.isDm()) { | ||||
| 			x.channel.recipients = x.channel.recipients!.filter( | ||||
| 				(x) => x.id !== this.user_id | ||||
| @ -138,6 +140,13 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 	//We save the session and we delete it when the websocket is closed
 | ||||
| 	await session.save(); | ||||
| 
 | ||||
| 	const read_states = await ReadState.find({ user_id: this.user_id }); | ||||
| 	read_states.forEach((s: any) => { | ||||
| 		s.id = s.channel_id; | ||||
| 		delete s.user_id; | ||||
| 		delete s.channel_id; | ||||
| 	}); | ||||
| 
 | ||||
| 	const privateUser = { | ||||
| 		avatar: user.avatar, | ||||
| 		mobile: user.mobile, | ||||
| @ -176,8 +185,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 		geo_ordered_rtc_regions: [], // TODO
 | ||||
| 		relationships: user.relationships.map((x) => x.toPublicRelationship()), | ||||
| 		read_state: { | ||||
| 			// TODO
 | ||||
| 			entries: [], | ||||
| 			entries: read_states, | ||||
| 			partial: false, | ||||
| 			version: 304128, | ||||
| 		}, | ||||
| @ -200,14 +208,12 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 		// @ts-ignore
 | ||||
| 		experiments: experiments, // TODO
 | ||||
| 		guild_join_requests: [], // TODO what is this?
 | ||||
| 		users: users.unique(), | ||||
| 		users: users.filter((x) => x).unique(), | ||||
| 		merged_members: merged_members, | ||||
| 		// shard // TODO: only for bots sharding
 | ||||
| 		// application // TODO for applications
 | ||||
| 	}; | ||||
| 
 | ||||
| 	console.log("Send ready"); | ||||
| 
 | ||||
| 	// TODO: send real proper data structure
 | ||||
| 	await Send(this, { | ||||
| 		op: OPCODES.Dispatch, | ||||
|  | ||||
| @ -41,6 +41,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { | ||||
| 	const items = []; | ||||
| 
 | ||||
| 	for (const role of roles) { | ||||
| 		// @ts-ignore
 | ||||
| 		const [role_members, other_members] = partition(members, (m: Member) => | ||||
| 			m.roles.find((r) => r.id === role.id) | ||||
| 		); | ||||
| @ -53,9 +54,12 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { | ||||
| 		groups.push(group); | ||||
| 
 | ||||
| 		for (const member of role_members) { | ||||
| 			member.roles = member.roles.filter((x) => x.id !== guild_id); | ||||
| 			member.roles = member.roles.filter((x: Role) => x.id !== guild_id); | ||||
| 			items.push({ | ||||
| 				member: { ...member, roles: member.roles.map((x) => x.id) }, | ||||
| 				member: { | ||||
| 					...member, | ||||
| 					roles: member.roles.map((x: Role) => x.id), | ||||
| 				}, | ||||
| 			}); | ||||
| 		} | ||||
| 		members = other_members; | ||||
| @ -84,7 +88,9 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { | ||||
| } | ||||
| 
 | ||||
| function partition<T>(array: T[], isValid: Function) { | ||||
| 	// @ts-ignore
 | ||||
| 	return array.reduce( | ||||
| 		// @ts-ignore
 | ||||
| 		([pass, fail], elem) => { | ||||
| 			return isValid(elem) | ||||
| 				? [[...pass, elem], fail] | ||||
|  | ||||
| @ -18,6 +18,9 @@ export async function Send(socket: WebSocket, data: Payload) { | ||||
| 	} | ||||
| 
 | ||||
| 	return new Promise((res, rej) => { | ||||
| 		if (socket.readyState !== 1) { | ||||
| 			return rej("socket not open"); | ||||
| 		} | ||||
| 		socket.send(buffer, (err: any) => { | ||||
| 			if (err) return rej(err); | ||||
| 			return res(null); | ||||
|  | ||||
							
								
								
									
										6
									
								
								rtc/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								rtc/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|   "name": "rtc", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": {} | ||||
| } | ||||
							
								
								
									
										1
									
								
								rtc/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								rtc/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| {} | ||||
							
								
								
									
										9
									
								
								util/ormconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								util/ormconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| { | ||||
| 	"type": "sqlite", | ||||
| 	"database": "../bundle/database.db", | ||||
| 	"migrations": ["src/migrations/*.ts"], | ||||
| 	"entities": ["src/entities/*.ts"], | ||||
| 	"cli": { | ||||
| 		"migrationsDir": "src/migrations" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										1192
									
								
								util/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1192
									
								
								util/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -8,7 +8,8 @@ | ||||
| 		"start": "npm run build && node dist/", | ||||
| 		"test": "npm run build && jest", | ||||
| 		"postinstall": "npm run build", | ||||
| 		"build": "npx tsc -b ." | ||||
| 		"build": "npx tsc -p .", | ||||
| 		"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js" | ||||
| 	}, | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| @ -28,22 +29,16 @@ | ||||
| 	}, | ||||
| 	"homepage": "https://docs.fosscord.com/", | ||||
| 	"devDependencies": { | ||||
| 		"@swc/cli": "^0.1.51", | ||||
| 		"@swc/core": "^1.2.93", | ||||
| 		"@types/amqplib": "^0.8.1", | ||||
| 		"@types/jsonwebtoken": "^8.5.0", | ||||
| 		"@types/mongoose-autopopulate": "^0.10.1", | ||||
| 		"@types/multer": "^1.4.7", | ||||
| 		"@types/node": "^14.17.9", | ||||
| 		"@types/node-fetch": "^2.5.12", | ||||
| 		"jest": "^27.0.6" | ||||
| 		"jest": "^27.0.6", | ||||
| 		"ts-node": "^10.2.1" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"ajv": "^8.6.2", | ||||
| 		"amqplib": "^0.8.0", | ||||
| 		"class-validator": "^0.13.1", | ||||
| 		"dot-prop": "^6.0.1", | ||||
| 		"env-paths": "^2.2.1", | ||||
| 		"jsonwebtoken": "^8.5.1", | ||||
| 		"lambert-server": "^1.2.11", | ||||
| 		"missing-native-js-functions": "^1.2.17", | ||||
| @ -54,7 +49,6 @@ | ||||
| 		"pg": "^8.7.1", | ||||
| 		"reflect-metadata": "^0.1.13", | ||||
| 		"sqlite3": "^5.0.2", | ||||
| 		"tsconfig-paths": "^3.11.0", | ||||
| 		"typeorm": "^0.2.37", | ||||
| 		"typescript": "^4.4.2", | ||||
| 		"typescript-json-schema": "^0.50.1" | ||||
|  | ||||
| @ -55,10 +55,7 @@ export class AuditLog extends BaseClass { | ||||
| 	@ManyToOne(() => User, (user: User) => user.id) | ||||
| 	user: User; | ||||
| 
 | ||||
| 	@Column({ | ||||
| 		type: "simple-enum", | ||||
| 		enum: AuditLogEvents, | ||||
| 	}) | ||||
| 	@Column({ type: "int" }) | ||||
| 	action_type: AuditLogEvents; | ||||
| 
 | ||||
| 	@Column({ type: "simple-json", nullable: true }) | ||||
|  | ||||
| @ -1,19 +1,8 @@ | ||||
| import "reflect-metadata"; | ||||
| import { | ||||
| 	BaseEntity, | ||||
| 	BeforeInsert, | ||||
| 	BeforeUpdate, | ||||
| 	EntityMetadata, | ||||
| 	FindConditions, | ||||
| 	ObjectIdColumn, | ||||
| 	PrimaryColumn, | ||||
| } from "typeorm"; | ||||
| import { BaseEntity, EntityMetadata, FindConditions, ObjectIdColumn, PrimaryColumn } from "typeorm"; | ||||
| import { Snowflake } from "../util/Snowflake"; | ||||
| import "missing-native-js-functions"; | ||||
| 
 | ||||
| // TODO use class-validator https://typeorm.io/#/validation with class annotators (isPhone/isEmail) combined with types from typescript-json-schema
 | ||||
| // btw. we don't use class-validator for everything, because we need to explicitly set the type instead of deriving it from typescript also it doesn't easily support nested objects
 | ||||
| 
 | ||||
| export class BaseClassWithoutId extends BaseEntity { | ||||
| 	constructor(props?: any) { | ||||
| 		super(); | ||||
| @ -42,7 +31,7 @@ export class BaseClassWithoutId extends BaseEntity { | ||||
| 		for (const key in props) { | ||||
| 			if (!properties.has(key)) continue; | ||||
| 			// @ts-ignore
 | ||||
| 			const setter = this[`set${key.capitalize()}`]; | ||||
| 			const setter = this[`set${key.capitalize()}`]; // use setter function if it exists
 | ||||
| 
 | ||||
| 			if (setter) { | ||||
| 				setter.call(this, props[key]); | ||||
| @ -53,12 +42,6 @@ export class BaseClassWithoutId extends BaseEntity { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@BeforeUpdate() | ||||
| 	@BeforeInsert() | ||||
| 	validate() { | ||||
| 		return this; | ||||
| 	} | ||||
| 
 | ||||
| 	toJSON(): any { | ||||
| 		return Object.fromEntries( | ||||
| 			this.metadata.columns // @ts-ignore
 | ||||
| @ -76,42 +59,6 @@ export class BaseClassWithoutId extends BaseEntity { | ||||
| 		const repository = this.getRepository(); | ||||
| 		return repository.decrement(conditions, propertyPath, value); | ||||
| 	} | ||||
| 
 | ||||
| 	// static async delete<T>(criteria: FindConditions<T>, options?: RemoveOptions) {
 | ||||
| 	// 	if (!criteria) throw new Error("You need to specify delete criteria");
 | ||||
| 
 | ||||
| 	// 	const repository = this.getRepository();
 | ||||
| 	// 	const promises = repository.metadata.relations.map(async (x) => {
 | ||||
| 	// 		if (x.orphanedRowAction !== "delete") return;
 | ||||
| 
 | ||||
| 	// 		const foreignKey =
 | ||||
| 	// 			x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) ||
 | ||||
| 	// 			x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity
 | ||||
| 	// 		if (!foreignKey) {
 | ||||
| 	// 			throw new Error(
 | ||||
| 	// 				`Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}`
 | ||||
| 	// 			);
 | ||||
| 	// 		}
 | ||||
| 	// 		const id = (criteria as any)[foreignKey.referencedColumnNames[0]];
 | ||||
| 	// 		if (!id) throw new Error("id missing in criteria options " + foreignKey.referencedColumnNames);
 | ||||
| 
 | ||||
| 	// 		if (x.relationType === "many-to-many") {
 | ||||
| 	// 			return getConnection()
 | ||||
| 	// 				.createQueryBuilder()
 | ||||
| 	// 				.relation(this, x.propertyName)
 | ||||
| 	// 				.of(id)
 | ||||
| 	// 				.remove({ [foreignKey.columnNames[0]]: id });
 | ||||
| 	// 		} else if (
 | ||||
| 	// 			x.relationType === "one-to-one" ||
 | ||||
| 	// 			x.relationType === "many-to-one" ||
 | ||||
| 	// 			x.relationType === "one-to-many"
 | ||||
| 	// 		) {
 | ||||
| 	// 			return (x.inverseEntityMetadata.target as any).delete({ [foreignKey.columnNames[0]]: id });
 | ||||
| 	// 		}
 | ||||
| 	// 	});
 | ||||
| 	// 	await Promise.all(promises);
 | ||||
| 	// 	return super.delete(criteria, options);
 | ||||
| 	// }
 | ||||
| } | ||||
| 
 | ||||
| export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn; | ||||
|  | ||||
| @ -39,7 +39,7 @@ export class Channel extends BaseClass { | ||||
| 	@Column({ type: "text", nullable: true }) | ||||
| 	icon?: string | null; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: ChannelType }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	type: ChannelType; | ||||
| 
 | ||||
| 	@OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { | ||||
|  | ||||
| @ -51,11 +51,6 @@ export interface ConfigValue { | ||||
| 	general: { | ||||
| 		instanceId: string; | ||||
| 	}; | ||||
| 	permissions: { | ||||
| 		user: { | ||||
| 			createGuilds: boolean; | ||||
| 		}; | ||||
| 	}; | ||||
| 	limits: { | ||||
| 		user: { | ||||
| 			maxGuilds: number; | ||||
| @ -64,6 +59,7 @@ export interface ConfigValue { | ||||
| 		}; | ||||
| 		guild: { | ||||
| 			maxRoles: number; | ||||
| 			maxEmojis: number; | ||||
| 			maxMembers: number; | ||||
| 			maxChannels: number; | ||||
| 			maxChannelsInCategory: number; | ||||
| @ -153,6 +149,11 @@ export interface ConfigValue { | ||||
| 			canLeave: boolean; | ||||
| 		}; | ||||
| 	}; | ||||
| 	gif: { | ||||
| 		enabled: boolean; | ||||
| 		provider: "tenor"; // more coming soon
 | ||||
| 		apiKey?: string; | ||||
| 	}; | ||||
| 	rabbitmq: { | ||||
| 		host: string | null; | ||||
| 	}; | ||||
| @ -175,11 +176,6 @@ export const DefaultConfigOptions: ConfigValue = { | ||||
| 	general: { | ||||
| 		instanceId: Snowflake.generate(), | ||||
| 	}, | ||||
| 	permissions: { | ||||
| 		user: { | ||||
| 			createGuilds: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 	limits: { | ||||
| 		user: { | ||||
| 			maxGuilds: 100, | ||||
| @ -188,6 +184,7 @@ export const DefaultConfigOptions: ConfigValue = { | ||||
| 		}, | ||||
| 		guild: { | ||||
| 			maxRoles: 250, | ||||
| 			maxEmojis: 50, // TODO: max emojis per guild per nitro level
 | ||||
| 			maxMembers: 250000, | ||||
| 			maxChannels: 500, | ||||
| 			maxChannelsInCategory: 50, | ||||
| @ -305,7 +302,6 @@ export const DefaultConfigOptions: ConfigValue = { | ||||
| 			}, | ||||
| 		], | ||||
| 	}, | ||||
| 
 | ||||
| 	guild: { | ||||
| 		showAllGuildsInDiscovery: false, | ||||
| 		autoJoin: { | ||||
| @ -314,6 +310,11 @@ export const DefaultConfigOptions: ConfigValue = { | ||||
| 			guilds: [], | ||||
| 		}, | ||||
| 	}, | ||||
| 	gif: { | ||||
| 		enabled: true, | ||||
| 		provider: "tenor", | ||||
| 		apiKey: "LIVDSRZULELA", | ||||
| 	}, | ||||
| 	rabbitmq: { | ||||
| 		host: null, | ||||
| 	}, | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; | ||||
| import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; | ||||
| import { User } from "."; | ||||
| import { BaseClass } from "./BaseClass"; | ||||
| import { Guild } from "./Guild"; | ||||
| import { Role } from "./Role"; | ||||
| @ -20,6 +21,14 @@ export class Emoji extends BaseClass { | ||||
| 	}) | ||||
| 	guild: Guild; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	@RelationId((emoji: Emoji) => emoji.user) | ||||
| 	user_id: string; | ||||
| 
 | ||||
| 	@JoinColumn({ name: "user_id" }) | ||||
| 	@ManyToOne(() => User) | ||||
| 	user: User; | ||||
| 
 | ||||
| 	@Column() | ||||
| 	managed: boolean; | ||||
| 
 | ||||
| @ -28,4 +37,7 @@ export class Emoji extends BaseClass { | ||||
| 
 | ||||
| 	@Column() | ||||
| 	require_colons: boolean; | ||||
| 
 | ||||
| 	@Column({ type: "simple-array" }) | ||||
| 	roles: string[]; // roles this emoji is whitelisted to (new discord feature?)
 | ||||
| } | ||||
|  | ||||
| @ -257,14 +257,6 @@ export class Guild extends BaseClass { | ||||
| 	@Column({ nullable: true }) | ||||
| 	unavailable?: boolean; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	@RelationId((guild: Guild) => guild.vanity_url) | ||||
| 	vanity_url_code?: string; | ||||
| 
 | ||||
| 	@JoinColumn({ name: "vanity_url_code" }) | ||||
| 	@ManyToOne(() => Invite) | ||||
| 	vanity_url?: Invite; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	verification_level?: number; | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; | ||||
| import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm"; | ||||
| import { Member } from "./Member"; | ||||
| import { BaseClass, PrimaryIdColumn } from "./BaseClass"; | ||||
| import { BaseClassWithoutId } from "./BaseClass"; | ||||
| import { Channel } from "./Channel"; | ||||
| import { Guild } from "./Guild"; | ||||
| import { User } from "./User"; | ||||
| @ -8,8 +8,8 @@ import { User } from "./User"; | ||||
| export const PublicInviteRelation = ["inviter", "guild", "channel"]; | ||||
| 
 | ||||
| @Entity("invites") | ||||
| export class Invite extends BaseClass { | ||||
| 	@PrimaryIdColumn() | ||||
| export class Invite extends BaseClassWithoutId { | ||||
| 	@PrimaryColumn() | ||||
| 	code: string; | ||||
| 
 | ||||
| 	@Column() | ||||
| @ -71,6 +71,9 @@ export class Invite extends BaseClass { | ||||
| 	@Column({ nullable: true }) | ||||
| 	target_user_type?: number; | ||||
| 
 | ||||
| 	@Column({ nullable: true}) | ||||
| 	vanity_url?: boolean; | ||||
| 
 | ||||
| 	static async joinGuild(user_id: string, code: string) { | ||||
| 		const invite = await Invite.findOneOrFail({ code }); | ||||
| 		if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); | ||||
|  | ||||
| @ -46,9 +46,6 @@ export enum MessageType { | ||||
| 
 | ||||
| @Entity("messages") | ||||
| export class Message extends BaseClass { | ||||
| 	@Column() | ||||
| 	id: string; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	@RelationId((message: Message) => message.channel) | ||||
| 	channel_id: string; | ||||
| @ -151,7 +148,7 @@ export class Message extends BaseClass { | ||||
| 	@Column({ nullable: true }) | ||||
| 	pinned?: boolean; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: MessageType }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	type: MessageType; | ||||
| 
 | ||||
| 	@Column({ type: "simple-json", nullable: true }) | ||||
|  | ||||
| @ -3,9 +3,6 @@ import { BaseClass } from "./BaseClass"; | ||||
| 
 | ||||
| @Entity("rate_limits") | ||||
| export class RateLimit extends BaseClass { | ||||
| 	@Column() | ||||
| 	id: "global" | "error" | string; // channel_239842397 | guild_238927349823 | webhook_238923423498
 | ||||
| 
 | ||||
| 	@Column() // no relation as it also
 | ||||
| 	executor_id: string; | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; | ||||
| import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; | ||||
| import { BaseClass } from "./BaseClass"; | ||||
| import { Channel } from "./Channel"; | ||||
| import { Message } from "./Message"; | ||||
| @ -9,8 +9,9 @@ import { User } from "./User"; | ||||
| // public read receipt ≥ notification cursor ≥ private fully read marker
 | ||||
| 
 | ||||
| @Entity("read_states") | ||||
| @Index(["channel_id", "user_id"], { unique: true }) | ||||
| export class ReadState extends BaseClass { | ||||
| 	@Column({ nullable: true }) | ||||
| 	@Column() | ||||
| 	@RelationId((read_state: ReadState) => read_state.channel) | ||||
| 	channel_id: string; | ||||
| 
 | ||||
| @ -20,7 +21,7 @@ export class ReadState extends BaseClass { | ||||
| 	}) | ||||
| 	channel: Channel; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	@Column() | ||||
| 	@RelationId((read_state: ReadState) => read_state.user) | ||||
| 	user_id: string; | ||||
| 
 | ||||
| @ -35,15 +36,15 @@ export class ReadState extends BaseClass { | ||||
| 	last_message_id: string; | ||||
| 
 | ||||
| 	@JoinColumn({ name: "last_message_id" }) | ||||
| 	@ManyToOne(() => Message) | ||||
| 	@ManyToOne(() => Message, { nullable: true }) | ||||
| 	last_message?: Message; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	last_pin_timestamp?: Date; | ||||
| 
 | ||||
| 	@Column() | ||||
| 	@Column({ nullable: true }) | ||||
| 	mention_count: number; | ||||
| 
 | ||||
| 	@Column() | ||||
| 	@Column({ nullable: true }) | ||||
| 	manual: boolean; | ||||
| } | ||||
|  | ||||
| @ -35,7 +35,7 @@ export class Relationship extends BaseClass { | ||||
| 	@Column({ nullable: true }) | ||||
| 	nickname?: string; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: RelationshipType }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	type: RelationshipType; | ||||
| 
 | ||||
| 	toPublicRelationship() { | ||||
|  | ||||
| @ -36,9 +36,9 @@ export class Sticker extends BaseClass { | ||||
| 	}) | ||||
| 	guild?: Guild; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: StickerType }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	type: StickerType; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: StickerFormatType }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	format_type: StickerFormatType; | ||||
| } | ||||
|  | ||||
| @ -9,7 +9,7 @@ export enum TeamMemberState { | ||||
| 
 | ||||
| @Entity("team_members") | ||||
| export class TeamMember extends BaseClass { | ||||
| 	@Column({ type: "simple-enum", enum: TeamMemberState }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	membership_state: TeamMemberState; | ||||
| 
 | ||||
| 	@Column({ type: "simple-array" }) | ||||
|  | ||||
| @ -198,7 +198,7 @@ export class User extends BaseClass { | ||||
| 		// randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists
 | ||||
| 		// if it all five times already exists, abort with USERNAME_TOO_MANY_USERS error
 | ||||
| 		// else just continue
 | ||||
| 		// TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database?
 | ||||
| 		// TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database?
 | ||||
| 		for (let tries = 0; tries < 5; tries++) { | ||||
| 			discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); | ||||
| 			exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] }); | ||||
| @ -219,7 +219,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 = await new User({ | ||||
| 		const user = new User({ | ||||
| 			created_at: new Date(), | ||||
| 			username: username, | ||||
| 			discriminator, | ||||
| @ -246,7 +246,9 @@ export class User extends BaseClass { | ||||
| 			}, | ||||
| 			settings: { ...defaultSettings, locale: language }, | ||||
| 			fingerprints: [], | ||||
| 		}).save(); | ||||
| 		}); | ||||
| 
 | ||||
| 		await user.save(); | ||||
| 
 | ||||
| 		if (Config.get().guild.autoJoin.enabled) { | ||||
| 			for (const guild of Config.get().guild.autoJoin.guilds || []) { | ||||
|  | ||||
| @ -12,10 +12,7 @@ export enum WebhookType { | ||||
| 
 | ||||
| @Entity("webhooks") | ||||
| export class Webhook extends BaseClass { | ||||
| 	@Column() | ||||
| 	id: string; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: WebhookType }) | ||||
| 	@Column({ type: "int" }) | ||||
| 	type: WebhookType; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
|  | ||||
| @ -1,12 +1,6 @@ | ||||
| import "reflect-metadata"; | ||||
| 
 | ||||
| // export * as Constants from "../util/Constants";
 | ||||
| export * from "./util/index"; | ||||
| export * from "./interfaces/index"; | ||||
| export * from "./entities/index"; | ||||
| export * from "./dtos/index"; | ||||
| 
 | ||||
| // import Config from "../util/Config";
 | ||||
| // import db, { MongooseCache, toObject } from "./util/Database";
 | ||||
| 
 | ||||
| // export { Config };
 | ||||
|  | ||||
| @ -185,8 +185,8 @@ export interface GuildBanRemoveEvent extends Event { | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export interface GuildEmojiUpdateEvent extends Event { | ||||
| 	event: "GUILD_EMOJI_UPDATE"; | ||||
| export interface GuildEmojisUpdateEvent extends Event { | ||||
| 	event: "GUILD_EMOJIS_UPDATE"; | ||||
| 	data: { | ||||
| 		guild_id: string; | ||||
| 		emojis: Emoji[]; | ||||
| @ -459,7 +459,7 @@ export type EventData = | ||||
| 	| GuildDeleteEvent | ||||
| 	| GuildBanAddEvent | ||||
| 	| GuildBanRemoveEvent | ||||
| 	| GuildEmojiUpdateEvent | ||||
| 	| GuildEmojisUpdateEvent | ||||
| 	| GuildIntegrationUpdateEvent | ||||
| 	| GuildMemberAddEvent | ||||
| 	| GuildMemberRemoveEvent | ||||
| @ -552,7 +552,7 @@ export type EVENT = | ||||
| 	| "GUILD_DELETE" | ||||
| 	| "GUILD_BAN_ADD" | ||||
| 	| "GUILD_BAN_REMOVE" | ||||
| 	| "GUILD_EMOJI_UPDATE" | ||||
| 	| "GUILD_EMOJIS_UPDATE" | ||||
| 	| "GUILD_INTEGRATIONS_UPDATE" | ||||
| 	| "GUILD_MEMBER_ADD" | ||||
| 	| "GUILD_MEMBER_REMOVE" | ||||
|  | ||||
							
								
								
									
										13
									
								
								util/src/migrations/1633864260873-EmojiRoles.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								util/src/migrations/1633864260873-EmojiRoles.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| import { MigrationInterface, QueryRunner } from "typeorm"; | ||||
| 
 | ||||
| export class EmojiRoles1633864260873 implements MigrationInterface { | ||||
| 	name = "EmojiRoles1633864260873"; | ||||
| 
 | ||||
| 	public async up(queryRunner: QueryRunner): Promise<void> { | ||||
| 		await queryRunner.query(`ALTER TABLE "emojis" ADD "roles" text NOT NULL DEFAULT ''`); | ||||
| 	} | ||||
| 
 | ||||
| 	public async down(queryRunner: QueryRunner): Promise<void> { | ||||
| 		await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "roles"`); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										23
									
								
								util/src/migrations/1633864669243-EmojiUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								util/src/migrations/1633864669243-EmojiUser.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| import { MigrationInterface, QueryRunner } from "typeorm"; | ||||
| 
 | ||||
| export class EmojiUser1633864669243 implements MigrationInterface { | ||||
| 	name = "EmojiUser1633864669243"; | ||||
| 
 | ||||
| 	public async up(queryRunner: QueryRunner): Promise<void> { | ||||
| 		await queryRunner.query(`ALTER TABLE "emojis" ADD "user_id" varchar`); | ||||
| 		try { | ||||
| 			await queryRunner.query( | ||||
| 				`ALTER TABLE "emojis" ADD CONSTRAINT FK_fa7ddd5f9a214e28ce596548421 FOREIGN KEY (user_id) REFERENCES users(id)` | ||||
| 			); | ||||
| 		} catch (error) { | ||||
| 			console.error( | ||||
| 				"sqlite doesn't support altering foreign keys: https://stackoverflow.com/questions/1884818/how-do-i-add-a-foreign-key-to-an-existing-sqlite-table" | ||||
| 			); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public async down(queryRunner: QueryRunner): Promise<void> { | ||||
| 		await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "user_id"`); | ||||
| 		await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_fa7ddd5f9a214e28ce596548421`); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										17
									
								
								util/src/migrations/1633881705509-VanityInvite.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								util/src/migrations/1633881705509-VanityInvite.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| import { MigrationInterface, QueryRunner } from "typeorm"; | ||||
| 
 | ||||
| export class VanityInvite1633881705509 implements MigrationInterface { | ||||
| 	public async up(queryRunner: QueryRunner): Promise<void> { | ||||
| 		try { | ||||
| 			await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN vanity_url_code`); | ||||
| 			await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad`); | ||||
| 		} catch (error) {} | ||||
| 	} | ||||
| 
 | ||||
| 	public async down(queryRunner: QueryRunner): Promise<void> { | ||||
| 		await queryRunner.query(`ALTER TABLE "emojis" ADD vanity_url_code varchar`); | ||||
| 		await queryRunner.query( | ||||
| 			`ALTER TABLE "emojis" ADD CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad FOREIGN KEY ("vanity_url_code") REFERENCES "invites"("code") ON DELETE NO ACTION ON UPDATE NO ACTION` | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										109
									
								
								util/src/migrations/migrate_db_engine.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								util/src/migrations/migrate_db_engine.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| const { config } = require("dotenv"); | ||||
| config(); | ||||
| const { createConnection } = require("typeorm"); | ||||
| const { initDatabase } = require("../../dist/util/Database"); | ||||
| require("missing-native-js-functions"); | ||||
| const { | ||||
| 	Application, | ||||
| 	Attachment, | ||||
| 	Ban, | ||||
| 	Channel, | ||||
| 	ConfigEntity, | ||||
| 	ConnectedAccount, | ||||
| 	Emoji, | ||||
| 	Guild, | ||||
| 	Invite, | ||||
| 	Member, | ||||
| 	Message, | ||||
| 	ReadState, | ||||
| 	Recipient, | ||||
| 	Relationship, | ||||
| 	Role, | ||||
| 	Sticker, | ||||
| 	Team, | ||||
| 	TeamMember, | ||||
| 	Template, | ||||
| 	User, | ||||
| 	VoiceState, | ||||
| 	Webhook, | ||||
| } = require("../../dist/entities/index"); | ||||
| 
 | ||||
| async function main() { | ||||
| 	if (!process.env.TO) throw new Error("TO database env connection string not set"); | ||||
| 
 | ||||
| 	// manually arrange them because of foreign keys
 | ||||
| 	const entities = [ | ||||
| 		ConfigEntity, | ||||
| 		User, | ||||
| 		Guild, | ||||
| 		Channel, | ||||
| 		Invite, | ||||
| 		Role, | ||||
| 		Ban, | ||||
| 		Application, | ||||
| 		Emoji, | ||||
| 		ConnectedAccount, | ||||
| 		Member, | ||||
| 		ReadState, | ||||
| 		Recipient, | ||||
| 		Relationship, | ||||
| 		Sticker, | ||||
| 		Team, | ||||
| 		TeamMember, | ||||
| 		Template, | ||||
| 		VoiceState, | ||||
| 		Webhook, | ||||
| 		Message, | ||||
| 		Attachment, | ||||
| 	]; | ||||
| 
 | ||||
| 	const oldDB = await initDatabase(); | ||||
| 
 | ||||
| 	const type = process.env.TO.includes("://") ? process.env.TO.split(":")[0]?.replace("+srv", "") : "sqlite"; | ||||
| 	const isSqlite = type.includes("sqlite"); | ||||
| 
 | ||||
| 	// @ts-ignore
 | ||||
| 	const newDB = await createConnection({ | ||||
| 		type, | ||||
| 		url: isSqlite ? undefined : process.env.TO, | ||||
| 		database: isSqlite ? process.env.TO : undefined, | ||||
| 		entities, | ||||
| 		name: "new", | ||||
| 		synchronize: true, | ||||
| 	}); | ||||
| 	let i = 0; | ||||
| 
 | ||||
| 	try { | ||||
| 		for (const entity of entities) { | ||||
| 			const entries = await oldDB.manager.find(entity); | ||||
| 
 | ||||
| 			// @ts-ignore
 | ||||
| 			console.log("migrating " + entries.length + " " + entity.name + " ..."); | ||||
| 
 | ||||
| 			for (const entry of entries) { | ||||
| 				console.log(i++); | ||||
| 
 | ||||
| 				try { | ||||
| 					await newDB.manager.insert(entity, entry); | ||||
| 				} catch (error) { | ||||
| 					try { | ||||
| 						if (!entry.id) throw new Error("object doesn't have a unique id: " + entry); | ||||
| 						await newDB.manager.update(entity, { id: entry.id }, entry); | ||||
| 					} catch (error) { | ||||
| 						console.error("couldn't migrate " + i + " " + entity.name, error); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// @ts-ignore
 | ||||
| 			console.log("migrated " + entries.length + " " + entity.name); | ||||
| 		} | ||||
| 	} catch (error) { | ||||
| 		console.error(error.message); | ||||
| 	} | ||||
| 
 | ||||
| 	console.log("SUCCESS migrated all data"); | ||||
| 	await newDB.close(); | ||||
| } | ||||
| 
 | ||||
| main().caught(); | ||||
| @ -1,129 +0,0 @@ | ||||
| import { config } from "dotenv"; | ||||
| config(); | ||||
| import { BaseEntity, createConnection, EntityTarget } from "typeorm"; | ||||
| import { initDatabase } from "../util/Database"; | ||||
| import "missing-native-js-functions"; | ||||
| import { | ||||
| 	Application, | ||||
| 	Attachment, | ||||
| 	Ban, | ||||
| 	Channel, | ||||
| 	ConnectedAccount, | ||||
| 	defaultSettings, | ||||
| 	Emoji, | ||||
| 	Guild, | ||||
| 	Invite, | ||||
| 	Member, | ||||
| 	Message, | ||||
| 	RateLimit, | ||||
| 	ReadState, | ||||
| 	Recipient, | ||||
| 	Relationship, | ||||
| 	Role, | ||||
| 	Sticker, | ||||
| 	Team, | ||||
| 	TeamMember, | ||||
| 	Template, | ||||
| 	User, | ||||
| 	VoiceState, | ||||
| 	Webhook, | ||||
| } from ".."; | ||||
| 
 | ||||
| async function main() { | ||||
| 	if (!process.env.FROM) throw new Error("FROM database env connection string not set"); | ||||
| 
 | ||||
| 	// manually arrange them because of foreign key
 | ||||
| 	const entities = [ | ||||
| 		User, | ||||
| 		Guild, | ||||
| 		Channel, | ||||
| 		Invite, | ||||
| 		Role, | ||||
| 		Ban, | ||||
| 		Application, | ||||
| 		Emoji, | ||||
| 		ConnectedAccount, | ||||
| 		Member, | ||||
| 		ReadState, | ||||
| 		Recipient, | ||||
| 		Relationship, | ||||
| 		Sticker, | ||||
| 		Team, | ||||
| 		TeamMember, | ||||
| 		Template, | ||||
| 		VoiceState, | ||||
| 		Webhook, | ||||
| 		Message, | ||||
| 		Attachment, | ||||
| 	]; | ||||
| 
 | ||||
| 	const newDB = await initDatabase(); | ||||
| 
 | ||||
| 	// @ts-ignore
 | ||||
| 	const oldDB = await createConnection({ | ||||
| 		type: process.env.FROM.split(":")[0]?.replace("+srv", ""), | ||||
| 		url: process.env.FROM, | ||||
| 		entities, | ||||
| 		name: "old", | ||||
| 	}); | ||||
| 	let i = 0; | ||||
| 
 | ||||
| 	try { | ||||
| 		for (const e of entities) { | ||||
| 			const entity = e as EntityTarget<any>; | ||||
| 			const entries = await oldDB.manager.find(entity); | ||||
| 			//@ts-ignore
 | ||||
| 			console.log("migrated " + entries.length + " " + entity.name); | ||||
| 
 | ||||
| 			for (const entry of entries) { | ||||
| 				console.log(i++); | ||||
| 
 | ||||
| 				if (entry instanceof User) { | ||||
| 					console.log("instance of User"); | ||||
| 					if (entry.bio == null) entry.bio = ""; | ||||
| 					if (entry.rights == null) entry.rights = "0"; | ||||
| 					if (entry.disabled == null) entry.disabled = false; | ||||
| 					if (entry.fingerprints == null) entry.fingerprints = []; | ||||
| 					if (entry.deleted == null) entry.deleted = false; | ||||
| 					if (entry.data == null) { | ||||
| 						entry.data = { | ||||
| 							valid_tokens_since: new Date(0), | ||||
| 							hash: undefined, | ||||
| 						}; | ||||
| 						// @ts-ignore
 | ||||
| 						if (entry.user_data) { | ||||
| 							// TODO: relationships
 | ||||
| 							entry.data = { | ||||
| 								// @ts-ignore
 | ||||
| 								valid_tokens_since: entry.user_data.valid_tokens_since, // @ts-ignore
 | ||||
| 								hash: entry.user_data.hash, | ||||
| 							}; | ||||
| 						} | ||||
| 					} | ||||
| 					// @ts-ignore
 | ||||
| 					if (entry.settings == null) { | ||||
| 						entry.settings = defaultSettings; | ||||
| 						// @ts-ignore
 | ||||
| 						if (entry.user_data) entry.settings = entry.user_settings; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				// try {
 | ||||
| 				await newDB.manager.insert(entity, entry); | ||||
| 				// } catch (error) {
 | ||||
| 				// 	if (!entry.id) throw new Error("object doesn't have a unique id: " + entry);
 | ||||
| 				// 	await newDB.manager.update(entity, { id: entry.id }, entry);
 | ||||
| 				// }
 | ||||
| 			} | ||||
| 			// @ts-ignore
 | ||||
| 			console.log("migrated all " + entity.name); | ||||
| 		} | ||||
| 	} catch (error) { | ||||
| 		console.error((error as any).message); | ||||
| 	} | ||||
| 
 | ||||
| 	console.log("SUCCESS migrated all data"); | ||||
| 	await newDB.close(); | ||||
| } | ||||
| 
 | ||||
| main().caught(); | ||||
| @ -47,16 +47,18 @@ function pairsToConfig(pairs: ConfigEntity[]) { | ||||
| 
 | ||||
| 	pairs.forEach((p) => { | ||||
| 		const keys = p.key.split("_"); | ||||
| 		let prev = ""; | ||||
| 		let obj = value; | ||||
| 		let prev = ""; | ||||
| 		let prevObj = obj; | ||||
| 		let i = 0; | ||||
| 
 | ||||
| 		for (const key of keys) { | ||||
| 			if (Number(key) && !obj[prev]) obj = obj[prev] = []; | ||||
| 			if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; | ||||
| 			if (i++ === keys.length - 1) obj[key] = p.value; | ||||
| 			else if (!obj[key]) obj[key] = {}; | ||||
| 
 | ||||
| 			prev = key; | ||||
| 			prevObj = obj; | ||||
| 			obj = obj[key]; | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| @ -46,7 +46,9 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => | ||||
| 	} else { | ||||
| 		const cancel = () => { | ||||
| 			events.removeListener(event, callback); | ||||
| 			events.setMaxListeners(events.getMaxListeners() - 1); | ||||
| 		}; | ||||
| 		events.setMaxListeners(events.getMaxListeners() + 1); | ||||
| 		events.addListener(event, (opts) => callback({ ...opts, cancel })); | ||||
| 
 | ||||
| 		return cancel; | ||||
|  | ||||
| @ -30,7 +30,7 @@ export class Rights extends BitField { | ||||
| 		MANAGE_MESSAGES: BitFlag(3), // Can't see other messages but delete/edit them in channels that they can see
 | ||||
| 		MANAGE_RATE_LIMITS: BitFlag(4), | ||||
| 		MANAGE_ROUTING: BitFlag(5), // can create custom message routes to any channel/guild
 | ||||
| 		MANAGE_TICKETS: BitFlag(6), | ||||
| 		MANAGE_TICKETS: BitFlag(6), // can respond to and resolve support tickets
 | ||||
| 		MANAGE_USERS: BitFlag(7), | ||||
| 		ADD_MEMBERS: BitFlag(8), // can manually add any members in their guilds
 | ||||
| 		BYPASS_RATE_LIMITS: BitFlag(9), | ||||
| @ -39,7 +39,7 @@ export class Rights extends BitField { | ||||
| 		CREATE_DMS: BitFlag(12), | ||||
| 		CREATE_DM_GROUPS: BitFlag(13), | ||||
| 		CREATE_GUILDS: BitFlag(14), | ||||
| 		CREATE_INVITES: BitFlag(15), | ||||
| 		CREATE_INVITES: BitFlag(15), // can create mass invites in the guilds that they have CREATE_INSTANT_INVITE
 | ||||
| 		CREATE_ROLES: BitFlag(16), | ||||
| 		CREATE_TEMPLATES: BitFlag(17), | ||||
| 		CREATE_WEBHOOKS: BitFlag(18), | ||||
| @ -50,9 +50,13 @@ export class Rights extends BitField { | ||||
| 		SELF_EDIT_MESSAGES: BitFlag(23), | ||||
| 		SELF_EDIT_NAME: BitFlag(24), | ||||
| 		SEND_MESSAGES: BitFlag(25), | ||||
| 		USE_SCREEN: BitFlag(26), | ||||
| 		USE_ACTIVITIES: BitFlag(26), // use (game) activities in voice channels (e.g. Watch together)
 | ||||
| 		USE_VIDEO: BitFlag(27), | ||||
| 		USE_VOICE: BitFlag(28), | ||||
| 		INVITE_USERS: BitFlag(29), // can create user-specific invites in the guilds that they have INVITE_USERS
 | ||||
| 		SELF_DELETE_DISABLE: BitFlag(30), // can disable/delete own account
 | ||||
| 		DEBTABLE: BitFlag(31), // can use pay-to-use features
 | ||||
| 		CREDITABLE: BitFlag(32) // can receive money from monetisation related features
 | ||||
| 	}; | ||||
| 
 | ||||
| 	any(permission: RightResolvable, checkOperator = true) { | ||||
|  | ||||
							
								
								
									
										10
									
								
								util/src/util/TraverseDirectory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								util/src/util/TraverseDirectory.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| import { Server, traverseDirectory } from "lambert-server"; | ||||
| 
 | ||||
| const DEFAULT_FILTER = /^([^\.].*)(?<!\.d)\.(js)$/; | ||||
| 
 | ||||
| export function registerRoutes(server: Server, root: string) { | ||||
| 	return traverseDirectory( | ||||
| 		{ dirname: root, recursive: true, filter: DEFAULT_FILTER }, | ||||
| 		server.registerRoute.bind(server, root) | ||||
| 	); | ||||
| } | ||||
| @ -26,7 +26,7 @@ export async function uploadFile(path: string, file: Express.Multer.File) { | ||||
| } | ||||
| 
 | ||||
| export async function handleFile(path: string, body?: string): Promise<string | undefined> { | ||||
| 	if (!body || !body.startsWith("data:")) return body; | ||||
| 	if (!body || !body.startsWith("data:")) return undefined; | ||||
| 	try { | ||||
| 		const mimetype = body.split(":")[1].split(";")[0]; | ||||
| 		const buffer = Buffer.from(body.split(",")[1], "base64"); | ||||
|  | ||||
| @ -17,3 +17,4 @@ export * from "./Rights"; | ||||
| export * from "./Snowflake"; | ||||
| export * from "./String"; | ||||
| export * from "./Array"; | ||||
| export * from "./TraverseDirectory"; | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| { | ||||
| 	"include": ["src/**/*.ts", "tests/Test.ts"], | ||||
| 	"include": ["src/**/*.ts"], | ||||
| 	"compilerOptions": { | ||||
| 		/* Visit https://aka.ms/tsconfig.json to read more about this file */ | ||||
| 
 | ||||
| 		/* Basic Options */ | ||||
| 		"incremental": true,                   /* Enable incremental compilation */ | ||||
| 		"incremental": true /* Enable incremental compilation */, | ||||
| 		"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, | ||||
| 		"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, | ||||
| 		"lib": ["ES2021"] /* Specify library files to be included in the compilation. */, | ||||
|  | ||||
							
								
								
									
										1071
									
								
								webrtc/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1071
									
								
								webrtc/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -5,7 +5,7 @@ | ||||
| 	"main": "index.js", | ||||
| 	"scripts": { | ||||
| 		"test": "npm run build && node dist/test.js", | ||||
| 		"build": "npx tsc -b .", | ||||
| 		"build": "npx tsc -p .", | ||||
| 		"start": "npm run build && node dist/start.js" | ||||
| 	}, | ||||
| 	"keywords": [], | ||||
| @ -17,7 +17,6 @@ | ||||
| 		"typescript": "^4.3.2" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"../util": "*", | ||||
| 		"mediasoup": "^3.7.16", | ||||
| 		"node-turn": "^0.0.6", | ||||
| 		"ws": "^7.4.6" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Flam3rboy
						Flam3rboy