Merge branch 'master' into fix/widget.json-channel-ordering-deleted-channels
This commit is contained in:
		
						commit
						ad2dda63c9
					
				
							
								
								
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| * text=auto | ||||
| *.sh -crlf | ||||
| *.nix -crlf | ||||
| .husky/pre-commit -crlf | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| .DS_STORE | ||||
| **/.DS_STORE | ||||
| db/ | ||||
| dist/ | ||||
| node_modules | ||||
| @ -19,4 +19,4 @@ build | ||||
| *.tmp | ||||
| tmp/ | ||||
| dump/ | ||||
| result | ||||
| result | ||||
| @ -2,4 +2,5 @@ assets | ||||
| dist | ||||
| node_modules | ||||
| .github | ||||
| .vscode | ||||
| .vscode | ||||
| hashes.json | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     "openapi": "3.1.0", | ||||
|     "info": { | ||||
|         "title": "Spacebar Server", | ||||
|         "description": "Spacebar is a free open source selfhostable discord compatible chat, voice and video platform", | ||||
|         "description": "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.", | ||||
|         "license": { | ||||
|             "name": "AGPLV3", | ||||
|             "url": "https://www.gnu.org/licenses/agpl-3.0.en.html" | ||||
| @ -61,109 +61,208 @@ | ||||
|                     "read_states" | ||||
|                 ] | ||||
|             }, | ||||
|             "DiagnosticsChannel.Response": { | ||||
|             "ConnectedAccountCommonOAuthTokenResponse": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "statusCode": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "statusText": { | ||||
|                     "access_token": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "headers": { | ||||
|                         "type": "array", | ||||
|                         "items": { | ||||
|                             "type": "object", | ||||
|                             "additionalProperties": false, | ||||
|                             "patternProperties": { | ||||
|                                 "^[0-9]+$": { | ||||
|                                     "type": "integer" | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     "token_type": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "scope": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "refresh_token": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "expires_in": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "headers", | ||||
|                     "statusCode", | ||||
|                     "statusText" | ||||
|                     "access_token", | ||||
|                     "scope", | ||||
|                     "token_type" | ||||
|                 ] | ||||
|             }, | ||||
|             "Headers": { | ||||
|             "ApplicationAuthorizeSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "append": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     "authorize": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "delete": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     "guild_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "get": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     "permissions": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "has": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     "captcha_key": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "set": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "getSetCookie": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "forEach": { | ||||
|                         "description": "Performs the specified action for each element in an array.", | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "keys": { | ||||
|                         "description": "Returns an array consisting of the keys of the object", | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "values": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "entries": { | ||||
|                         "description": "Returns an array consisting of the key value pairs of the object", | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "__@iterator": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     "code": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "__@iterator", | ||||
|                     "append", | ||||
|                     "delete", | ||||
|                     "entries", | ||||
|                     "forEach", | ||||
|                     "get", | ||||
|                     "getSetCookie", | ||||
|                     "has", | ||||
|                     "keys", | ||||
|                     "set", | ||||
|                     "values" | ||||
|                     "authorize", | ||||
|                     "guild_id", | ||||
|                     "permissions" | ||||
|                 ] | ||||
|             }, | ||||
|             "ResponseType": { | ||||
|                 "enum": [ | ||||
|                     "basic", | ||||
|                     "cors", | ||||
|                     "default", | ||||
|                     "error", | ||||
|                     "opaque", | ||||
|                     "opaqueredirect" | ||||
|                 ], | ||||
|                 "type": "string" | ||||
|             "ApplicationCreateSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "name": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "team_id": { | ||||
|                         "type": [ | ||||
|                             "string", | ||||
|                             "integer" | ||||
|                         ] | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "name" | ||||
|                 ] | ||||
|             }, | ||||
|             "ApplicationModifySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "description": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "icon": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "interactions_endpoint_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "max_participants": { | ||||
|                         "type": "integer", | ||||
|                         "nullable": true | ||||
|                     }, | ||||
|                     "name": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "privacy_policy_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "role_connections_verification_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "tags": { | ||||
|                         "type": "array", | ||||
|                         "items": { | ||||
|                             "type": "string" | ||||
|                         } | ||||
|                     }, | ||||
|                     "terms_of_service_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "bot_public": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "bot_require_code_grant": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "flags": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "BackupCodesChallengeSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "password": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "password" | ||||
|                 ] | ||||
|             }, | ||||
|             "BanCreateSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "delete_message_seconds": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "delete_message_days": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "reason": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "BanModeratorSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "user_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "guild_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "executor_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "reason": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "executor_id", | ||||
|                     "guild_id", | ||||
|                     "id", | ||||
|                     "user_id" | ||||
|                 ] | ||||
|             }, | ||||
|             "BanRegistrySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "user_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "guild_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "executor_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "ip": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "reason": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "executor_id", | ||||
|                     "guild_id", | ||||
|                     "id", | ||||
|                     "user_id" | ||||
|                 ] | ||||
|             }, | ||||
|             "BotModifySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "avatar": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "username": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "ChannelPermissionOverwriteType": { | ||||
|                 "enum": [ | ||||
| @ -4729,372 +4828,6 @@ | ||||
|                     "webauthn" | ||||
|                 ] | ||||
|             }, | ||||
|             "_Response": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "headers": { | ||||
|                         "$ref": "#/components/schemas/Headers" | ||||
|                     }, | ||||
|                     "ok": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "status": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "statusText": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "type": { | ||||
|                         "$ref": "#/components/schemas/ResponseType" | ||||
|                     }, | ||||
|                     "url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "redirected": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "body": { | ||||
|                         "anyOf": [ | ||||
|                             { | ||||
|                                 "$ref": "#/components/schemas/ReadableStream<any>" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "type": "null" | ||||
|                             } | ||||
|                         ] | ||||
|                     }, | ||||
|                     "bodyUsed": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "arrayBuffer": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "blob": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "formData": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "json": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "text": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "clone": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "arrayBuffer", | ||||
|                     "blob", | ||||
|                     "body", | ||||
|                     "bodyUsed", | ||||
|                     "clone", | ||||
|                     "formData", | ||||
|                     "headers", | ||||
|                     "json", | ||||
|                     "ok", | ||||
|                     "redirected", | ||||
|                     "status", | ||||
|                     "statusText", | ||||
|                     "text", | ||||
|                     "type", | ||||
|                     "url" | ||||
|                 ] | ||||
|             }, | ||||
|             "global.Response": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "headers": { | ||||
|                         "$ref": "#/components/schemas/Headers" | ||||
|                     }, | ||||
|                     "ok": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "status": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "statusText": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "type": { | ||||
|                         "$ref": "#/components/schemas/ResponseType" | ||||
|                     }, | ||||
|                     "url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "redirected": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "body": { | ||||
|                         "anyOf": [ | ||||
|                             { | ||||
|                                 "$ref": "#/components/schemas/ReadableStream<any>" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "type": "null" | ||||
|                             } | ||||
|                         ] | ||||
|                     }, | ||||
|                     "bodyUsed": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "arrayBuffer": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "blob": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "formData": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "json": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "text": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "clone": { | ||||
|                         "type": "object", | ||||
|                         "additionalProperties": false | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "arrayBuffer", | ||||
|                     "blob", | ||||
|                     "body", | ||||
|                     "bodyUsed", | ||||
|                     "clone", | ||||
|                     "formData", | ||||
|                     "headers", | ||||
|                     "json", | ||||
|                     "ok", | ||||
|                     "redirected", | ||||
|                     "status", | ||||
|                     "statusText", | ||||
|                     "text", | ||||
|                     "type", | ||||
|                     "url" | ||||
|                 ] | ||||
|             }, | ||||
|             "ConnectedAccountCommonOAuthTokenResponse": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "access_token": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "token_type": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "scope": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "refresh_token": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "expires_in": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "access_token", | ||||
|                     "scope", | ||||
|                     "token_type" | ||||
|                 ] | ||||
|             }, | ||||
|             "ExpressResponse": { | ||||
|                 "type": "object" | ||||
|             }, | ||||
|             "ApplicationAuthorizeSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "authorize": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "guild_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "permissions": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "captcha_key": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "code": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "authorize", | ||||
|                     "guild_id", | ||||
|                     "permissions" | ||||
|                 ] | ||||
|             }, | ||||
|             "ApplicationCreateSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "name": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "team_id": { | ||||
|                         "type": [ | ||||
|                             "string", | ||||
|                             "integer" | ||||
|                         ] | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "name" | ||||
|                 ] | ||||
|             }, | ||||
|             "ApplicationModifySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "description": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "icon": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "interactions_endpoint_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "max_participants": { | ||||
|                         "type": "integer", | ||||
|                         "nullable": true | ||||
|                     }, | ||||
|                     "name": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "privacy_policy_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "role_connections_verification_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "tags": { | ||||
|                         "type": "array", | ||||
|                         "items": { | ||||
|                             "type": "string" | ||||
|                         } | ||||
|                     }, | ||||
|                     "terms_of_service_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "bot_public": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "bot_require_code_grant": { | ||||
|                         "type": "boolean" | ||||
|                     }, | ||||
|                     "flags": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "BackupCodesChallengeSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "password": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "password" | ||||
|                 ] | ||||
|             }, | ||||
|             "BanCreateSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "delete_message_seconds": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "delete_message_days": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "reason": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "BanModeratorSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "user_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "guild_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "executor_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "reason": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "executor_id", | ||||
|                     "guild_id", | ||||
|                     "id", | ||||
|                     "user_id" | ||||
|                 ] | ||||
|             }, | ||||
|             "BanRegistrySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "user_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "guild_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "executor_id": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "ip": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "reason": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 }, | ||||
|                 "required": [ | ||||
|                     "executor_id", | ||||
|                     "guild_id", | ||||
|                     "id", | ||||
|                     "user_id" | ||||
|                 ] | ||||
|             }, | ||||
|             "BotModifySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "avatar": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "username": { | ||||
|                         "type": "string" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "ChannelPermissionOverwriteSchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
| @ -6184,7 +5917,6 @@ | ||||
|                 "properties": { | ||||
|                     "username": { | ||||
|                         "minLength": 2, | ||||
|                         "maxLength": 32, | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "password": { | ||||
| @ -6600,8 +6332,7 @@ | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "username": { | ||||
|                         "minLength": 1, | ||||
|                         "maxLength": 100, | ||||
|                         "minLength": 2, | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "avatar": { | ||||
| @ -10845,6 +10576,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "updates" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -10857,6 +10594,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "track" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -11119,6 +10862,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "scheduled-maintenances" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -11190,6 +10939,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "policies" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -11209,6 +10964,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "policies" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -11228,6 +10989,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "policies" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -11247,6 +11014,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "policies" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -11266,6 +11039,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "ping" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -17512,6 +17291,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "auth" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -17551,6 +17336,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "auth" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -17669,6 +17460,12 @@ | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "auth" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -18152,6 +17949,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "-" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }, | ||||
| @ -18164,6 +17967,12 @@ | ||||
|                 }, | ||||
|                 "tags": [ | ||||
|                     "-" | ||||
|                 ], | ||||
|                 "x-badges": [ | ||||
|                     { | ||||
|                         "label": "Spacebar-only", | ||||
|                         "color": "red" | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         } | ||||
|  | ||||
							
								
								
									
										69365
									
								
								assets/schemas.json
									
									
									
									
									
								
							
							
						
						
									
										69365
									
								
								assets/schemas.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							| @ -20,11 +20,11 @@ | ||||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1723362943, | ||||
|         "narHash": "sha256-dFZRVSgmJkyM0bkPpaYRtG/kRMRTorUIDj8BxoOt1T4=", | ||||
|         "lastModified": 1723637854, | ||||
|         "narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=", | ||||
|         "owner": "nixos", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "a58bc8ad779655e790115244571758e8de055e3d", | ||||
|         "rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|  | ||||
							
								
								
									
										12
									
								
								flake.nix
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								flake.nix
									
									
									
									
									
								
							| @ -13,11 +13,21 @@ | ||||
|           inherit system; | ||||
|         }; | ||||
|         hashesFile = builtins.fromJSON (builtins.readFile ./hashes.json); | ||||
|         lib = pkgs.lib; | ||||
|       in rec { | ||||
|         packages.default = pkgs.buildNpmPackage { | ||||
|           pname = "spacebar-server-ts"; | ||||
|           src = ./.; | ||||
|           name = "spacebar-server-ts"; | ||||
| 
 | ||||
|           meta = with lib; { | ||||
|             description = "Spacebar server, a FOSS reimplementation of the Discord backend."; | ||||
|             homepage = "https://github.com/spacebarchat/server"; | ||||
|             license = licenses.agpl3Plus; | ||||
|             platforms = platforms.all; | ||||
|             mainProgram = "start-bundle"; | ||||
|           }; | ||||
| 
 | ||||
|           src = ./.; | ||||
|           nativeBuildInputs = with pkgs; [ python3 ]; | ||||
|           npmDepsHash = hashesFile.npmDepsHash; | ||||
|           makeCacheWritable = true; | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| { | ||||
| 	"npmDepsHash": "sha256-kdS1SwcBu6Dor92iO1ickLgz0T5UL16nyA49xXGajf4=" | ||||
| 	"npmDepsHash": "sha256-qcHlktC4qrhOJ6AwKbccPkr0cVrAtPhGK+xD/eV+scU=" | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| #!/usr/bin/env nix-shell | ||||
| #!nix-shell -i "bash -x" -p bash prefetch-npm-deps jq git nix-output-monitor | ||||
| nix flake update | ||||
| nix flake update --extra-experimental-features 'nix-command flakes' | ||||
| DEPS_HASH=`prefetch-npm-deps package-lock.json` | ||||
| TMPFILE=$(mktemp) | ||||
| jq '.npmDepsHash = "'$DEPS_HASH'"' hashes.json > $TMPFILE | ||||
| mv -- "$TMPFILE" hashes.json | ||||
| 
 | ||||
| nom build .# || exit $? | ||||
| git add hashes.json flake.lock flake.nix | ||||
| nom build .# --extra-experimental-features 'nix-command flakes' || exit $? | ||||
| git add hashes.json flake.lock flake.nix | ||||
|  | ||||
							
								
								
									
										3354
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3354
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -117,6 +117,7 @@ | ||||
| 	}, | ||||
| 	"optionalDependencies": { | ||||
| 		"erlpack": "^0.1.4", | ||||
| 		"jimp": "^0.22.12", | ||||
| 		"mysql": "^2.18.1", | ||||
| 		"nodemailer-mailgun-transport": "^2.1.5", | ||||
| 		"nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport", | ||||
|  | ||||
| @ -1,17 +1,17 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 	 | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 	 | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 	 | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| @ -28,15 +28,13 @@ require("missing-native-js-functions"); | ||||
| const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); | ||||
| const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); | ||||
| const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); | ||||
| // const specification = JSON.parse(
 | ||||
| // 	fs.readFileSync(openapiPath, { encoding: "utf8" }),
 | ||||
| // );
 | ||||
| 
 | ||||
| let specification = { | ||||
| 	openapi: "3.1.0", | ||||
| 	info: { | ||||
| 		title: "Spacebar Server", | ||||
| 		description: | ||||
| 			"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform", | ||||
| 			"Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.", | ||||
| 		license: { | ||||
| 			name: "AGPLV3", | ||||
| 			url: "https://www.gnu.org/licenses/agpl-3.0.en.html", | ||||
| @ -68,8 +66,9 @@ let specification = { | ||||
| 	paths: {}, | ||||
| }; | ||||
| 
 | ||||
| const schemaRegEx = new RegExp(/^[\w.]+$/); | ||||
| function combineSchemas(schemas) { | ||||
| 	var definitions = {}; | ||||
| 	let definitions = {}; | ||||
| 
 | ||||
| 	for (const name in schemas) { | ||||
| 		definitions = { | ||||
| @ -84,9 +83,8 @@ function combineSchemas(schemas) { | ||||
| 	} | ||||
| 
 | ||||
| 	for (const key in definitions) { | ||||
| 		const reg = new RegExp(/^[a-zA-Z0-9.\-_]+$/, "gm"); | ||||
| 		if (!reg.test(key)) { | ||||
| 			console.error(`Invalid schema name: ${key} (${reg.test(key)})`); | ||||
| 		if (!schemaRegEx.test(key)) { | ||||
| 			console.error(`Invalid schema name: ${key}`); | ||||
| 			continue; | ||||
| 		} | ||||
| 		specification.components = specification.components || {}; | ||||
| @ -116,7 +114,7 @@ function getTag(key) { | ||||
| 	return key.match(/\/([\w-]+)/)[1]; | ||||
| } | ||||
| 
 | ||||
| function apiRoutes() { | ||||
| function apiRoutes(missingRoutes) { | ||||
| 	const routes = getRouteDescriptions(); | ||||
| 
 | ||||
| 	// populate tags
 | ||||
| @ -157,32 +155,30 @@ function apiRoutes() { | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}.merge(obj.requestBody); | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		if (route.responses) { | ||||
| 			for (const [k, v] of Object.entries(route.responses)) { | ||||
| 				let schema = { | ||||
| 					$ref: `#/components/schemas/${v.body}`, | ||||
| 				}; | ||||
| 			obj.responses = {}; | ||||
| 
 | ||||
| 				obj.responses = { | ||||
| 					[k]: { | ||||
| 						...(v.body | ||||
| 							? { | ||||
| 									description: | ||||
| 										obj?.responses?.[k]?.description || "", | ||||
| 									content: { | ||||
| 										"application/json": { | ||||
| 											schema: schema, | ||||
| 										}, | ||||
| 									}, | ||||
| 							  } | ||||
| 							: { | ||||
| 									description: "No description available", | ||||
| 							  }), | ||||
| 					}, | ||||
| 				}.merge(obj.responses); | ||||
| 			for (const [k, v] of Object.entries(route.responses)) { | ||||
| 				if (v.body) | ||||
| 					obj.responses[k] = { | ||||
| 						description: obj?.responses?.[k]?.description || "", | ||||
| 						content: { | ||||
| 							"application/json": { | ||||
| 								schema: { | ||||
| 									$ref: `#/components/schemas/${v.body}`, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}; | ||||
| 				else | ||||
| 					obj.responses[k] = { | ||||
| 						description: | ||||
| 							obj?.responses?.[k]?.description || | ||||
| 							"No description available", | ||||
| 					}; | ||||
| 			} | ||||
| 		} else { | ||||
| 			obj.responses = { | ||||
| @ -218,6 +214,15 @@ function apiRoutes() { | ||||
| 
 | ||||
| 		obj.tags = [...(obj.tags || []), getTag(p)].unique(); | ||||
| 
 | ||||
| 		if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) { | ||||
| 			obj["x-badges"] = [ | ||||
| 				{ | ||||
| 					label: "Spacebar-only", | ||||
| 					color: "red", | ||||
| 				}, | ||||
| 			]; | ||||
| 		} | ||||
| 
 | ||||
| 		specification.paths[path] = Object.assign( | ||||
| 			specification.paths[path] || {}, | ||||
| 			{ | ||||
| @ -227,10 +232,21 @@ function apiRoutes() { | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function main() { | ||||
| async function main() { | ||||
| 	console.log("Generating OpenAPI Specification..."); | ||||
| 
 | ||||
| 	const routesRes = await fetch( | ||||
| 		"https://github.com/spacebarchat/missing-routes/raw/main/missing.json", | ||||
| 		{ | ||||
| 			headers: { | ||||
| 				Accept: "application/json", | ||||
| 			}, | ||||
| 		}, | ||||
| 	); | ||||
| 	const missingRoutes = await routesRes.json(); | ||||
| 
 | ||||
| 	combineSchemas(schemas); | ||||
| 	apiRoutes(); | ||||
| 	apiRoutes(missingRoutes); | ||||
| 
 | ||||
| 	fs.writeFileSync( | ||||
| 		openapiPath, | ||||
|  | ||||
| @ -1,17 +1,17 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 	 | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 	 | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 	 | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| @ -41,11 +41,16 @@ const Excluded = [ | ||||
| 	"EntitySchema", | ||||
| 	"ServerResponse", | ||||
| 	"Http2ServerResponse", | ||||
| 	"ExpressResponse", | ||||
| 	"global.Express.Response", | ||||
| 	"global.Response", | ||||
| 	"Response", | ||||
| 	"e.Response", | ||||
| 	"request.Response", | ||||
| 	"supertest.Response", | ||||
| 	"DiagnosticsChannel.Response", | ||||
| 	"_Response", | ||||
| 	"ReadableStream<any>", | ||||
| 
 | ||||
| 	// TODO: Figure out how to exclude schemas from node_modules?
 | ||||
| 	"SomeJSONSchema", | ||||
|  | ||||
| @ -34,7 +34,7 @@ import "missing-native-js-functions"; | ||||
| import morgan from "morgan"; | ||||
| import path from "path"; | ||||
| import { red } from "picocolors"; | ||||
| import { Authentication, CORS } from "./middlewares/"; | ||||
| import { Authentication, CORS, ImageProxy } from "./middlewares/"; | ||||
| import { BodyParser } from "./middlewares/BodyParser"; | ||||
| import { ErrorHandler } from "./middlewares/ErrorHandler"; | ||||
| import { initRateLimits } from "./middlewares/RateLimit"; | ||||
| @ -137,6 +137,8 @@ export class SpacebarServer extends Server { | ||||
| 		app.use("/api/v9", api); | ||||
| 		app.use("/api", api); // allow unversioned requests
 | ||||
| 
 | ||||
| 		app.use("/imageproxy/:hash/:size/:url", ImageProxy); | ||||
| 
 | ||||
| 		app.get("/", (req, res) => | ||||
| 			res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")), | ||||
| 		); | ||||
|  | ||||
							
								
								
									
										180
									
								
								src/api/middlewares/ImageProxy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/api/middlewares/ImageProxy.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,180 @@ | ||||
| /* | ||||
|         Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
|         Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 
 | ||||
|         This program is free software: you can redistribute it and/or modify | ||||
|         it under the terms of the GNU Affero General Public License as published | ||||
|         by the Free Software Foundation, either version 3 of the License, or | ||||
|         (at your option) any later version. | ||||
| 
 | ||||
|         This program is distributed in the hope that it will be useful, | ||||
|         but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|         GNU Affero General Public License for more details. | ||||
| 
 | ||||
|         You should have received a copy of the GNU Affero General Public License | ||||
|         along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| import { Config, JimpType } from "@spacebar/util"; | ||||
| import { Request, Response } from "express"; | ||||
| import { yellow } from "picocolors"; | ||||
| import crypto from "crypto"; | ||||
| import fetch from "node-fetch"; | ||||
| 
 | ||||
| let sharp: undefined | false | { default: typeof import("sharp") } = undefined; | ||||
| 
 | ||||
| let Jimp: JimpType | undefined = undefined; | ||||
| try { | ||||
| 	Jimp = require("jimp") as JimpType; | ||||
| } catch { | ||||
| 	// empty
 | ||||
| } | ||||
| 
 | ||||
| let sentImageProxyWarning = false; | ||||
| 
 | ||||
| const sharpSupported = new Set([ | ||||
| 	"image/jpeg", | ||||
| 	"image/png", | ||||
| 	"image/bmp", | ||||
| 	"image/tiff", | ||||
| 	"image/gif", | ||||
| 	"image/webp", | ||||
| 	"image/avif", | ||||
| 	"image/svg+xml", | ||||
| ]); | ||||
| const jimpSupported = new Set([ | ||||
| 	"image/jpeg", | ||||
| 	"image/png", | ||||
| 	"image/bmp", | ||||
| 	"image/tiff", | ||||
| 	"image/gif", | ||||
| ]); | ||||
| const resizeSupported = new Set([...sharpSupported, ...jimpSupported]); | ||||
| 
 | ||||
| export async function ImageProxy(req: Request, res: Response) { | ||||
| 	const path = req.originalUrl.split("/").slice(2); | ||||
| 
 | ||||
| 	// src/api/util/utility/EmbedHandlers.ts getProxyUrl
 | ||||
| 	const hash = crypto | ||||
| 		.createHmac("sha1", Config.get().security.requestSignature) | ||||
| 		.update(path.slice(1).join("/")) | ||||
| 		.digest("base64") | ||||
| 		.replace(/\+/g, "-") | ||||
| 		.replace(/\//g, "_"); | ||||
| 
 | ||||
| 	try { | ||||
| 		if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) | ||||
| 			throw new Error("Invalid signature"); | ||||
| 	} catch { | ||||
| 		console.log("Invalid signature, expected " + hash + " got " + path[0]); | ||||
| 		res.status(403).send("Invalid signature"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	const abort = new AbortController(); | ||||
| 	setTimeout(() => abort.abort(), 5000); | ||||
| 
 | ||||
| 	const request = await fetch(path.slice(2).join("/"), { | ||||
| 		headers: { | ||||
| 			"User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)", | ||||
| 		}, | ||||
| 		signal: abort.signal, | ||||
| 	}).catch((e) => { | ||||
| 		if (e.name === "AbortError") res.status(504).send("Request timed out"); | ||||
| 		else res.status(500).send("Unable to proxy origin: " + e.message); | ||||
| 	}); | ||||
| 	if (!request) return; | ||||
| 
 | ||||
| 	if (request.status !== 200) { | ||||
| 		res.status(request.status).send( | ||||
| 			"Origin failed to respond: " + | ||||
| 				request.status + | ||||
| 				" " + | ||||
| 				request.statusText, | ||||
| 		); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if ( | ||||
| 		!request.headers.get("Content-Type") || | ||||
| 		!request.headers.get("Content-Length") | ||||
| 	) { | ||||
| 		res.status(500).send( | ||||
| 			"Origin did not provide a Content-Type or Content-Length header", | ||||
| 		); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
 | ||||
| 	if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) { | ||||
| 		res.status(500).send( | ||||
| 			"Origin provided a Content-Length header that is too large", | ||||
| 		); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	// @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
 | ||||
| 	let contentType: string = request.headers.get("Content-Type"); | ||||
| 
 | ||||
| 	const arrayBuffer = await request.arrayBuffer(); | ||||
| 	let resultBuffer = Buffer.from(arrayBuffer); | ||||
| 
 | ||||
| 	if ( | ||||
| 		!sentImageProxyWarning && | ||||
| 		resizeSupported.has(contentType) && | ||||
| 		/^\d+x\d+$/.test(path[1]) | ||||
| 	) { | ||||
| 		if (sharp !== false) { | ||||
| 			try { | ||||
| 				sharp = await import("sharp"); | ||||
| 			} catch { | ||||
| 				sharp = false; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if (sharp === false && !Jimp) { | ||||
| 			try { | ||||
| 				// eslint-disable-next-line @typescript-eslint/ban-ts-comment
 | ||||
| 				// @ts-ignore Typings don't fit
 | ||||
| 				Jimp = await import("jimp"); | ||||
| 			} catch { | ||||
| 				sentImageProxyWarning = true; | ||||
| 				console.log( | ||||
| 					`[ImageProxy] ${yellow( | ||||
| 						'Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled', | ||||
| 					)}`,
 | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		const [width, height] = path[1].split("x").map((x) => parseInt(x)); | ||||
| 
 | ||||
| 		const buffer = Buffer.from(arrayBuffer); | ||||
| 		if (sharp && sharpSupported.has(contentType)) { | ||||
| 			resultBuffer = await sharp | ||||
| 				.default(buffer) | ||||
| 				// Sharp doesn't support "scaleToFit"
 | ||||
| 				.resize(width) | ||||
| 				.toBuffer(); | ||||
| 		} else if (Jimp && jimpSupported.has(contentType)) { | ||||
| 			resultBuffer = await Jimp.read(buffer).then((image) => { | ||||
| 				contentType = image.getMIME(); | ||||
| 				return ( | ||||
| 					image | ||||
| 						.scaleToFit(width, height) | ||||
| 						// @ts-expect-error Jimp is defined at this point
 | ||||
| 						.getBufferAsync(Jimp.AUTO) | ||||
| 				); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	res.header("Content-Type", contentType); | ||||
| 	res.setHeader( | ||||
| 		"Cache-Control", | ||||
| 		"public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds, | ||||
| 	); | ||||
| 
 | ||||
| 	res.send(resultBuffer); | ||||
| } | ||||
| @ -21,3 +21,4 @@ export * from "./BodyParser"; | ||||
| export * from "./CORS"; | ||||
| export * from "./ErrorHandler"; | ||||
| export * from "./RateLimit"; | ||||
| export * from "./ImageProxy"; | ||||
|  | ||||
| @ -1,31 +1,24 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 	 | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 	 | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 	 | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; | ||||
| import { | ||||
| 	Config, | ||||
| 	Email, | ||||
| 	FieldErrors, | ||||
| 	ForgotPasswordSchema, | ||||
| 	User, | ||||
| } from "@spacebar/util"; | ||||
| import { Config, Email, ForgotPasswordSchema, User } from "@spacebar/util"; | ||||
| import { Request, Response, Router } from "express"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.post( | ||||
| @ -37,9 +30,6 @@ router.post( | ||||
| 			400: { | ||||
| 				body: "APIErrorOrCaptchaResponse", | ||||
| 			}, | ||||
| 			500: { | ||||
| 				body: "APIErrorResponse", | ||||
| 			}, | ||||
| 		}, | ||||
| 	}), | ||||
| 	async (req: Request, res: Response) => { | ||||
| @ -71,50 +61,20 @@ router.post( | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		const user = await User.findOneOrFail({ | ||||
| 		res.sendStatus(204); | ||||
| 
 | ||||
| 		const user = await User.findOne({ | ||||
| 			where: [{ phone: login }, { email: login }], | ||||
| 			select: ["username", "id", "disabled", "deleted", "email"], | ||||
| 			relations: ["security_keys"], | ||||
| 		}).catch(() => { | ||||
| 			throw FieldErrors({ | ||||
| 				login: { | ||||
| 					message: req.t("auth:password_reset.EMAIL_DOES_NOT_EXIST"), | ||||
| 					code: "EMAIL_DOES_NOT_EXIST", | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
| 			select: ["username", "id", "email"], | ||||
| 		}).catch(() => {}); | ||||
| 
 | ||||
| 		if (!user.email) | ||||
| 			throw FieldErrors({ | ||||
| 				login: { | ||||
| 					message: | ||||
| 						"This account does not have an email address associated with it.", | ||||
| 					code: "NO_EMAIL", | ||||
| 				}, | ||||
| 			}); | ||||
| 
 | ||||
| 		if (user.deleted) | ||||
| 			return res.status(400).json({ | ||||
| 				message: "This account is scheduled for deletion.", | ||||
| 				code: 20011, | ||||
| 			}); | ||||
| 
 | ||||
| 		if (user.disabled) | ||||
| 			return res.status(400).json({ | ||||
| 				message: req.t("auth:login.ACCOUNT_DISABLED"), | ||||
| 				code: 20013, | ||||
| 			}); | ||||
| 
 | ||||
| 		return await Email.sendResetPassword(user, user.email) | ||||
| 			.then(() => { | ||||
| 				return res.sendStatus(204); | ||||
| 			}) | ||||
| 			.catch((e) => { | ||||
| 		if (user && user.email) { | ||||
| 			Email.sendResetPassword(user, user.email).catch((e) => { | ||||
| 				console.error( | ||||
| 					`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`, | ||||
| 					`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`, | ||||
| 				); | ||||
| 				throw new HTTPError("Failed to send password reset email", 500); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| ); | ||||
| 
 | ||||
|  | ||||
| @ -23,7 +23,9 @@ import { | ||||
| 	DiscordApiErrors, | ||||
| 	emitEvent, | ||||
| 	Message, | ||||
| 	MessageCreateEvent, | ||||
| 	MessageUpdateEvent, | ||||
| 	User, | ||||
| } from "@spacebar/util"; | ||||
| import { Request, Response, Router } from "express"; | ||||
| 
 | ||||
| @ -61,6 +63,30 @@ router.put( | ||||
| 
 | ||||
| 		message.pinned = true; | ||||
| 
 | ||||
| 		const author = await User.getPublicUser(req.user_id); | ||||
| 
 | ||||
| 		const systemPinMessage = Message.create({ | ||||
| 			timestamp: new Date(), | ||||
| 			type: 6, | ||||
| 			guild_id: message.guild_id, | ||||
| 			channel_id: message.channel_id, | ||||
| 			author, | ||||
| 			message_reference: { | ||||
| 				message_id: message.id, | ||||
| 				channel_id: message.channel_id, | ||||
| 				guild_id: message.guild_id, | ||||
| 			}, | ||||
| 			reactions: [], | ||||
| 			attachments: [], | ||||
| 			embeds: [], | ||||
| 			sticker_items: [], | ||||
| 			edited_timestamp: undefined, | ||||
| 			mentions: [], | ||||
| 			mention_channels: [], | ||||
| 			mention_roles: [], | ||||
| 			mention_everyone: false, | ||||
| 		}); | ||||
| 
 | ||||
| 		await Promise.all([ | ||||
| 			message.save(), | ||||
| 			emitEvent({ | ||||
| @ -77,6 +103,12 @@ router.put( | ||||
| 					last_pin_timestamp: undefined, | ||||
| 				}, | ||||
| 			} as ChannelPinsUpdateEvent), | ||||
| 			systemPinMessage.save(), | ||||
| 			emitEvent({ | ||||
| 				event: "MESSAGE_CREATE", | ||||
| 				channel_id: message.channel_id, | ||||
| 				data: systemPinMessage, | ||||
| 			} as MessageCreateEvent), | ||||
| 		]); | ||||
| 
 | ||||
| 		res.sendStatus(204); | ||||
|  | ||||
| @ -17,9 +17,15 @@ | ||||
| */ | ||||
| 
 | ||||
| import { random, route } from "@spacebar/api"; | ||||
| import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util"; | ||||
| import { | ||||
| 	Channel, | ||||
| 	DiscordApiErrors, | ||||
| 	Guild, | ||||
| 	Invite, | ||||
| 	Member, | ||||
| 	Permissions, | ||||
| } from "@spacebar/util"; | ||||
| import { Request, Response, Router } from "express"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| 
 | ||||
| const router: Router = Router(); | ||||
| 
 | ||||
| @ -46,14 +52,14 @@ router.get( | ||||
| 	}), | ||||
| 	async (req: Request, res: Response) => { | ||||
| 		const { guild_id } = req.params; | ||||
| 
 | ||||
|      | ||||
| 		const guild = await Guild.findOneOrFail({ | ||||
| 			where: { id: guild_id }, | ||||
| 			select: { | ||||
| 				channel_ordering: true, | ||||
| 			}, | ||||
| 		}); | ||||
| 		if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); | ||||
| 		if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; | ||||
| 
 | ||||
| 		// Fetch existing widget invite for widget channel
 | ||||
| 		let invite = await Invite.findOne({ | ||||
|  | ||||
| @ -1,17 +1,17 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 	 | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 	 | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 	 | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| @ -19,11 +19,12 @@ | ||||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||||
| 
 | ||||
| import { route } from "@spacebar/api"; | ||||
| import { Guild } from "@spacebar/util"; | ||||
| import { DiscordApiErrors, Guild } from "@spacebar/util"; | ||||
| import { Request, Response, Router } from "express"; | ||||
| import fs from "fs"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| import path from "path"; | ||||
| import { storage } from "../../../../cdn/util/Storage"; | ||||
| 
 | ||||
| const router: Router = Router(); | ||||
| 
 | ||||
| @ -48,10 +49,10 @@ router.get( | ||||
| 		const { guild_id } = req.params; | ||||
| 
 | ||||
| 		const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); | ||||
| 		if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404); | ||||
| 		if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; | ||||
| 
 | ||||
| 		// Fetch guild information
 | ||||
| 		const icon = guild.icon; | ||||
| 		const icon = "avatars/" + guild_id + "/" + guild.icon; | ||||
| 		const name = guild.name; | ||||
| 		const presence = guild.presence_count + " ONLINE"; | ||||
| 
 | ||||
| @ -69,8 +70,7 @@ router.get( | ||||
| 		} | ||||
| 
 | ||||
| 		// Setup canvas
 | ||||
| 		const { createCanvas } = require("canvas"); | ||||
| 		const { loadImage } = require("canvas"); | ||||
| 		const { createCanvas, loadImage } = require("canvas"); | ||||
| 		const sizeOf = require("image-size"); | ||||
| 
 | ||||
| 		// TODO: Widget style templates need Spacebar branding
 | ||||
| @ -211,8 +211,8 @@ async function drawIcon( | ||||
| 	scale: number, | ||||
| 	icon: string, | ||||
| ) { | ||||
| 	const img = new (require("canvas").Image)(); | ||||
| 	img.src = icon; | ||||
| 	const { loadImage } = require("canvas"); | ||||
| 	const img = await loadImage(await storage.get(icon)); | ||||
| 
 | ||||
| 	// Do some canvas clipping magic!
 | ||||
| 	canvas.save(); | ||||
|  | ||||
| @ -1,17 +1,17 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 	 | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 	 | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 	 | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| @ -25,4 +25,6 @@ export class CdnConfiguration extends EndpointConfiguration { | ||||
| 
 | ||||
| 	endpointPublic: string | null = null; | ||||
| 	endpointPrivate: string | null = null; | ||||
| 
 | ||||
| 	proxyCacheHeaderSeconds: number = 60 * 60 * 24; | ||||
| } | ||||
|  | ||||
| @ -290,7 +290,9 @@ export class Member extends BaseClassWithoutId { | ||||
| 			}, | ||||
| 			relations: ["user"], | ||||
| 		}); | ||||
| 		member.nick = nickname; | ||||
| 
 | ||||
| 		// @ts-expect-error Member nickname is nullable
 | ||||
| 		member.nick = nickname || null; | ||||
| 
 | ||||
| 		await Promise.all([ | ||||
| 			member.save(), | ||||
| @ -300,7 +302,7 @@ export class Member extends BaseClassWithoutId { | ||||
| 				data: { | ||||
| 					guild_id, | ||||
| 					user: member.user, | ||||
| 					nick: nickname, | ||||
| 					nick: nickname || null, | ||||
| 				}, | ||||
| 				guild_id, | ||||
| 			} as GuildMemberUpdateEvent), | ||||
|  | ||||
| @ -1,17 +1,17 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 	 | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 	 | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 	 | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
|  | ||||
							
								
								
									
										23
									
								
								src/util/imports/Jimp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/util/imports/Jimp.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| /* | ||||
| 	Spacebar: A FOSS re-implementation and extension of the Discord.com backend. | ||||
| 	Copyright (C) 2023 Spacebar and Spacebar Contributors | ||||
| 
 | ||||
| 	This program is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU Affero General Public License as published | ||||
| 	by the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	This program is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU Affero General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Affero General Public License | ||||
| 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||||
| 
 | ||||
| export type JimpType = { | ||||
| 	read: (data: Buffer) => Promise<any>; | ||||
| }; | ||||
| @ -18,3 +18,4 @@ | ||||
| 
 | ||||
| export * from "./OrmUtils"; | ||||
| export * from "./Erlpack"; | ||||
| export * from "./Jimp"; | ||||
|  | ||||
| @ -812,7 +812,7 @@ export const DiscordApiErrors = { | ||||
| 		"Cannot execute action on a DM channel", | ||||
| 		50003, | ||||
| 	), | ||||
| 	EMBED_DISABLED: new ApiError("Guild widget disabled", 50004), | ||||
| 	EMBED_DISABLED: new ApiError("Widget Disabled", 50004), | ||||
| 	CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError( | ||||
| 		"Cannot edit a message authored by another user", | ||||
| 		50005, | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								src/webrtc/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/webrtc/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Madeline
						Madeline