discord_embed: Finished initial version of discord_embed
This commit is contained in:
parent
bcc4dfe745
commit
e737909b45
7 changed files with 149 additions and 24 deletions
1
discord_embed/.npmrc
Normal file
1
discord_embed/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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">
|
||||||
|
</form>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Alternatively, you can generate a link yourself using the following syntax:
|
Alternatively, copy paste the following full url:
|
||||||
</p>
|
</p>
|
||||||
<pre>
|
<pre id="generateurl">
|
||||||
{{=siteUrlBase}}?v=<video link>&i=<image link>
|
{{=siteUrlBase}}?v=<video link>&i=<image link>
|
||||||
</pre>
|
</pre>
|
||||||
</form>
|
<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>
|
||||||
|
|
Loading…
Reference in a new issue