summary refs log tree commit diff
path: root/api/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/util')
-rw-r--r--api/src/util/handlers/Message.ts115
-rw-r--r--api/src/util/index.ts1
-rw-r--r--api/src/util/utility/captcha.ts46
3 files changed, 133 insertions, 29 deletions
diff --git a/api/src/util/handlers/Message.ts b/api/src/util/handlers/Message.ts

index 48f87dfe..a6754bd1 100644 --- a/api/src/util/handlers/Message.ts +++ b/api/src/util/handlers/Message.ts
@@ -54,26 +54,26 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { channel_id: opts.channel_id, attachments: opts.attachments || [], embeds: opts.embeds || [], - reactions: /*opts.reactions ||*/ [], + reactions: /*opts.reactions ||*/[], type: opts.type ?? 0 }); if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { - throw new HTTPError("Content length over max character limit") + throw new HTTPError("Content length over max character limit"); } if (opts.author_id) { message.author = await User.getPublicUser(opts.author_id); const rights = await getRights(opts.author_id); rights.hasThrow("SEND_MESSAGES"); - } + } if (opts.application_id) { message.application = await Application.findOneOrFail({ id: opts.application_id }); } if (opts.webhook_id) { message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id }); } - + const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id); permission.hasThrow("SEND_MESSAGES"); if (permission.cache.member) { @@ -152,6 +152,8 @@ export async function postHandleMessage(message: Message) { links = links.slice(0, 20); // embed max 20 links — TODO: make this configurable with instance policies + const { endpointPublic, resizeWidthMax, resizeHeightMax } = Config.get().cdn; + for (const link of links) { try { const request = await fetch(link, { @@ -159,33 +161,88 @@ export async function postHandleMessage(message: Message) { size: Config.get().limits.message.maxEmbedDownloadSize, }); - const text = await request.text(); - const $ = cheerio.load(text); - - const title = $('meta[property="og:title"]').attr("content"); - const provider_name = $('meta[property="og:site_name"]').text(); - const author_name = $('meta[property="article:author"]').attr("content"); - const description = $('meta[property="og:description"]').attr("content") || $('meta[property="description"]').attr("content"); - const image = $('meta[property="og:image"]').attr("content"); - const url = $('meta[property="og:url"]').attr("content"); - // TODO: color - const embed: Embed = { - provider: { - url: link, - name: provider_name - } - }; - - if (author_name) embed.author = { name: author_name }; - if (image) embed.thumbnail = { proxy_url: image, url: image }; - if (title) embed.title = title; - if (url) embed.url = url; - if (description) embed.description = description; + let embed: Embed; - if (title || description) { + const type = request.headers.get("content-type"); + if (type?.indexOf("image") == 0) { + embed = { + provider: { + url: link, + name: new URL(link).hostname, + }, + image: { + // can't be bothered rn + proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(link)}?width=500&height=400`, + url: link, + width: 500, + height: 400 + } + }; data.embeds.push(embed); } - } catch (error) {} + else { + const text = await request.text(); + const $ = cheerio.load(text); + + const title = $('meta[property="og:title"]').attr("content"); + const provider_name = $('meta[property="og:site_name"]').text(); + const author_name = $('meta[property="article:author"]').attr("content"); + const description = $('meta[property="og:description"]').attr("content") || $('meta[property="description"]').attr("content"); + + const image = $('meta[property="og:image"]').attr("content"); + const width = parseInt($('meta[property="og:image:width"]').attr("content") || "") || undefined; + const height = parseInt($('meta[property="og:image:height"]').attr("content") || "") || undefined; + + const url = $('meta[property="og:url"]').attr("content"); + // TODO: color + embed = { + provider: { + url: link, + name: provider_name + } + }; + + const resizeWidth = Math.min(resizeWidthMax ?? 1, width ?? 100); + const resizeHeight = Math.min(resizeHeightMax ?? 1, height ?? 100); + if (author_name) embed.author = { name: author_name }; + if (image) embed.thumbnail = { + proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(image)}?width=${resizeWidth}&height=${resizeHeight}`, + url: image, + width: width, + height: height + }; + if (title) embed.title = title; + if (url) embed.url = url; + if (description) embed.description = description; + + const approvedProviders = [ + "media4.giphy.com", + "c.tenor.com", + // todo: make configurable? don't really care tho + ]; + + // very bad code below + // don't care lol + if (embed?.thumbnail?.url && approvedProviders.indexOf(new URL(embed.thumbnail.url).hostname) !== -1) { + embed = { + provider: { + url: link, + name: new URL(link).hostname, + }, + image: { + proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(image!)}?width=${resizeWidth}&height=${resizeHeight}`, + url: image, + width: width, + height: height + } + }; + } + + if (title || description) { + data.embeds.push(embed); + } + } + } catch (error) { } } await Promise.all([ @@ -206,7 +263,7 @@ export async function sendMessage(opts: MessageOptions) { emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent) ]); - postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly + postHandleMessage(message).catch((e) => { }); // no await as it should catch error non-blockingly return message; } diff --git a/api/src/util/index.ts b/api/src/util/index.ts
index ffbcf24e..de6b6064 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts
@@ -6,3 +6,4 @@ export * from "./utility/RandomInviteID"; export * from "./handlers/route"; export * from "./utility/String"; export * from "./handlers/Voice"; +export * from "./utility/captcha"; \ No newline at end of file diff --git a/api/src/util/utility/captcha.ts b/api/src/util/utility/captcha.ts new file mode 100644
index 00000000..739647d2 --- /dev/null +++ b/api/src/util/utility/captcha.ts
@@ -0,0 +1,46 @@ +import { Config } from "@fosscord/util"; +import fetch from "node-fetch"; + +export interface hcaptchaResponse { + success: boolean; + challenge_ts: string; + hostname: string; + credit: boolean; + "error-codes": string[]; + score: number; // enterprise only + score_reason: string[]; // enterprise only +} + +export interface recaptchaResponse { + success: boolean; + score: number; // between 0 - 1 + action: string; + challenge_ts: string; + hostname: string; + "error-codes"?: string[]; +} + +const verifyEndpoints = { + hcaptcha: "https://hcaptcha.com/siteverify", + recaptcha: "https://www.google.com/recaptcha/api/siteverify", +} + +export async function verifyCaptcha(response: string, ip?: string) { + const { security } = Config.get(); + const { service, secret, sitekey } = security.captcha; + + if (!service) throw new Error("Cannot verify captcha without service"); + + const res = await fetch(verifyEndpoints[service], { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: `response=${encodeURIComponent(response)}` + + `&secret=${encodeURIComponent(secret!)}` + + `&sitekey=${encodeURIComponent(sitekey!)}` + + (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""), + }); + + return await res.json() as hcaptchaResponse | recaptchaResponse; +} \ No newline at end of file