discord_embed: Finished initial version of discord_embed

master
Jonatan Nilsson 2022-10-17 14:13:20 +00:00
parent bcc4dfe745
commit e737909b45
7 changed files with 149 additions and 24 deletions

1
discord_embed/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -12,7 +12,7 @@
**/ **/
const AlphabeticID = { const AlphabeticID = {
index:'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', index:'WzPmtXQhwGnOiB8Vvu9fC1SL2l4F7MrUNc0RbD6dAayI3YTosejpZxJH5KqgEk', // abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
/** /**
* [@function](https://twitter.com/function) AlphabeticID.encode * [@function](https://twitter.com/function) AlphabeticID.encode

View File

@ -1,11 +1,13 @@
import { uploadMedia } from '../base/media/upload.mjs' import { uploadMedia, deleteFile } from '../base/media/upload.mjs'
import config from '../base/config.mjs' import config from '../base/config.mjs'
import AlphabeticID from './id.mjs'
export default class IndexPost { export default class IndexPost {
constructor(opts = {}) { constructor(opts = {}) {
Object.assign(this, { Object.assign(this, {
frontend: opts.frontend, frontend: opts.frontend,
uploadMedia: uploadMedia, uploadMedia: uploadMedia,
deleteFile: deleteFile,
}) })
} }
@ -44,12 +46,41 @@ export default class IndexPost {
} }
} }
/** PUT: /api/auth/articles/:id */ async getLink(ctx) {
async createNewLink(ctx) {
let hasMedia = ctx.req.files.media && ctx.req.files.media.size
return this.serve.serveIndex(ctx)
}
/** POST: / */
async createNewLink(ctx) {
ctx.state.video = ctx.req.body.video ctx.state.video = ctx.req.body.video
ctx.state.image = ctx.req.body.image ctx.state.image = ctx.req.body.image
let rateLimited = false
let redisKey = 'ratelimit_' + ctx.req.ip.replace(/:/g, '-')
try {
let val = (await ctx.redis.get(redisKey))
console.log(redisKey, val)
val = val && Number(val) || 0
if (val > 3) {
rateLimited = true
} else if (val > 2) {
await ctx.redis.setex(redisKey, 60 * 15, val + 1)
rateLimited = true
} else {
await ctx.redis.setex(redisKey, 15, val + 1)
}
} catch (err) {
ctx.log.error(err, 'Error checking rate limit for ' + redisKey)
}
if (rateLimited) {
ctx.state.error = 'You have reached rate limit. Please wait at least 15 minutes.'
return this.serve.serveIndex(ctx)
}
let hasMedia = ctx.req.files.media && ctx.req.files.media.size
let redirect = '' let redirect = ''
let error = this.hasErrors(ctx, hasMedia) let error = this.hasErrors(ctx, hasMedia)
@ -57,6 +88,10 @@ export default class IndexPost {
try { try {
let temp = await this.uploadMedia(ctx.req.files.media) let temp = await this.uploadMedia(ctx.req.files.media)
ctx.state.image = ctx.req.body.image = 'https://cdn.nfp.is' + temp.sizes.small.jpeg.path ctx.state.image = ctx.req.body.image = 'https://cdn.nfp.is' + temp.sizes.small.jpeg.path
await this.deleteFile(temp.filename).catch(err => {
ctx.log.error(err, 'Error removing ' + temp.filename)
})
} }
catch (err) { catch (err) {
ctx.log.error(err) ctx.log.error(err)
@ -70,9 +105,13 @@ export default class IndexPost {
if (!error) { if (!error) {
try { try {
let params = [ let params = [
ctx.state.video,
ctx.state.image,
ctx.req.ip,
] ]
let res = await ctx.db.safeCallProc('discord_embed.link_add', params) let res = await ctx.db.safeCallProc('discord_embed.link_add', params)
console.log(res) let id = AlphabeticID.encode(res.first[0].id + 3843)
redirect = `${this.frontend}/${id}`
} }
catch (err) { catch (err) {
ctx.log.error(err) ctx.log.error(err)

View File

@ -4,6 +4,7 @@ import fs from 'fs/promises'
import fsSync from 'fs' import fsSync from 'fs'
import dot from 'dot' import dot from 'dot'
import config from '../base/config.mjs' import config from '../base/config.mjs'
import AlphabeticID from './id.mjs'
export default class ServeHandler extends Parent { export default class ServeHandler extends Parent {
loadTemplate(indexFile) { loadTemplate(indexFile) {
@ -27,12 +28,31 @@ export default class ServeHandler extends Parent {
this.loadTemplate(indexFile) this.loadTemplate(indexFile)
} }
let videoLink = ctx.query.get('v') || ''
let imageLink = ctx.query.get('i') || ''
if (ctx.url.match(/^\/[a-zA-Z0-9][a-zA-Z0-9][a-zA-Z0-9]+$/)) {
try {
let 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) {
videoLink = res.first[0].video_link
imageLink = res.first[0].image_link
}
}
} catch (err) {
ctx.log.error(err)
ctx.state.error = 'Unknown error while fetching link.'
}
}
let payload = { let payload = {
imageLink: ctx.query.get('i') || '', videoLink: videoLink,
videoLink: ctx.query.get('v') || '', imageLink: imageLink,
error: ctx.state.error || '', error: ctx.state.error || '',
inputVideo: ctx.state.video || ctx.query.get('v') || '', inputVideo: ctx.state.video || videoLink || '',
inputImage: ctx.state.image || ctx.query.get('i') || '', inputImage: ctx.state.image || imageLink || '',
siteUrl: this.frontend + ctx.url, siteUrl: this.frontend + ctx.url,
siteUrlBase: this.frontend + '/', siteUrlBase: this.frontend + '/',
version: this.version, version: this.version,

View File

@ -1,3 +1,4 @@
import Redis from 'ioredis'
import config from '../base/config.mjs' import config from '../base/config.mjs'
import Parent from '../base/server.mjs' import Parent from '../base/server.mjs'
import IndexPost from './post.mjs' import IndexPost from './post.mjs'
@ -8,6 +9,12 @@ export default class Server extends Parent {
super.init() super.init()
let localUtil = new this.core.sc.Util(import.meta.url) let localUtil = new this.core.sc.Util(import.meta.url)
this.flaskaOptions.appendHeaders['Content-Security-Policy'] = `default-src 'self'; script-src 'self' 'nonce-0d1valZOnjp8ZpR6vBd4dg=='; style-src 'self' 'unsafe-inline'; img-src * data: blob:; media-src *; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`
this.flaskaOptions.appendHeaders['Cross-Origin-Embedder-Policy'] = 'unsafe-none'
this.redis = new Redis(config.get('redis'))
this.redis.on('error', (err) => {
this.core.log.error(err)
})
this.routes = { this.routes = {
post: new IndexPost({ post: new IndexPost({
frontend: config.get('frontend:url'), frontend: config.get('frontend:url'),
@ -19,4 +26,13 @@ export default class Server extends Parent {
frontend: config.get('frontend:url'), frontend: config.get('frontend:url'),
}) })
} }
runCreateServer() {
super.runCreateServer()
this.flaska.before((ctx) => {
ctx.redis = this.redis
})
}
} }

View File

@ -35,6 +35,7 @@
}, },
"homepage": "https://git.nfp.is/nfp/nfp_sites", "homepage": "https://git.nfp.is/nfp/nfp_sites",
"dependencies": { "dependencies": {
"bunyan-lite": "^1.2.1",
"dot": "^2.0.0-beta.1", "dot": "^2.0.0-beta.1",
"flaska": "^1.3.0", "flaska": "^1.3.0",
"formidable": "^1.2.6", "formidable": "^1.2.6",

View File

@ -47,6 +47,7 @@ body {
text-rendering: optimizeSpeed; text-rendering: optimizeSpeed;
line-height: 1.5; line-height: 1.5;
font-size: 16px; font-size: 16px;
padding: 1rem 0.5rem;
font-family: sans-serif; font-family: sans-serif;
background: var(--bg); background: var(--bg);
color: var(--color); color: var(--color);
@ -57,16 +58,16 @@ input, button, textarea, select {
} }
h1 { h1 {
font-size: 2.488rem; font-size: 1.88rem;
} }
h2 { h2 {
font-size: 2.074rem; font-size: 1.66rem;
} }
h3 { h3 {
font-size: 1.728rem; font-size: 1.44rem;
} }
h4 { h4 {
font-size: 1.44rem; font-size: 1.22rem;
} }
h5 { h5 {
font-size: 1.0rem; font-size: 1.0rem;
@ -123,6 +124,8 @@ input[type=submit] {
pre { pre {
background: var(--bg-content-alt); background: var(--bg-content-alt);
padding: 0.5rem; padding: 0.5rem;
white-space: break-spaces;
word-break: break-all;
} }
.row { .row {
@ -150,6 +153,15 @@ pre {
padding: 1rem 1rem 0; padding: 1rem 1rem 0;
} }
.center {
text-align: center;
}
video {
max-width: calc(100vw - 2rem);
max-height: calc(100vh - 140px);
}
.inside { .inside {
width: 100%; width: 100%;
max-width: var(--content-max-width); max-width: var(--content-max-width);
@ -162,15 +174,24 @@ pre {
</style> </style>
</head> </head>
<body> <body>
{{ if (videoLink && imageLink) { }}
<p>Your link is:</p>
<pre>{{=siteUrl}}</pre>
<div class="center">
<video controls poster="{{=imageLink}}">
<source src="{{=videoLink}}">
</video>
</div>
{{ } }}
<form action="/" method="post" enctype="multipart/form-data" class="inside"> <form action="/" method="post" enctype="multipart/form-data" class="inside">
<h1>Create/generate embed url</h1> <h1>Create/generate embed url</h1>
{{ if (error) { }}<p class="error">{{=error}}</p>{{ } }} {{ if (error) { }}<p class="error">{{=error}}</p>{{ } }}
<label>Video link</label> <label>Video link</label>
<input type="text" name="video" value="{{=inputVideo}}"> <input id="inputVideo" type="text" name="video" value="{{=inputVideo}}">
<div class="row"> <div class="row">
<div class="row-item"> <div class="row-item">
<label>Image link (required for proper discord embed)</label> <label>Image link (required for proper discord embed)</label>
<input type="text" name="image" value="{{=inputImage}}"> <input id="inputImage" type="text" name="image" value="{{=inputImage}}">
</div> </div>
<span class="row-inbetween">or</span> <span class="row-inbetween">or</span>
<div class="row-item"> <div class="row-item">
@ -179,14 +200,41 @@ pre {
</div> </div>
</div> </div>
<input type="submit" value="Generate embed url"> <input type="submit" value="Generate short url">
<p>
Alternatively, you can generate a link yourself using the following syntax:
</p>
<pre>
{{=siteUrlBase}}?v=&lt;video link&gt;&amp;i=&lt;image link&gt;
</pre>
</form> </form>
<p>
Alternatively, copy paste the following full url:
</p>
<pre id="generateurl">
{{=siteUrlBase}}?v=&lt;video link&gt;&amp;i=&lt;image link&gt;
</pre>
<script type="text/javascript" nonce="{{=nonce}}">
var baseSite = '{{=siteUrlBase}}';
var generateurl = document.getElementById('generateurl');
var inputVideo = document.getElementById('inputVideo');
var inputImage = document.getElementById('inputImage');
var isExample = true;
function checkChange() {
var currentIsExample = true;
if (inputVideo.value && inputImage.value) {
currentIsExample = false;
}
if (isExample && currentIsExample) { return; }
isExample = currentIsExample;
if (isExample) {
generateurl.innerText = baseSite + '?v=<video link>&i=<image link>';
} else {
generateurl.innerText = baseSite + '?v=' + encodeURI(inputVideo.value) + '&i=' + encodeURI(inputImage.value);
}
};
inputVideo.addEventListener('change', checkChange);
inputImage.addEventListener('change', checkChange);
inputVideo.addEventListener('keyup', checkChange);
inputImage.addEventListener('keyup', checkChange);
checkChange()
</script>
</body> </body>
</html> </html>