This commit is contained in:
Lea 2024-06-16 22:25:56 +02:00
parent e730a98b48
commit 4c8ba752cd
Signed by: Lea
GPG key ID: 1BAFFE8347019C42
6 changed files with 79 additions and 6 deletions

View file

@ -15,6 +15,7 @@
"devDependencies": {
"@types/color": "^3.0.6",
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/node": "^20.14.2",
"@types/seedrandom": "^3.0.8",
"typescript": "^5.4.5"
@ -25,6 +26,7 @@
"color": "^4.2.3",
"express": "^4.19.2",
"isolated-vm": "^5.0.0",
"morgan": "^1.10.0",
"seedrandom": "^3.0.5"
}
}

View file

@ -20,6 +20,9 @@ dependencies:
isolated-vm:
specifier: ^5.0.0
version: 5.0.0
morgan:
specifier: ^1.10.0
version: 1.10.0
seedrandom:
specifier: ^3.0.5
version: 3.0.5
@ -31,6 +34,9 @@ devDependencies:
'@types/express':
specifier: ^4.17.21
version: 4.17.21
'@types/morgan':
specifier: ^1.9.9
version: 1.9.9
'@types/node':
specifier: ^20.14.2
version: 20.14.2
@ -116,6 +122,12 @@ packages:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
dev: true
/@types/morgan@1.9.9:
resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==}
dependencies:
'@types/node': 20.14.2
dev: true
/@types/node@20.14.2:
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
dependencies:
@ -214,6 +226,13 @@ packages:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false
/basic-auth@2.0.1:
resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
engines: {node: '>= 0.8'}
dependencies:
safe-buffer: 5.1.2
dev: false
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
@ -818,6 +837,19 @@ packages:
hasBin: true
dev: false
/morgan@1.10.0:
resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==}
engines: {node: '>= 0.8.0'}
dependencies:
basic-auth: 2.0.1
debug: 2.6.9
depd: 2.0.0
on-finished: 2.3.0
on-headers: 1.0.2
transitivePeerDependencies:
- supports-color
dev: false
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
@ -889,6 +921,13 @@ packages:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: false
/on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
dependencies:
ee-first: 1.1.1
dev: false
/on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@ -896,6 +935,11 @@ packages:
ee-first: 1.1.1
dev: false
/on-headers@1.0.2:
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
engines: {node: '>= 0.8'}
dev: false
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@ -1003,6 +1047,10 @@ packages:
glob: 7.2.3
dev: false
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: false
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false

View file

@ -5,9 +5,12 @@ import { generatePicrew } from "./picrew.js";
import { dirname, isValidSha256 } from "./util.js";
import { HttpRespondError } from "./types.js";
import path from "path";
import morgan from "morgan";
const app = express();
app.use(morgan("tiny"));
app.get("/generate/:makerid/:hash", async (req: express.Request, res: express.Response) => {
try {
const makerid = req.params.makerid as string;
@ -19,12 +22,16 @@ app.get("/generate/:makerid/:hash", async (req: express.Request, res: express.Re
if (["true", "1"].includes(`${req.query.gravatar || ""}`.toLowerCase())) {
const gravatarExists = !!(await axios.head(`https://gravatar.com/avatar/${hash}?d=404`).catch(() => false));
if (gravatarExists) return res.redirect(`https://gravatar.com/avatar/${hash}?s=256`);
if (gravatarExists) {
console.log(`Redirecting to Gravatar: ${hash}`);
return res.redirect(`https://gravatar.com/avatar/${hash}?s=256`);
}
}
const path = await generatePicrew(hash, makerid);
res.sendFile(path);
} catch(e) {
console.log(e);
if (e instanceof HttpRespondError) {
res.status(e.statusCode).send(e.message);
} else {

View file

@ -1,7 +1,6 @@
import { createCanvas, loadImage } from "canvas";
import { fetchScript, extractDataFromScript, getAsset, dirname } from "./util.js";
import fs from "fs/promises";
import crypto from "crypto";
import path from "path";
import Color from "color";
import seedrandom from "seedrandom";
@ -9,11 +8,14 @@ import seedrandom from "seedrandom";
// Generates a picrew from a given maker ID using the provided hash.
// Returns the output file path.
export async function generatePicrew(hash: string, maker: string) {
console.log(`Request for ${hash}:${maker}`);
const outDir = path.join(dirname, "..", "cache", "outputs", maker);
const filePath = path.join(outDir, hash + ".png");
// If the file is already cached, just send that again
if (!!(await fs.stat(filePath).catch(() => false))) {
console.log(`${hash}:${maker} already exists, responding with cached file`);
return filePath;
}
@ -85,9 +87,9 @@ export async function generatePicrew(hash: string, maker: string) {
if (canHide) {
// If there's other things that would be blocked by this,
// it should be more likely for this one to be hidden so
// the other item appears more frequently.
if (rng() < (inRuleset ? 0.5 : 0.2)) continue;
// it should possible for this one to be hidden so
// the other item can appear too.
if (rng() < (inRuleset ? 0.5 : 0)) continue;
}
// Check if a conflicting object already exists, and if so, skip this one
@ -134,5 +136,6 @@ export async function generatePicrew(hash: string, maker: string) {
await fs.writeFile(filePath, canvas.toBuffer());
console.log(`Generated ${hash}:${maker}, responding with generated file`);
return filePath;
}

View file

@ -36,6 +36,7 @@ export async function fetchScript(makerId: string) {
try {
await downloadToPath(`https://picrew.me/en/image_maker/${encodeURIComponent(makerId)}`, htmlPath);
} catch(e) {
await fs.unlink(htmlPath).catch(() => {});
if (e instanceof AxiosError && e.status == 404) {
throw new HttpRespondError(`There is no Picrew maker with ID ${makerId}.`, 400);
}

View file

@ -33,7 +33,7 @@
</style>
</head>
<body>
<h1>Picvatar</h1>
<h1 id="picvatar">Picvatar</h1>
<p>Generate unique avatars from a Picrew - Like Gravatar, but cooler!</p>
<div class="input-wrapper">
@ -91,6 +91,18 @@
</p>
</body>
<h3>The boring stuff</h3>
<p>
If you plan on using this, please respect the copyright and license of the Picrew - it's not my job to enforce other people's copyright, so please be reasonable. I also recommend self-hosting this - a Dockerfile and example docker-compose config is provided in the repository.
</p>
<p>
This is a one-day side project of mine that I'll likely never touch again, but the source code is available <a href="https://git.amogus.cloud/Lea/picvatar" target="_blank">here</a>. <br />
Reverse engineering Picrew and trying to figure out how to use the available data to manually render images was a fun challenge, but it is nothing I intend to put long term maintenance into. If you find a bug, feel free to report it, but if Picrew completely changes their backend then I will likely not rewrite everything from scratch to accomodate that.
</p>
<p>
Enjoy &lt;3
</p>
<script>
const outputImg = document.getElementById("output-img");