Compare commits

..

3 commits

Author SHA1 Message Date
89ea760ce3 discord_embed: package rename name
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
2022-10-17 14:14:33 +00:00
e737909b45 discord_embed: Finished initial version of discord_embed 2022-10-17 14:13:20 +00:00
bcc4dfe745 base: add url to client error message 2022-10-17 14:12:58 +00:00
8 changed files with 151 additions and 25 deletions

View file

@ -77,6 +77,7 @@ export default class Client {
}
if (!options.getRaw && output.status && typeof(output.status) === 'number') {
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
err.url = path
err.body = output
return reject(err)
}

1
discord_embed/.npmrc Normal file
View file

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

View file

@ -12,7 +12,7 @@
**/
const AlphabeticID = {
index:'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
index:'WzPmtXQhwGnOiB8Vvu9fC1SL2l4F7MrUNc0RbD6dAayI3YTosejpZxJH5KqgEk', // abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
/**
* [@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 AlphabeticID from './id.mjs'
export default class IndexPost {
constructor(opts = {}) {
Object.assign(this, {
frontend: opts.frontend,
uploadMedia: uploadMedia,
deleteFile: deleteFile,
})
}
@ -44,12 +46,41 @@ export default class IndexPost {
}
}
/** PUT: /api/auth/articles/:id */
async createNewLink(ctx) {
let hasMedia = ctx.req.files.media && ctx.req.files.media.size
async getLink(ctx) {
return this.serve.serveIndex(ctx)
}
/** POST: / */
async createNewLink(ctx) {
ctx.state.video = ctx.req.body.video
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 error = this.hasErrors(ctx, hasMedia)
@ -57,6 +88,10 @@ export default class IndexPost {
try {
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
await this.deleteFile(temp.filename).catch(err => {
ctx.log.error(err, 'Error removing ' + temp.filename)
})
}
catch (err) {
ctx.log.error(err)
@ -70,9 +105,13 @@ export default class IndexPost {
if (!error) {
try {
let params = [
ctx.state.video,
ctx.state.image,
ctx.req.ip,
]
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) {
ctx.log.error(err)

View file

@ -4,6 +4,7 @@ import fs from 'fs/promises'
import fsSync from 'fs'
import dot from 'dot'
import config from '../base/config.mjs'
import AlphabeticID from './id.mjs'
export default class ServeHandler extends Parent {
loadTemplate(indexFile) {
@ -27,12 +28,31 @@ export default class ServeHandler extends Parent {
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 = {
imageLink: ctx.query.get('i') || '',
videoLink: ctx.query.get('v') || '',
videoLink: videoLink,
imageLink: imageLink,
error: ctx.state.error || '',
inputVideo: ctx.state.video || ctx.query.get('v') || '',
inputImage: ctx.state.image || ctx.query.get('i') || '',
inputVideo: ctx.state.video || videoLink || '',
inputImage: ctx.state.image || imageLink || '',
siteUrl: this.frontend + ctx.url,
siteUrlBase: this.frontend + '/',
version: this.version,

View file

@ -1,3 +1,4 @@
import Redis from 'ioredis'
import config from '../base/config.mjs'
import Parent from '../base/server.mjs'
import IndexPost from './post.mjs'
@ -8,6 +9,12 @@ export default class Server extends Parent {
super.init()
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 = {
post: new IndexPost({
frontend: config.get('frontend:url'),
@ -19,4 +26,13 @@ export default class Server extends Parent {
frontend: config.get('frontend:url'),
})
}
runCreateServer() {
super.runCreateServer()
this.flaska.before((ctx) => {
ctx.redis = this.redis
})
}
}

View file

@ -1,5 +1,5 @@
{
"name": "av1_embed",
"name": "discord_embed",
"version": "1.0.0",
"port": 4120,
"description": "AV1 discord server embed helper",
@ -35,6 +35,7 @@
},
"homepage": "https://git.nfp.is/nfp/nfp_sites",
"dependencies": {
"bunyan-lite": "^1.2.1",
"dot": "^2.0.0-beta.1",
"flaska": "^1.3.0",
"formidable": "^1.2.6",

View file

@ -47,6 +47,7 @@ body {
text-rendering: optimizeSpeed;
line-height: 1.5;
font-size: 16px;
padding: 1rem 0.5rem;
font-family: sans-serif;
background: var(--bg);
color: var(--color);
@ -57,16 +58,16 @@ input, button, textarea, select {
}
h1 {
font-size: 2.488rem;
font-size: 1.88rem;
}
h2 {
font-size: 2.074rem;
font-size: 1.66rem;
}
h3 {
font-size: 1.728rem;
font-size: 1.44rem;
}
h4 {
font-size: 1.44rem;
font-size: 1.22rem;
}
h5 {
font-size: 1.0rem;
@ -123,6 +124,8 @@ input[type=submit] {
pre {
background: var(--bg-content-alt);
padding: 0.5rem;
white-space: break-spaces;
word-break: break-all;
}
.row {
@ -150,6 +153,15 @@ pre {
padding: 1rem 1rem 0;
}
.center {
text-align: center;
}
video {
max-width: calc(100vw - 2rem);
max-height: calc(100vh - 140px);
}
.inside {
width: 100%;
max-width: var(--content-max-width);
@ -162,15 +174,24 @@ pre {
</style>
</head>
<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">
<h1>Create/generate embed url</h1>
{{ if (error) { }}<p class="error">{{=error}}</p>{{ } }}
<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-item">
<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>
<span class="row-inbetween">or</span>
<div class="row-item">
@ -179,14 +200,41 @@ pre {
</div>
</div>
<input type="submit" value="Generate embed 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>
<input type="submit" value="Generate short url">
</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>
</html>