-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
polished homepage update dependencies write proper readme better defaults for Windows use post or query params on POST endpoint add build stage to Docker to streamline development
- Loading branch information
Showing
11 changed files
with
1,291 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,4 +104,7 @@ dist | |
.tern-port | ||
|
||
# Custom | ||
config/config.json5 | ||
config/config.json5 | ||
assets/css/ | ||
assets/js/ | ||
assets/webp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,23 @@ | ||
# rct-screenshotter | ||
|
||
Screenshotter is a simple web server that generates screenshots of Rollercoaster Tycoon save files. | ||
[![Docker Pulls](https://img.shields.io/docker/pulls/corysanin/rct-screenshotter)](https://hub.docker.com/r/corysanin/rct-screenshotter) | ||
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/CorySanin/rct-screenshotter/docker-image.yml)](https://github.com/CorySanin/rct-screenshotter/actions) | ||
[![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/CorySanin/rct-screenshotter)](https://libraries.io/github/CorySanin/rct-screenshotter) | ||
[![GitHub repo size](https://img.shields.io/github/repo-size/CorySanin/rct-screenshotter)](https://github.com/CorySanin/rct-screenshotter) | ||
[![GitHub](https://img.shields.io/github/license/CorySanin/rct-screenshotter)](https://github.com/CorySanin/rct-screenshotter/blob/master/LICENSE) | ||
|
||
`rct-screenshotter` is a simple web server that generates screenshots of Rollercoaster Tycoon save files. | ||
|
||
This project is intended to be used as a REST API for projects like [ffa-tycoon](https://github.com/CorySanin/ffa-tycoon). But it also has a form on its homepage to allow for use in a web browser. | ||
|
||
It uses OpenRCT2 to generate screenshots, and as such is compatible with RCT1, RCT2, and OpenRCT2 save formats. | ||
|
||
## API | ||
|
||
Submit a multipart post request to /upload with the following fields: | ||
|
||
| Name | Description | | ||
|----------|---------------------------------------------------------| | ||
| park | The save file to generate a screenshot of | | ||
| zoom | The zoom level to use in the screenshot. 0-7 (optional) | | ||
| rotation | The rotation of the map. 0-3 (optional) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/** | ||
* Wrote this out of anger. | ||
*/ | ||
const fsp = require('fs').promises; | ||
const path = require('path'); | ||
const spawn = require('child_process').spawn; | ||
const sass = require('sass'); | ||
const csso = require('csso'); | ||
const uglifyjs = require("uglify-js"); | ||
const { resolve } = require('path'); | ||
|
||
const STYLESDIR = 'styles'; | ||
const SCRIPTSDIR = 'scripts'; | ||
const IMAGESDIR = path.join('assets', 'images'); | ||
const STYLEOUTDIR = process.env.STYLEOUTDIR || path.join(__dirname, 'assets', 'css'); | ||
const SCRIPTSOUTDIR = process.env.SCRIPTSOUTDIR || path.join(__dirname, 'assets', 'js'); | ||
const IMAGESOUTDIR = process.env.IMAGESOUTDIR || path.join(__dirname, 'assets', 'webp'); | ||
const STYLEOUTFILE = process.env.STYLEOUTFILE || 'styles.css'; | ||
const SQUASH = new RegExp('^[0-9]+-'); | ||
|
||
async function emptyDir(dir) { | ||
await Promise.all((await fsp.readdir(dir, { withFileTypes: true })).map(f => path.join(dir, f.name)).map(p => fsp.rm(p, { | ||
recursive: true, | ||
force: true | ||
}))); | ||
} | ||
|
||
async function mkdir(dir) { | ||
if (typeof dir === 'string') { | ||
await fsp.mkdir(dir, { recursive: true }); | ||
} | ||
else { | ||
await Promise.all(dir.map(mkdir)); | ||
} | ||
} | ||
|
||
// Process styles | ||
async function styles() { | ||
await mkdir([STYLEOUTDIR, STYLESDIR]); | ||
await await emptyDir(STYLEOUTDIR); | ||
let styles = []; | ||
let files = await fsp.readdir(STYLESDIR); | ||
await Promise.all(files.map(f => new Promise(async (res, reject) => { | ||
let p = path.join(STYLESDIR, f); | ||
if (f.charAt(0) !== '_') { | ||
console.log(`Processing style ${p}`); | ||
let style = sass.compile(p).css; | ||
if (SQUASH.test(f)) { | ||
styles.push(style); | ||
} | ||
else { | ||
let o = path.join(STYLEOUTDIR, f.substring(0, f.lastIndexOf('.')) + '.css'); | ||
await fsp.writeFile(o, csso.minify(style).css); | ||
console.log(`Wrote ${o}`); | ||
} | ||
} | ||
res(0); | ||
}))); | ||
let out = csso.minify(styles.join('\n')).css; | ||
let outpath = path.join(STYLEOUTDIR, STYLEOUTFILE); | ||
await fsp.writeFile(outpath, out); | ||
console.log(`Wrote ${outpath}`); | ||
} | ||
|
||
// Process scripts | ||
async function scripts() { | ||
await mkdir([SCRIPTSOUTDIR, SCRIPTSDIR]); | ||
await emptyDir(SCRIPTSOUTDIR); | ||
let files = await fsp.readdir(SCRIPTSDIR); | ||
await Promise.all(files.map(f => new Promise(async (res, reject) => { | ||
let p = path.join(SCRIPTSDIR, f); | ||
let o = path.join(SCRIPTSOUTDIR, f); | ||
console.log(`Processing script ${p}`); | ||
try { | ||
await fsp.writeFile(o, uglifyjs.minify((await fsp.readFile(p)).toString()).code); | ||
console.log(`Wrote ${o}`); | ||
} | ||
catch (ex) { | ||
console.log(`error writing ${o}: ${ex}`); | ||
} | ||
res(0); | ||
}))); | ||
} | ||
|
||
// Process images | ||
async function images(dir = '') { | ||
let p = path.join(IMAGESDIR, dir); | ||
await mkdir(p); | ||
if (dir.length === 0) { | ||
await mkdir(IMAGESOUTDIR) | ||
await emptyDir(IMAGESOUTDIR); | ||
} | ||
let files = await fsp.readdir(p, { | ||
withFileTypes: true | ||
}); | ||
if (files.length) { | ||
await Promise.all(files.map(f => new Promise(async (res, reject) => { | ||
if (f.isFile()) { | ||
let outDir = path.join(IMAGESOUTDIR, dir); | ||
let infile = path.join(p, f.name); | ||
let outfile = path.join(outDir, f.name.substring(0, f.name.lastIndexOf('.')) + '.webp'); | ||
await mkdir(outDir); | ||
console.log(`Processing image ${infile}`) | ||
let process = spawn('cwebp', ['-mt', '-q', '40', infile, '-o', outfile]); | ||
let timeout = setTimeout(() => { | ||
reject('Timed out'); | ||
process.kill(); | ||
}, 30000); | ||
process.on('exit', async (code) => { | ||
clearTimeout(timeout); | ||
if (code === 0) { | ||
console.log(`Wrote ${outfile}`); | ||
res(); | ||
} | ||
else { | ||
reject(code); | ||
} | ||
}); | ||
} | ||
else if (f.isDirectory()) { | ||
images(path.join(dir, f.name)).then(res).catch(reject); | ||
} | ||
}))); | ||
} | ||
} | ||
|
||
(async function () { | ||
await Promise.all([styles(), scripts()]); | ||
if (process.argv.indexOf('--watch') >= 0) { | ||
console.log('watching for changes...'); | ||
(async () => { | ||
try { | ||
const watcher = fsp.watch(STYLESDIR); | ||
for await (const _ of watcher) | ||
await styles(); | ||
} catch (err) { | ||
if (err.name === 'AbortError') | ||
return; | ||
throw err; | ||
} | ||
})(); | ||
|
||
(async () => { | ||
try { | ||
const watcher = fsp.watch(SCRIPTSDIR); | ||
for await (const _ of watcher) | ||
await scripts(); | ||
} catch (err) { | ||
if (err.name === 'AbortError') | ||
return; | ||
throw err; | ||
} | ||
})(); | ||
|
||
(async () => { | ||
try { | ||
const watcher = fsp.watch(IMAGESDIR, { | ||
recursive: true // no Linux ☹️ | ||
}); | ||
for await (const _ of watcher) | ||
await images(); | ||
} catch (err) { | ||
if (err.name === 'AbortError') | ||
return; | ||
throw err; | ||
} | ||
})(); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.