🚧 webhook
This commit is contained in:
		
							parent
							
								
									8f862f0e5d
								
							
						
					
					
						commit
						df2b83ac15
					
				| @ -1770,10 +1770,6 @@ | ||||
|             } | ||||
|         }, | ||||
|         "additionalProperties": false, | ||||
|         "required": [ | ||||
|             "avatar", | ||||
|             "name" | ||||
|         ], | ||||
|         "definitions": { | ||||
|             "ChannelType": { | ||||
|                 "enum": [ | ||||
| @ -7446,5 +7442,256 @@ | ||||
|             } | ||||
|         }, | ||||
|         "$schema": "http://json-schema.org/draft-07/schema#" | ||||
|     }, | ||||
|     "WebhookModifySchema": { | ||||
|         "type": "object", | ||||
|         "properties": { | ||||
|             "name": { | ||||
|                 "type": "string" | ||||
|             }, | ||||
|             "avatar": { | ||||
|                 "type": "string" | ||||
|             } | ||||
|         }, | ||||
|         "additionalProperties": false, | ||||
|         "definitions": { | ||||
|             "ChannelType": { | ||||
|                 "enum": [ | ||||
|                     0, | ||||
|                     1, | ||||
|                     10, | ||||
|                     11, | ||||
|                     12, | ||||
|                     13, | ||||
|                     2, | ||||
|                     3, | ||||
|                     4, | ||||
|                     5, | ||||
|                     6 | ||||
|                 ], | ||||
|                 "type": "number" | ||||
|             }, | ||||
|             "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" | ||||
|                             } | ||||
|                         }, | ||||
|                         "additionalProperties": false, | ||||
|                         "required": [ | ||||
|                             "text" | ||||
|                         ] | ||||
|                     }, | ||||
|                     "image": { | ||||
|                         "$ref": "#/definitions/EmbedImage" | ||||
|                     }, | ||||
|                     "thumbnail": { | ||||
|                         "$ref": "#/definitions/EmbedImage" | ||||
|                     }, | ||||
|                     "video": { | ||||
|                         "$ref": "#/definitions/EmbedImage" | ||||
|                     }, | ||||
|                     "provider": { | ||||
|                         "type": "object", | ||||
|                         "properties": { | ||||
|                             "name": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "url": { | ||||
|                                 "type": "string" | ||||
|                             } | ||||
|                         }, | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "author": { | ||||
|                         "type": "object", | ||||
|                         "properties": { | ||||
|                             "name": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "url": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "icon_url": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "proxy_icon_url": { | ||||
|                                 "type": "string" | ||||
|                             } | ||||
|                         }, | ||||
|                         "additionalProperties": false | ||||
|                     }, | ||||
|                     "fields": { | ||||
|                         "type": "array", | ||||
|                         "items": { | ||||
|                             "type": "object", | ||||
|                             "properties": { | ||||
|                                 "name": { | ||||
|                                     "type": "string" | ||||
|                                 }, | ||||
|                                 "value": { | ||||
|                                     "type": "string" | ||||
|                                 }, | ||||
|                                 "inline": { | ||||
|                                     "type": "boolean" | ||||
|                                 } | ||||
|                             }, | ||||
|                             "additionalProperties": false, | ||||
|                             "required": [ | ||||
|                                 "name", | ||||
|                                 "value" | ||||
|                             ] | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 "additionalProperties": false | ||||
|             }, | ||||
|             "EmbedImage": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "proxy_url": { | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "height": { | ||||
|                         "type": "integer" | ||||
|                     }, | ||||
|                     "width": { | ||||
|                         "type": "integer" | ||||
|                     } | ||||
|                 }, | ||||
|                 "additionalProperties": false | ||||
|             }, | ||||
|             "ChannelModifySchema": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "name": { | ||||
|                         "maxLength": 100, | ||||
|                         "type": "string" | ||||
|                     }, | ||||
|                     "type": { | ||||
|                         "$ref": "#/definitions/ChannelType" | ||||
|                     }, | ||||
|                     "topic": { | ||||
|                         "type": "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": "bigint" | ||||
|                                 }, | ||||
|                                 "deny": { | ||||
|                                     "type": "bigint" | ||||
|                                 } | ||||
|                             }, | ||||
|                             "additionalProperties": false, | ||||
|                             "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" | ||||
|                     } | ||||
|                 }, | ||||
|                 "additionalProperties": false, | ||||
|                 "required": [ | ||||
|                     "name", | ||||
|                     "type" | ||||
|                 ] | ||||
|             }, | ||||
|             "RelationshipType": { | ||||
|                 "enum": [ | ||||
|                     1, | ||||
|                     2, | ||||
|                     3, | ||||
|                     4 | ||||
|                 ], | ||||
|                 "type": "number" | ||||
|             } | ||||
|         }, | ||||
|         "$schema": "http://json-schema.org/draft-07/schema#" | ||||
|     } | ||||
| } | ||||
| @ -11,7 +11,7 @@ | ||||
| 			window.__OVERLAY__ = /overlay/.test(location.pathname); | ||||
| 			window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname); | ||||
| 			window.GLOBAL_ENV = { | ||||
| 				API_ENDPOINT: "/api", | ||||
| 				API_ENDPOINT: `//${location.host}/api`, | ||||
| 				API_VERSION: 9, | ||||
| 				GATEWAY_ENDPOINT: `${location.protocol === "https:" ? "wss://" : "ws://"}${location.hostname}:3002`, | ||||
| 				WEBAPP_ENDPOINT: "", | ||||
|  | ||||
| @ -5,11 +5,11 @@ import { checkToken, Config } from "@fosscord/util"; | ||||
| export const NO_AUTHORIZATION_ROUTES = [ | ||||
| 	"/auth/login", | ||||
| 	"/auth/register", | ||||
| 	"/webhooks/", | ||||
| 	"/ping", | ||||
| 	"/gateway", | ||||
| 	"/experiments", | ||||
| 	/\/guilds\/\d+\/widget\.(json|png)/ | ||||
| 	/\/guilds\/\d+\/widget\.(json|png)/, | ||||
| 	/\/webhooks\/\d+\/\w+/ // only exclude webhook calls with webhook token
 | ||||
| ]; | ||||
| 
 | ||||
| export const API_PREFIX = /^\/api(\/v\d+)?/; | ||||
|  | ||||
| @ -1,9 +1,10 @@ | ||||
| import { NextFunction, Request, Response } from "express"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| import { EntityNotFoundError } from "typeorm"; | ||||
| import { FieldError } from "@fosscord/api"; | ||||
| import { ApiError } from "@fosscord/util"; | ||||
| 
 | ||||
| const EntityNotFoundErrorRegex = /"(\w+)"/; | ||||
| 
 | ||||
| export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { | ||||
| 	if (!error) return next(); | ||||
| 
 | ||||
| @ -18,8 +19,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne | ||||
| 			code = error.code; | ||||
| 			message = error.message; | ||||
| 			httpcode = error.httpStatus; | ||||
| 		} else if (error instanceof EntityNotFoundError) { | ||||
| 			message = `${(error as any).stringifyTarget || "Item"} could not be found`; | ||||
| 		} else if (error.name === "EntityNotFoundError") { | ||||
| 			message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; | ||||
| 			code = 404; | ||||
| 		} else if (error instanceof FieldError) { | ||||
| 			code = Number(error.code); | ||||
|  | ||||
| @ -10,7 +10,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	// ! this only works using SQL querys
 | ||||
| 	// TODO: implement this with default typeorm query
 | ||||
| 	// const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) });
 | ||||
| 	const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit)) }); | ||||
| 	const guilds = await Guild.find({ where: `"features" LIKE 'COMMUNITY'`, take: Math.abs(Number(limit) || 50) }); | ||||
| 	res.send({ guilds: guilds }); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										10
									
								
								api/src/routes/guilds/#guild_id/integrations.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								api/src/routes/guilds/#guild_id/integrations.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| import { route } from "@fosscord/api"; | ||||
