diff --git a/discord_embed/api/post.mjs b/discord_embed/api/post.mjs index 01776d6..6270d50 100644 --- a/discord_embed/api/post.mjs +++ b/discord_embed/api/post.mjs @@ -2,6 +2,8 @@ import { uploadMedia, deleteFile } from '../base/media/upload.mjs' import config from '../base/config.mjs' import AlphabeticID from './id.mjs' +const ImageRegex = /\.(jpg|png|gif)(\?|$)/ + export default class IndexPost { constructor(opts = {}) { Object.assign(this, { @@ -16,40 +18,6 @@ export default class IndexPost { server.flaska.post('/', [ server.formidable({ maxFileSize: 8 * 1024 * 1024, }), ], this.createNewLink.bind(this)) - server.flaska.get('/video/:id', this.videoRedirect.bind(this)) - } - - async videoRedirect(ctx) { - try { - let id = AlphabeticID.decode(ctx.params.id.slice(0,-5)) - let videoLink = null - if (id) { - let res = await ctx.db.safeCallProc('discord_embed.link_get', [id - 3843]) - if (res.first.length) { - videoLink = res.first[0].video_link - } else { - ctx.status = 404 - } - } - - if (videoLink) { - ctx.status = 302 - ctx.headers['Location'] = videoLink - ctx.type = 'text/html; charset=utf-8' - ctx.body = ` -Redirecting -Click here if it doesn't redirect -` - return - } else { - ctx.status = 404 - ctx.state.error = 'Video not found.' - } - } catch (err) { - ctx.log.error(err, 'Unable to fetch resource ' + ctx.url.slice(1)) - ctx.state.error = 'Unknown error while fetching link.' - } - return this.serve.serveIndex(ctx) } hasErrors(ctx, hasMedia) { @@ -60,6 +28,24 @@ Redirecting if (!ctx.req.body.video.startsWith('http')) { return 'Video link has to be a valid full url' } + + if (ImageRegex.exec(ctx.req.body.video)) { + return 'Image links are not gonna work, that is not how embedding video works. What are you even doing mate?' + } + + if (ctx.req.body.width) { + let n = Number(ctx.req.body.width) + if (isNaN(n) || n < 10 || n > 5000) { + return 'The video width does not look right' + } + } + + if (ctx.req.body.height) { + let n = Number(ctx.req.body.height) + if (isNaN(n) || n < 10 || n > 3000) { + return 'The video height does not look right' + } + } if (ctx.req.body.image) { if (!ctx.req.body.image.startsWith('http')) { @@ -77,6 +63,8 @@ Redirecting async createNewLink(ctx) { ctx.state.video = ctx.req.body.video ctx.state.image = ctx.req.body.image || 'https://cdn.nfp.is/av1/empty.png' + ctx.state.width = ctx.req.body.width || null + ctx.state.height = ctx.req.body.height || null let rateLimited = false let redisKey = 'ratelimit_' + ctx.req.ip.replace(/:/g, '-') @@ -129,6 +117,8 @@ Redirecting ctx.state.video, ctx.state.image, ctx.req.ip, + ctx.state.width, + ctx.state.height, ] let res = await ctx.db.safeCallProc('discord_embed.link_add', params) let id = AlphabeticID.encode(res.first[0].id + 3843) diff --git a/discord_embed/api/serve.mjs b/discord_embed/api/serve.mjs index 6cb514d..41b70dc 100644 --- a/discord_embed/api/serve.mjs +++ b/discord_embed/api/serve.mjs @@ -7,11 +7,15 @@ import dot from 'dot' import config from '../base/config.mjs' import AlphabeticID from './id.mjs' +const ExpirationRegex = /ex=([0-9a-fA-F]{8})/ + export default class ServeHandler extends Parent { loadTemplate(indexFile) { this.template = dot.template(indexFile.toString(), { argName: [ 'imageLink', 'videoLink', + 'width', + 'height', 'error', 'siteUrl', 'siteUrlBase', @@ -19,12 +23,28 @@ export default class ServeHandler extends Parent { 'nonce', 'in_debug', 'inputVideo', - 'inputImage' + 'inputImage', + 'inputWidth', + 'inputHeight' ], strip: false }) } + async refreshUrl(link) { + var res = await fetch('https://discord.com/api/v10/attachments/refresh-urls', { + method: 'POST', + headers: { + 'Authorization': `Bot ${config.get('discord_token')}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ attachment_urls: [link] }) + }) + let output = await res.json() + return output?.refreshed_urls?.[0].refreshed + } + register(server) { super.register(server) + server.flaska.get('/video/:id', this.videoRedirect.bind(this)) server.flaska.onerror(this.serveError.bind(this)) } @@ -41,13 +61,19 @@ export default class ServeHandler extends Parent { let videoLink = ctx.query.get('v') || '' let imageLink = ctx.query.get('i') || '' + let width = ctx.query.get('w') || 1280 + let height = ctx.query.get('h') || 720 ctx.body = this.template({ videoLink: videoLink, imageLink: imageLink, + width: width, + height: height, error: ctx.state.error || '', inputVideo: ctx.state.video || videoLink || '', inputImage: ctx.state.image || imageLink || '', + inputWidth: width, + inputHeight: height, siteUrl: this.frontend + ctx.url, siteUrlBase: this.frontend + '/', version: this.version, @@ -57,6 +83,39 @@ export default class ServeHandler extends Parent { ctx.type = 'text/html; charset=utf-8' } + async videoRedirect(ctx) { + try { + let id = AlphabeticID.decode(ctx.params.id.slice(0,-5)) + let videoLink = null + if (id) { + let res = await ctx.db.safeCallProc('discord_embed.link_get', [id - 3843]) + if (res.first.length) { + videoLink = res.first[0].video_link + } else { + ctx.status = 404 + } + } + + if (videoLink) { + ctx.status = 302 + ctx.headers['Location'] = videoLink + ctx.type = 'text/html; charset=utf-8' + ctx.body = ` +Redirecting +Click here if it doesn't redirect +` + return + } else { + ctx.status = 404 + ctx.state.error = 'Video not found.' + } + } catch (err) { + ctx.log.error(err, 'Unable to fetch resource ' + ctx.url.slice(1)) + ctx.state.error = 'Unknown error while fetching link.' + } + return this.serveIndex(ctx) + } + async serveIndex(ctx) { if (config.get('NODE_ENV') === 'development') { let indexFile = await fs.readFile(path.join(this.root, 'index.html')) @@ -65,11 +124,14 @@ export default class ServeHandler extends Parent { let videoLink = ctx.query.get('v') || '' let imageLink = ctx.query.get('i') || (videoLink ? 'https://cdn.nfp.is/av1/empty.png' : '') + let width = ctx.query.get('w') || 1280 + let height = ctx.query.get('h') || 720 + let id = null if (!ctx.state.error) { if (ctx.url.match(/^\/[a-zA-Z0-9][a-zA-Z0-9][a-zA-Z0-9]+$/) && ctx.url.length < 7) { try { - let id = AlphabeticID.decode(ctx.url.slice(1)) + id = AlphabeticID.decode(ctx.url.slice(1)) if (id) { let res = await ctx.db.safeCallProc('discord_embed.link_get', [id - 3843]) if (res.first.length) { @@ -80,6 +142,8 @@ export default class ServeHandler extends Parent { videoLink = this.frontend + '/video/' + ctx.url.slice(1) + '.webm' } imageLink = res.first[0].image_link + width = res.first[0].width || width + height = res.first[0].height || height } else { ctx.status = 404 } @@ -94,15 +158,40 @@ export default class ServeHandler extends Parent { } if (videoLink.startsWith('https://cdn.discordapp.com')) { + if (id) { + let match = ExpirationRegex.exec(videoLink) + if (match && match[1]) { + try { + let expiration = Number('0x' + match[1]) * 1000 + if (new Date() > new Date(expiration)) { + let newLink = await this.refreshUrl(videoLink) + if (newLink) { + ctx.log.info({ + old: videoLink, + new: newLink, + }, 'Updating link') + videoLink = newLink + await ctx.db.safeCallProc('discord_embed.link_update', [id - 3843, videoLink]) + } + } + } catch (err) { + ctx.log.error(err) + } + } + } videoLink = videoLink.replace('https://cdn.discordapp.com', 'https://discordproxy.nfp.is') } let payload = { videoLink: videoLink, imageLink: imageLink, + width: width, + height: height, error: ctx.state.error || '', inputVideo: ctx.state.video || videoLink || '', inputImage: ctx.state.image || imageLink || '', + inputWidth: width, + inputHeight: height, siteUrl: this.frontend + ctx.url, siteUrlBase: this.frontend + '/', version: this.version, diff --git a/discord_embed/api/server.mjs b/discord_embed/api/server.mjs index f3475e8..1d59a48 100644 --- a/discord_embed/api/server.mjs +++ b/discord_embed/api/server.mjs @@ -20,7 +20,7 @@ export default class Server extends Parent { static: new StaticRoutes(), post: new IndexPost({ frontend: config.get('frontend:url'), - }) + }), } this.routes.serve = new ServeHandler({ root: localUtil.getPathFromRoot('../public'), @@ -32,7 +32,6 @@ export default class Server extends Parent { runCreateServer() { super.runCreateServer() - this.flaska.before((ctx) => { ctx.redis = this.redis }) diff --git a/discord_embed/public/index.html b/discord_embed/public/index.html index f035738..5f06ef2 100644 --- a/discord_embed/public/index.html +++ b/discord_embed/public/index.html @@ -9,8 +9,8 @@ - - + + {{ } else { }} @@ -264,6 +264,18 @@ pre { +
Test area (does the video load and play? If not, it might not work on discord)
@@ -287,6 +299,8 @@ pre { var generateurl = document.getElementById('generateurl'); var inputVideo = document.getElementById('inputVideo'); var inputImage = document.getElementById('inputImage'); + var inputWidth = document.getElementById('inputWidth'); + var inputHeight = document.getElementById('inputHeight'); var previewVideo = document.getElementById('previewVideo'); var isExample = true; previewVideo.poster = defaultPoster; @@ -302,7 +316,10 @@ pre { generateurl.innerText = baseSite + '?v=