diff --git a/base/media/upload.mjs b/base/media/upload.mjs index b53688b..406ceb1 100644 --- a/base/media/upload.mjs +++ b/base/media/upload.mjs @@ -15,20 +15,27 @@ export function uploadMedia(file) { } } + console.log(media) + + let body = {} + + if (media.preview) { + body.preview = media.preview + } + if (media.small?.avif) { + body.small = media.small.avif + } + if (media.medium?.avif) { + body.medium = media.medium.avif + } + if (media.large?.avif) { + body.large = media.large.avif + } + return client.upload(media.path + '?token=' + token, { file: { file: file.path, filename: file.name, - } }, 'POST', { - preview: media.preview, - small: media.small?.avif, - medium: media.medium?.avif, - large: media.large?.avif, - /* - - small: media.small.jpeg, - medium: media.medium.avif, - large_jpeg: mediakl.large.jpeg,*/ - }).then(res => { + } }, 'POST', body).then(res => { out.filename = res.filename out.path = res.path out.preview = res.preview @@ -38,11 +45,18 @@ export function uploadMedia(file) { out.size = file.size out.type = file.type - return client.post(media.path + '/' + out.filename + '?token=' + token, { - small: media.small?.jpeg, - medium: media.medium?.jpeg, - large: media.large?.jpeg, - }) + let body = {} + if (media.small?.jpeg) { + body.small = media.small.jpeg + } + if (media.medium?.jpeg) { + body.medium = media.medium.jpeg + } + if (media.large?.jpeg) { + body.large = media.large.jpeg + } + + return client.post(media.path + '/' + out.filename + '?token=' + token, body) .then(res => { if (out.sizes.small) { out.sizes.small.jpeg = res.small } if (out.sizes.medium) { out.sizes.medium.jpeg = res.medium } diff --git a/base/serve.mjs b/base/serve.mjs index 98af5f4..0dd859c 100644 --- a/base/serve.mjs +++ b/base/serve.mjs @@ -22,8 +22,11 @@ export default class ServeHandler { } let indexFile = fsSync.readFileSync(path.join(this.root, 'index.html')) + this.loadTemplate(indexFile) + } + + loadTemplate(indexFile) { this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner', 'media', 'in_debug'] }) - // console.log(indexFile.toString()) } register(server) { diff --git a/base/server.mjs b/base/server.mjs index 52a9df4..95bf258 100644 --- a/base/server.mjs +++ b/base/server.mjs @@ -53,6 +53,10 @@ export default class Server { this.flaska.before(function(ctx) { ctx.state.started = new Date().getTime() + ctx.req.ip = ctx.req.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress + ctx.log = ctx.log.child({ + id: Math.random().toString(36).substring(2, 14), + }) ctx.db = pool }) this.flaska.before(QueryHandler()) @@ -92,6 +96,7 @@ export default class Server { ctx.log[level]({ duration: requestTime, status: ctx.status, + ip: ctx.req.ip, }, (ctx.aborted ? '[ABORT]' : '<--') + ` ${status}${ctx.method} ${ctx.url}`) }) } diff --git a/discord_embed/api/id.mjs b/discord_embed/api/id.mjs new file mode 100644 index 0000000..1496857 --- /dev/null +++ b/discord_embed/api/id.mjs @@ -0,0 +1,85 @@ +/** + * Javascript AlphabeticID class + * (based on a script by Kevin van Zonneveld ) + * + * Author: Even Simon + * + * Description: Translates a numeric identifier into a short string and backwords. + * + * Usage: + * var str = AlphabeticID.encode(9007199254740989); // str = 'fE2XnNGpF' + * var id = AlphabeticID.decode('fE2XnNGpF'); // id = 9007199254740989; + **/ + +const AlphabeticID = { + index:'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', + + /** + * [@function](https://twitter.com/function) AlphabeticID.encode + * [@description](https://twitter.com/description) Encode a number into short string + * [@param](https://twitter.com/param) integer + * [@return](https://twitter.com/return) string + **/ + encode:function(_number){ + if('undefined' == typeof _number){ + return null; + } + else if('number' != typeof(_number)){ + throw new Error('Wrong parameter type'); + } + + var ret = ''; + + for(var i=Math.floor(Math.log(parseInt(_number))/Math.log(AlphabeticID.index.length));i>=0;i--){ + ret = ret + AlphabeticID.index.substr((Math.floor(parseInt(_number) / AlphabeticID.bcpow(AlphabeticID.index.length, i)) % AlphabeticID.index.length),1); + } + + return reverse(ret); + }, + + /** + * [@function](https://twitter.com/function) AlphabeticID.decode + * [@description](https://twitter.com/description) Decode a short string and return number + * [@param](https://twitter.com/param) string + * [@return](https://twitter.com/return) integer + **/ + decode:function(_string){ + if('undefined' == typeof _string){ + return null; + } + else if('string' != typeof _string){ + throw new Error('Wrong parameter type'); + } + + var str = reverse(_string); + var ret = 0; + + for(var i=0;i<=(str.length - 1);i++){ + ret = ret + AlphabeticID.index.indexOf(str.substr(i,1)) * (AlphabeticID.bcpow(AlphabeticID.index.length, (str.length - 1) - i)); + } + + return ret; + }, + + /** + * [@function](https://twitter.com/function) AlphabeticID.bcpow + * [@description](https://twitter.com/description) Raise _a to the power _b + * [@param](https://twitter.com/param) float _a + * [@param](https://twitter.com/param) integer _b + * [@return](https://twitter.com/return) string + **/ + bcpow:function(_a, _b){ + return Math.floor(Math.pow(parseFloat(_a), parseInt(_b))); + } +}; + +/** + * [@function](https://twitter.com/function) String.reverse + * [@description](https://twitter.com/description) Reverse a string + * [@return](https://twitter.com/return) string + **/ +function reverse(str){ + return str.split('').reverse().join(''); +}; + +export default AlphabeticID diff --git a/discord_embed/api/post.mjs b/discord_embed/api/post.mjs new file mode 100644 index 0000000..40aa7a1 --- /dev/null +++ b/discord_embed/api/post.mjs @@ -0,0 +1,96 @@ +import { uploadMedia } from '../base/media/upload.mjs' +import config from '../base/config.mjs' + +export default class IndexPost { + constructor(opts = {}) { + Object.assign(this, { + frontend: opts.frontend, + uploadMedia: uploadMedia, + }) + } + + register(server) { + this.serve = server.routes.serve + server.flaska.post('/', [ + server.formidable({ maxFileSize: 8 * 1024 * 1024, }), + ], this.createNewLink.bind(this)) + } + + hasErrors(ctx, hasMedia) { + if (!ctx.req.body.video) { + return 'Missing video link' + } + + if (!ctx.req.body.video.startsWith('http') + || !(ctx.req.body.video.includes('mp4') + || ctx.req.body.video.includes('webm'))) { + return 'Video link has to be a valid full url and contain mp4 or webm in it' + } + + if (!ctx.req.body.image && !hasMedia) { + return 'Missing image link or file' + } + + if (ctx.req.body.image) { + if (!ctx.req.body.image.startsWith('http') + || !(ctx.req.body.image.includes('jpg') + || ctx.req.body.image.includes('jpeg') + || ctx.req.body.image.includes('webp') + || ctx.req.body.image.includes('png') + ) + ) { + return 'Image link has to be a valid full url and contain jpg, jpeg, webp or png' + } + } + } + + /** PUT: /api/auth/articles/:id */ + async createNewLink(ctx) { + let hasMedia = ctx.req.files.media && ctx.req.files.media.size + + ctx.state.video = ctx.req.body.video + ctx.state.image = ctx.req.body.image + let redirect = '' + let error = this.hasErrors(ctx, hasMedia) + + if (!error && hasMedia) { + 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 + } + catch (err) { + ctx.log.error(err) + error = 'Unable to upload file: ' + err.message + } + } + if (!error) { + redirect = `${this.frontend}/?v=${ctx.state.video}&i=${ctx.state.image}` + } + + if (!error) { + try { + let params = [ + ] + let res = await ctx.db.safeCallProc('discord_embed.link_add', params) + console.log(res) + } + catch (err) { + ctx.log.error(err) + error = 'Error while generating shortened link.' + } + } + + if (redirect && !error) { + ctx.status = 302 + ctx.headers['Location'] = redirect + ctx.type = 'text/html; charset=utf-8' + ctx.body = ` +Redirecting +Click here if it doesn't redirect +` + } + ctx.state.error = error + return this.serve.serveIndex(ctx) + } +} +// https://litter.catbox.moe/cnl6hy.mp4 \ No newline at end of file diff --git a/discord_embed/api/serve.mjs b/discord_embed/api/serve.mjs new file mode 100644 index 0000000..993ddf7 --- /dev/null +++ b/discord_embed/api/serve.mjs @@ -0,0 +1,46 @@ +import path from 'path' +import Parent from '../base/serve.mjs' +import fs from 'fs/promises' +import fsSync from 'fs' +import dot from 'dot' +import config from '../base/config.mjs' + +export default class ServeHandler extends Parent { + loadTemplate(indexFile) { + this.template = dot.template(indexFile.toString(), { argName: [ + 'imageLink', + 'videoLink', + 'error', + 'siteUrl', + 'siteUrlBase', + 'version', + 'nonce', + 'in_debug', + 'inputVideo', + 'inputImage' + ] }) + } + + async serveIndex(ctx) { + if (config.get('NODE_ENV') === 'development') { + let indexFile = await fs.readFile(path.join(this.root, 'index.html')) + this.loadTemplate(indexFile) + } + + let payload = { + imageLink: ctx.query.get('i') || '', + videoLink: ctx.query.get('v') || '', + error: ctx.state.error || '', + inputVideo: ctx.state.video || ctx.query.get('v') || '', + inputImage: ctx.state.image || ctx.query.get('i') || '', + siteUrl: this.frontend + ctx.url, + siteUrlBase: this.frontend + '/', + version: this.version, + nonce: ctx.state.nonce, + in_debug: config.get('NODE_ENV') === 'development' && false, + } + + ctx.body = this.template(payload) + ctx.type = 'text/html; charset=utf-8' + } +} diff --git a/discord_embed/api/server.mjs b/discord_embed/api/server.mjs new file mode 100644 index 0000000..8aeaaf8 --- /dev/null +++ b/discord_embed/api/server.mjs @@ -0,0 +1,22 @@ +import config from '../base/config.mjs' +import Parent from '../base/server.mjs' +import IndexPost from './post.mjs' +import ServeHandler from './serve.mjs' + +export default class Server extends Parent { + init() { + super.init() + let localUtil = new this.core.sc.Util(import.meta.url) + + this.routes = { + post: new IndexPost({ + frontend: config.get('frontend:url'), + }) + } + this.routes.serve = new ServeHandler({ + root: localUtil.getPathFromRoot('../public'), + version: this.core.app.running, + frontend: config.get('frontend:url'), + }) + } +} diff --git a/discord_embed/base b/discord_embed/base new file mode 120000 index 0000000..24312d1 --- /dev/null +++ b/discord_embed/base @@ -0,0 +1 @@ +../base \ No newline at end of file diff --git a/discord_embed/build-package.json b/discord_embed/build-package.json new file mode 100644 index 0000000..f03ccc7 --- /dev/null +++ b/discord_embed/build-package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "build": "echo done;" + }, + "dependencies": { + "service-core": "^3.0.0-beta.17" + } +} diff --git a/discord_embed/dev.mjs b/discord_embed/dev.mjs new file mode 100644 index 0000000..0d7f32f --- /dev/null +++ b/discord_embed/dev.mjs @@ -0,0 +1,24 @@ +import fs from 'fs' +import { ServiceCore } from 'service-core' +import * as index from './index.mjs' + +const port = 4120 + +var core = new ServiceCore('nfp_moe', import.meta.url, port, '') + +let config = { + frontend: { + url: 'http://localhost:' + port + } +} + +try { + config = JSON.parse(fs.readFileSync('./config.json')) +} catch {} + +config.port = port + +core.setConfig(config) +core.init(index).then(function() { + return core.run() +}) \ No newline at end of file diff --git a/discord_embed/index.mjs b/discord_embed/index.mjs new file mode 100644 index 0000000..6a42b60 --- /dev/null +++ b/discord_embed/index.mjs @@ -0,0 +1,11 @@ +import config from './base/config.mjs' + +export function start(http, port, ctx) { + config.sources[1].store = ctx.config + + return import('./api/server.mjs') + .then(function(module) { + let server = new module.default(http, port, ctx) + return server.run() + }) +} diff --git a/discord_embed/package.json b/discord_embed/package.json new file mode 100644 index 0000000..1c3870a --- /dev/null +++ b/discord_embed/package.json @@ -0,0 +1,47 @@ +{ + "name": "av1_embed", + "version": "1.0.0", + "port": 4120, + "description": "AV1 discord server embed helper", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "start": "node --experimental-modules index.mjs", + "dev:server": "node dev.mjs | bunyan", + "dev": "npm-watch dev:server" + }, + "watch": { + "dev:server": { + "patterns": [ + "api/*", + "base/*", + "../base/*" + ], + "extensions": "js,mjs", + "quiet": true, + "inherit": true + } + }, + "repository": { + "type": "git", + "url": "https://git.nfp.is/nfp/nfp_sites.git" + }, + "author": "Jonatan Nilsson", + "license": "WTFPL", + "bugs": { + "url": "https://git.nfp.is/nfp/nfp_sites/issues" + }, + "homepage": "https://git.nfp.is/nfp/nfp_sites", + "dependencies": { + "dot": "^2.0.0-beta.1", + "flaska": "^1.3.0", + "formidable": "^1.2.6", + "ioredis": "^5.2.3", + "nconf-lite": "^2.0.0" + }, + "devDependencies": { + "service-core": "^3.0.0-beta.17" + } +} diff --git a/discord_embed/public/app.css b/discord_embed/public/app.css new file mode 100644 index 0000000..e69de29 diff --git a/discord_embed/public/favicon.png b/discord_embed/public/favicon.png new file mode 100644 index 0000000..984f71e Binary files /dev/null and b/discord_embed/public/favicon.png differ diff --git a/discord_embed/public/heart.png b/discord_embed/public/heart.png new file mode 100644 index 0000000..8732988 Binary files /dev/null and b/discord_embed/public/heart.png differ diff --git a/discord_embed/public/index.html b/discord_embed/public/index.html new file mode 100644 index 0000000..e8c9d93 --- /dev/null +++ b/discord_embed/public/index.html @@ -0,0 +1,192 @@ + + + + + Discord Embedder from AV1 server + + + {{ if (imageLink) { }} + + + + + + {{ } else { }} + + + + + + {{ } }} + + + + +
+

Create/generate embed url

+ {{ if (error) { }}

{{=error}}

{{ } }} + + +
+
+ + +
+ or +
+ + +
+
+ + + +

+ Alternatively, you can generate a link yourself using the following syntax: +

+
+      {{=siteUrlBase}}?v=<video link>&i=<image link>
+    
+
+ + diff --git a/nfp_moe/api/serve.mjs b/nfp_moe/api/serve.mjs index b0b5b74..f3332e0 100644 --- a/nfp_moe/api/serve.mjs +++ b/nfp_moe/api/serve.mjs @@ -2,7 +2,6 @@ import path from 'path' import striptags from 'striptags' import Parent from '../base/serve.mjs' import fs from 'fs/promises' -import dot from 'dot' import config from '../base/config.mjs' export default class ServeHandler extends Parent { @@ -49,11 +48,11 @@ export default class ServeHandler extends Parent { async serveIndex(ctx) { if (config.get('NODE_ENV') === 'development') { let indexFile = await fs.readFile(path.join(this.root, 'index.html')) - this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner', 'media', 'in_debug'] }) + this.loadTemplate(indexFile) } let payload = { - headerDescription: 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.', + headerDescription: 'A small fansubbing and scanlation group translating and encoding our favourite shows from Japan.', headerImage: this.frontend + '/assets/img/heart.png', headerTitle: 'NFP Moe - Anime/Manga translation group', headerUrl: this.frontend + ctx.url, @@ -87,7 +86,8 @@ export default class ServeHandler extends Parent { if (data.page) { payload.headerTitle = data.page.name + ' - NFP Moe' - if (data.page.content.blocks.length) { + payload.headerDescription = 'Page ' + data.page.name + ' over at NFP Moe. ' + payload.headerDescription + if (data.page.content.blocks?.length) { payload.headerDescription = this.getDescriptionFromBlocks(data.page.content.blocks) || payload.headerDescription } if (data.page.media_alt_prefix) { @@ -114,6 +114,8 @@ export default class ServeHandler extends Parent { payload.media = data.article?.media_avif_preview || false payload.payloadData = JSON.stringify(data) payload.type = 'article' + } else if (ctx.url !== '/login' && !ctx.url.startsWith('/admin/')) { + ctx.status = 404 } } catch (e) { ctx.log.error(e)