| import { Router, Request, Response } from "express"; | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| 	// TODO: integrations (followed channels, youtube, twitch)
 | ||||
| 	res.send([]); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
| @ -4,7 +4,7 @@ import { Router, Request, Response } from "express"; | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.get("/", async (req: Request, res: Response) => { | ||||
| 	res.send({}); | ||||
| 	res.json({}); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
|  | ||||
							
								
								
									
										89
									
								
								api/src/routes/webhooks/#webhook_id/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								api/src/routes/webhooks/#webhook_id/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| import { Channel, Config, emitEvent, JWTOptions, Webhook, WebhooksUpdateEvent } from "@fosscord/util"; | ||||
| import { route, Authentication, handleFile } from "@fosscord/api"; | ||||
| import { Router, Request, Response, NextFunction } from "express"; | ||||
| import jwt from "jsonwebtoken"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| const router = Router(); | ||||
| 
 | ||||
| export interface WebhookModifySchema { | ||||
| 	name?: string; | ||||
| 	avatar?: string; | ||||
| 	// channel_id?: string; // TODO
 | ||||
| } | ||||
| 
 | ||||
| function validateWebhookToken(req: Request, res: Response, next: NextFunction) { | ||||
| 	const { jwtSecret } = Config.get().security; | ||||
| 
 | ||||
| 	jwt.verify(req.params.token, jwtSecret, JWTOptions, async (err, decoded: any) => { | ||||
| 		if (err) return next(new HTTPError("Invalid Token", 401)); | ||||
| 		next(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); | ||||
| }); | ||||
| 
 | ||||
| router.get("/:token", route({}), validateWebhookToken, async (req: Request, res: Response) => { | ||||
| 	res.json(await Webhook.findOneOrFail({ id: req.params.webhook_id })); | ||||
| }); | ||||
| 
 | ||||
| router.patch("/", route({ body: "WebhookModifySchema", permission: "MANAGE_WEBHOOKS" }), (req: Request, res: Response) => { | ||||
| 	return updateWebhook(req, res); | ||||
| }); | ||||
| 
 | ||||
| router.patch("/:token", route({ body: "WebhookModifySchema" }), validateWebhookToken, (req: Request, res: Response) => { | ||||
| 	return updateWebhook(req, res); | ||||
| }); | ||||
| 
 | ||||
| async function updateWebhook(req: Request, res: Response) { | ||||
| 	const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); | ||||
| 	if (req.body.channel_id) await Channel.findOneOrFail({ id: req.body.channel_id, guild_id: webhook.guild_id }); | ||||
| 
 | ||||
| 	webhook.assign({ | ||||
| 		...req.body, | ||||
| 		avatar: await handleFile(`/icons/${req.params.webhook_id}`, req.body.avatar) | ||||
| 	}); | ||||
| 
 | ||||
| 	await Promise.all([ | ||||
| 		emitEvent({ | ||||
| 			event: "WEBHOOKS_UPDATE", | ||||
| 			channel_id: webhook.channel_id, | ||||
| 			data: { | ||||
| 				channel_id: webhook.channel_id, | ||||
| 				guild_id: webhook.guild_id | ||||
| 			} | ||||
| 		} as WebhooksUpdateEvent), | ||||
| 		webhook.save() | ||||
| 	]); | ||||
| 
 | ||||
| 	res.json(webhook); | ||||
| } | ||||
| 
 | ||||
| router.delete("/", route({ permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { | ||||
| 	return deleteWebhook(req, res); | ||||
| }); | ||||
| 
 | ||||
| router.delete("/:token", route({}), validateWebhookToken, (req: Request, res: Response) => { | ||||
| 	return deleteWebhook(req, res); | ||||
| }); | ||||
| 
 | ||||
| async function deleteWebhook(req: Request, res: Response) { | ||||
| 	const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); | ||||
| 
 | ||||
| 	await Promise.all([ | ||||
| 		emitEvent({ | ||||
| 			event: "WEBHOOKS_UPDATE", | ||||
| 			channel_id: webhook.channel_id, | ||||
| 			data: { | ||||
| 				channel_id: webhook.channel_id, | ||||
| 				guild_id: webhook.guild_id | ||||
| 			} | ||||
| 		} as WebhooksUpdateEvent), | ||||
| 		webhook.remove() | ||||
| 	]); | ||||
| 
 | ||||
| 	res.sendStatus(204); | ||||
| } | ||||
| 
 | ||||
| export default router; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; | ||||
| import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions, Webhook } from "@fosscord/util"; | ||||
| import { NextFunction, Request, Response } from "express"; | ||||
| import fs from "fs"; | ||||
| import path from "path"; | ||||
| @ -54,9 +54,13 @@ export function route(opts: RouteOptions) { | ||||
| 	return async (req: Request, res: Response, next: NextFunction) => { | ||||
| 		if (opts.permission) { | ||||
| 			const required = new Permissions(opts.permission); | ||||
| 			if (req.params.webhook_id) { | ||||
| 				const webhook = await Webhook.findOneOrFail({ id: req.params.webhook_id }); | ||||
| 				req.params.channel_id = webhook.channel_id; | ||||
| 				req.params.guild_id = webhook.guild_id; | ||||
| 			} | ||||
| 			const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); | ||||
| 
 | ||||
| 			// bitfield comparison: check if user lacks certain permission
 | ||||
| 			if (!permission.has(required)) { | ||||
| 				throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string); | ||||
| 			} | ||||
|  | ||||
| @ -18,13 +18,13 @@ export class Webhook extends BaseClass { | ||||
| 	@Column({ type: "simple-enum", enum: WebhookType }) | ||||
| 	type: WebhookType; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	name?: string; | ||||
| 	@Column() | ||||
| 	name: string; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	avatar?: string; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	@Column({ nullable: true, select: false }) | ||||
| 	token?: string; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| export const DOUBLE_WHITE_SPACE = /\s\s+/g; | ||||
| export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; | ||||
| export const SPECIAL_CHAR = /[@#\r\n\t\f\v]/gu; | ||||
| export const CHANNEL_MENTION = /<#(\d+)>/g; | ||||
| export const USER_MENTION = /<@!?(\d+)>/g; | ||||
| export const ROLE_MENTION = /<@&(\d+)>/g; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Flam3rboy
						Flam3rboy