stuff's
This commit is contained in:
parent
e730a98b48
commit
4c8ba752cd
|
@ -15,6 +15,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/color": "^3.0.6",
|
"@types/color": "^3.0.6",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/morgan": "^1.9.9",
|
||||||
"@types/node": "^20.14.2",
|
"@types/node": "^20.14.2",
|
||||||
"@types/seedrandom": "^3.0.8",
|
"@types/seedrandom": "^3.0.8",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"isolated-vm": "^5.0.0",
|
"isolated-vm": "^5.0.0",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
"seedrandom": "^3.0.5"
|
"seedrandom": "^3.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ dependencies:
|
||||||
isolated-vm:
|
isolated-vm:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.0
|
version: 5.0.0
|
||||||
|
morgan:
|
||||||
|
specifier: ^1.10.0
|
||||||
|
version: 1.10.0
|
||||||
seedrandom:
|
seedrandom:
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
|
@ -31,6 +34,9 @@ devDependencies:
|
||||||
'@types/express':
|
'@types/express':
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
'@types/morgan':
|
||||||
|
specifier: ^1.9.9
|
||||||
|
version: 1.9.9
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.14.2
|
specifier: ^20.14.2
|
||||||
version: 20.14.2
|
version: 20.14.2
|
||||||
|
@ -116,6 +122,12 @@ packages:
|
||||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||||
dev: true
|
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:
|
/@types/node@20.14.2:
|
||||||
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
|
resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -214,6 +226,13 @@ packages:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
dev: false
|
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:
|
/bl@4.1.0:
|
||||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -818,6 +837,19 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
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:
|
/ms@2.0.0:
|
||||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -889,6 +921,13 @@ packages:
|
||||||
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
|
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
|
||||||
dev: false
|
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:
|
/on-finished@2.4.1:
|
||||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
@ -896,6 +935,11 @@ packages:
|
||||||
ee-first: 1.1.1
|
ee-first: 1.1.1
|
||||||
dev: false
|
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:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1003,6 +1047,10 @@ packages:
|
||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/safe-buffer@5.1.2:
|
||||||
|
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/safe-buffer@5.2.1:
|
/safe-buffer@5.2.1:
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
|
@ -5,9 +5,12 @@ import { generatePicrew } from "./picrew.js";
|
||||||
import { dirname, isValidSha256 } from "./util.js";
|
import { dirname, isValidSha256 } from "./util.js";
|
||||||
import { HttpRespondError } from "./types.js";
|
import { HttpRespondError } from "./types.js";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import morgan from "morgan";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
app.use(morgan("tiny"));
|
||||||
|
|
||||||
app.get("/generate/:makerid/:hash", async (req: express.Request, res: express.Response) => {
|
app.get("/generate/:makerid/:hash", async (req: express.Request, res: express.Response) => {
|
||||||
try {
|
try {
|
||||||
const makerid = req.params.makerid as string;
|
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())) {
|
if (["true", "1"].includes(`${req.query.gravatar || ""}`.toLowerCase())) {
|
||||||
const gravatarExists = !!(await axios.head(`https://gravatar.com/avatar/${hash}?d=404`).catch(() => false));
|
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);
|
const path = await generatePicrew(hash, makerid);
|
||||||
res.sendFile(path);
|
res.sendFile(path);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
if (e instanceof HttpRespondError) {
|
if (e instanceof HttpRespondError) {
|
||||||
res.status(e.statusCode).send(e.message);
|
res.status(e.statusCode).send(e.message);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { createCanvas, loadImage } from "canvas";
|
import { createCanvas, loadImage } from "canvas";
|
||||||
import { fetchScript, extractDataFromScript, getAsset, dirname } from "./util.js";
|
import { fetchScript, extractDataFromScript, getAsset, dirname } from "./util.js";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import crypto from "crypto";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
import seedrandom from "seedrandom";
|
import seedrandom from "seedrandom";
|
||||||
|
@ -9,11 +8,14 @@ import seedrandom from "seedrandom";
|
||||||
// Generates a picrew from a given maker ID using the provided hash.
|
// Generates a picrew from a given maker ID using the provided hash.
|
||||||
// Returns the output file path.
|
// Returns the output file path.
|
||||||
export async function generatePicrew(hash: string, maker: string) {
|
export async function generatePicrew(hash: string, maker: string) {
|
||||||
|
console.log(`Request for ${hash}:${maker}`);
|
||||||
|
|
||||||
const outDir = path.join(dirname, "..", "cache", "outputs", maker);
|
const outDir = path.join(dirname, "..", "cache", "outputs", maker);
|
||||||
const filePath = path.join(outDir, hash + ".png");
|
const filePath = path.join(outDir, hash + ".png");
|
||||||
|
|
||||||
// If the file is already cached, just send that again
|
// If the file is already cached, just send that again
|
||||||
if (!!(await fs.stat(filePath).catch(() => false))) {
|
if (!!(await fs.stat(filePath).catch(() => false))) {
|
||||||
|
console.log(`${hash}:${maker} already exists, responding with cached file`);
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,9 +87,9 @@ export async function generatePicrew(hash: string, maker: string) {
|
||||||
|
|
||||||
if (canHide) {
|
if (canHide) {
|
||||||
// If there's other things that would be blocked by this,
|
// If there's other things that would be blocked by this,
|
||||||
// it should be more likely for this one to be hidden so
|
// it should possible for this one to be hidden so
|
||||||
// the other item appears more frequently.
|
// the other item can appear too.
|
||||||
if (rng() < (inRuleset ? 0.5 : 0.2)) continue;
|
if (rng() < (inRuleset ? 0.5 : 0)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a conflicting object already exists, and if so, skip this one
|
// 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());
|
await fs.writeFile(filePath, canvas.toBuffer());
|
||||||
|
|
||||||
|
console.log(`Generated ${hash}:${maker}, responding with generated file`);
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export async function fetchScript(makerId: string) {
|
||||||
try {
|
try {
|
||||||
await downloadToPath(`https://picrew.me/en/image_maker/${encodeURIComponent(makerId)}`, htmlPath);
|
await downloadToPath(`https://picrew.me/en/image_maker/${encodeURIComponent(makerId)}`, htmlPath);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
await fs.unlink(htmlPath).catch(() => {});
|
||||||
if (e instanceof AxiosError && e.status == 404) {
|
if (e instanceof AxiosError && e.status == 404) {
|
||||||
throw new HttpRespondError(`There is no Picrew maker with ID ${makerId}.`, 400);
|
throw new HttpRespondError(`There is no Picrew maker with ID ${makerId}.`, 400);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Picvatar</h1>
|
<h1 id="picvatar">Picvatar</h1>
|
||||||
<p>Generate unique avatars from a Picrew - Like Gravatar, but cooler!</p>
|
<p>Generate unique avatars from a Picrew - Like Gravatar, but cooler!</p>
|
||||||
|
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
|
@ -91,6 +91,18 @@
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</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 <3
|
||||||
|
</p>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const outputImg = document.getElementById("output-img");
|
const outputImg = document.getElementById("output-img");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue