refactor (backend): port genIdenticon to backend-rs

This commit is contained in:
naskya 2024-07-29 23:10:28 +09:00
parent 7246c8675c
commit b85f459cfe
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
11 changed files with 103 additions and 169 deletions

77
Cargo.lock generated
View file

@ -214,6 +214,7 @@ dependencies = [
"cuid2",
"emojis",
"futures-util",
"identicon-rs",
"idna 1.0.2",
"image",
"isahc",
@ -326,6 +327,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -612,6 +619,12 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-bigint"
version = "0.5.5"
@ -928,6 +941,22 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "exr"
version = "1.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
dependencies = [
"bit_field",
"flume",
"half",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "fastrand"
version = "1.9.0"
@ -1166,6 +1195,16 @@ dependencies = [
"subtle",
]
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -1421,6 +1460,17 @@ dependencies = [
"syn 2.0.72",
]
[[package]]
name = "identicon-rs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c4fdcf9f82fef33187cc52ad515de24786ec837837e69f641d1cb8157f4f02"
dependencies = [
"image",
"sha3",
"thiserror",
]
[[package]]
name = "idna"
version = "0.5.0"
@ -1452,11 +1502,14 @@ dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"exr",
"gif",
"image-webp",
"num-traits",
"png",
"qoi",
"ravif",
"rayon",
"rgb",
"tiff",
"zune-core",
@ -1664,6 +1717,12 @@ dependencies = [
"spin",
]
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.155"
@ -2515,6 +2574,15 @@ dependencies = [
"syn 2.0.72",
]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "quick-error"
version = "2.0.1"
@ -4475,6 +4543,15 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
[[package]]
name = "zune-jpeg"
version = "0.4.13"

View file

@ -24,6 +24,7 @@ convert_case = { version = "0.6.0", default-features = false }
cuid2 = { version = "0.1.2", default-features = false }
emojis = { version = "0.6.3", default-features = false }
futures-util = { version = "0.3.30", default-features = false }
identicon-rs = "5.0.1"
idna = { version = "1.0.2", default-features = false }
image = { version = "0.25.2", default-features = false }
isahc = { version = "1.7.2", default-features = false }

View file

@ -28,6 +28,7 @@ chrono = { workspace = true }
cuid2 = { workspace = true }
emojis = { workspace = true }
futures-util = { workspace = true, features = ["io"] }
identicon-rs = { workspace = true }
idna = { workspace = true, features = ["std", "compiled_data"] }
image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] }
isahc = { workspace = true, features = ["http2", "text-decoding", "json"] }

View file

@ -495,6 +495,8 @@ export declare function genId(): string
/** Generate an ID using a specific datetime */
export declare function genIdAt(date: Date): string
export declare function genIdenticon(id: string): Buffer
export declare function getFullApAccount(username: string, host?: string | undefined | null): string
export declare function getImageSizeFromUrl(url: string): Promise<ImageSize>

View file

@ -384,6 +384,7 @@ module.exports.generateSecureRandomString = nativeBinding.generateSecureRandomSt
module.exports.generateUserToken = nativeBinding.generateUserToken
module.exports.genId = nativeBinding.genId
module.exports.genIdAt = nativeBinding.genIdAt
module.exports.genIdenticon = nativeBinding.genIdenticon
module.exports.getFullApAccount = nativeBinding.getFullApAccount
module.exports.getImageSizeFromUrl = nativeBinding.getImageSizeFromUrl
module.exports.getInstanceActor = nativeBinding.getInstanceActor

View file

@ -13,6 +13,7 @@ pub mod latest_version;
pub mod note;
pub mod nyaify;
pub mod password;
pub mod random_icon;
pub mod reaction;
pub mod remove_old_attestation_challenges;
pub mod should_nyaify;

View file

@ -0,0 +1,17 @@
use identicon_rs::{error::IdenticonError, Identicon};
pub fn generate(id: &str) -> Result<Vec<u8>, IdenticonError> {
Identicon::new(id).set_border(35).export_png_data()
}
#[cfg(feature = "napi")]
#[napi_derive::napi(js_name = "genIdenticon")]
pub fn generate_js(id: String) -> napi::Result<napi::bindgen_prelude::Buffer> {
match generate(&id) {
Ok(icon) => Ok(icon.into()),
Err(err) => Err(napi::Error::from_reason(format!(
"\n{}\n",
crate::util::error_chain::format_error(&err)
))),
}
}

View file

@ -87,10 +87,8 @@
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"punycode": "2.3.1",
"pureimage": "0.4.13",
"qrcode": "1.5.3",
"qs": "6.12.3",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"redis-semaphore": "5.6.0",
"reflect-metadata": "0.2.2",

View file

@ -1,114 +0,0 @@
/**
* Identicon generator
* https://en.wikipedia.org/wiki/Identicon
*/
import type { WriteStream } from "node:fs";
import * as p from "pureimage";
import gen from "random-seed";
const size = 128; // px
const n = 5; // resolution
const margin = size / 4;
const colors = [
["#eb6f92", "#b4637a"],
["#f6c177", "#ea9d34"],
["#ebbcba", "#d7827e"],
["#9ccfd8", "#56949f"],
["#c4a7e7", "#907aa9"],
["#eb6f92", "#f6c177"],
["#eb6f92", "#ebbcba"],
["#eb6f92", "#31748f"],
["#eb6f92", "#9ccfd8"],
["#eb6f92", "#c4a7e7"],
["#f6c177", "#eb6f92"],
["#f6c177", "#ebbcba"],
["#f6c177", "#31748f"],
["#f6c177", "#9ccfd8"],
["#f6c177", "#c4a7e7"],
["#ebbcba", "#eb6f92"],
["#ebbcba", "#f6c177"],
["#ebbcba", "#31748f"],
["#ebbcba", "#9ccfd8"],
["#ebbcba", "#c4a7e7"],
["#31748f", "#eb6f92"],
["#31748f", "#f6c177"],
["#31748f", "#ebbcba"],
["#31748f", "#9ccfd8"],
["#31748f", "#c4a7e7"],
["#9ccfd8", "#eb6f92"],
["#9ccfd8", "#f6c177"],
["#9ccfd8", "#ebbcba"],
["#9ccfd8", "#31748f"],
["#9ccfd8", "#c4a7e7"],
["#c4a7e7", "#eb6f92"],
["#c4a7e7", "#f6c177"],
["#c4a7e7", "#ebbcba"],
["#c4a7e7", "#31748f"],
["#c4a7e7", "#9ccfd8"],
];
const actualSize = size - margin * 2;
const cellSize = actualSize / n;
const sideN = Math.floor(n / 2);
/**
* Generate buffer of an identicon by seed
*/
export function genIdenticon(seed: string, stream: WriteStream): Promise<void> {
const rand = gen.create(seed);
const canvas = p.make(size, size, undefined);
const ctx = canvas.getContext("2d");
const bgColors = colors[rand(colors.length)];
const bg = ctx.createLinearGradient(0, 0, size, size);
bg.addColorStop(0, bgColors[0]);
bg.addColorStop(1, bgColors[1]);
ctx.fillStyle = bg;
ctx.beginPath();
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = "#ffffff";
// side bitmap (filled by false)
const side: boolean[][] = new Array(sideN);
for (let i = 0; i < side.length; i++) {
side[i] = new Array(n).fill(false);
}
// 1*n (filled by false)
const center: boolean[] = new Array(n).fill(false);
for (let x = 0; x < side.length; x++) {
for (let y = 0; y < side[x].length; y++) {
side[x][y] = rand(3) === 0;
}
}
for (let i = 0; i < center.length; i++) {
center[i] = rand(3) === 0;
}
// Draw
for (let x = 0; x < n; x++) {
for (let y = 0; y < n; y++) {
const isXCenter = x === (n - 1) / 2;
if (isXCenter && !center[y]) continue;
const isLeftSide = x < (n - 1) / 2;
if (isLeftSide && !side[x][y]) continue;
const isRightSide = x > (n - 1) / 2;
if (isRightSide && !side[sideN - (x - sideN)][y]) continue;
const actualX = margin + cellSize * x;
const actualY = margin + cellSize * y;
ctx.beginPath();
ctx.fillRect(actualX, actualY, cellSize, cellSize);
}
}
return p.encodePNGToStream(canvas, stream);
}

View file

@ -15,8 +15,7 @@ import { IsNull } from "typeorm";
import { config } from "@/config.js";
import Logger from "@/services/logger.js";
import { Users } from "@/models/index.js";
import { fetchMeta, stringToAcct } from "backend-rs";
import { genIdenticon } from "@/misc/gen-identicon.js";
import { fetchMeta, genIdenticon, stringToAcct } from "backend-rs";
import { createTemp } from "@/misc/create-temp.js";
import activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js";
@ -119,8 +118,9 @@ router.get("/avatar/@:acct", async (ctx) => {
router.get("/identicon/:x", async (ctx) => {
const instanceMeta = await fetchMeta();
if (instanceMeta.enableIdenticonGeneration) {
const identicon = genIdenticon(ctx.params.x);
const [temp, cleanup] = await createTemp();
await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
fs.createWriteStream(temp).write(identicon);
ctx.set("Content-Type", "image/png");
ctx.body = fs.createReadStream(temp).on("close", () => cleanup());
} else {

View file

@ -249,18 +249,12 @@ importers:
punycode:
specifier: 2.3.1
version: 2.3.1
pureimage:
specifier: 0.4.13
version: 0.4.13
qrcode:
specifier: 1.5.3
version: 1.5.3
qs:
specifier: 6.12.3
version: 6.12.3
random-seed:
specifier: 0.3.0
version: 0.3.0
ratelimiter:
specifier: 3.4.1
version: 3.4.1
@ -973,13 +967,11 @@ packages:
'@biomejs/cli-darwin-arm64@1.8.3':
resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@1.8.3':
resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@1.8.3':
@ -991,7 +983,6 @@ packages:
'@biomejs/cli-linux-arm64@1.8.3':
resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@1.8.3':
@ -1003,7 +994,6 @@ packages:
'@biomejs/cli-linux-x64@1.8.3':
resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@1.8.3':
@ -4683,9 +4673,6 @@ packages:
resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==}
engines: {node: '>= 0.6.0'}
jpeg-js@0.4.4:
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
js-beautify@1.15.1:
resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==}
engines: {node: '>=14'}
@ -4752,9 +4739,6 @@ packages:
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@ -5289,10 +5273,6 @@ packages:
resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}
hasBin: true
opentype.js@0.4.11:
resolution: {integrity: sha512-GthxucX/6aftfLdeU5Ho7o7zmQcC8uVtqdcelVq12X++ndxwBZG8Xb5rFEKT7nEcWDD2P1x+TNuJ70jtj1Mbpw==}
hasBin: true
os-tmpdir@1.0.2:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
@ -5516,10 +5496,6 @@ packages:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
pngjs@7.0.0:
resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
engines: {node: '>=14.19.0'}
pnpm@9.6.0:
resolution: {integrity: sha512-ONxvuo26NbOTQLlwARLC/h4S8QsXE0cVpKqYzPe7A152/Zgc8Ls4TfqY+NavVIHCvvL0Jmokv6IMNOtxR84LXg==}
engines: {node: '>=18.12'}
@ -5683,10 +5659,6 @@ packages:
pure-rand@6.1.0:
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
pureimage@0.4.13:
resolution: {integrity: sha512-P8aonTNAnXWJn2pBIqyeWw0I/D4YDOfEavCVvbDG+wx3dCujQX0ENZiW5OcHfbd8HKLfVhCf4F/3Xivf1yWDiA==}
engines: {node: '>=14.19.0'}
qrcode-generator@1.4.4:
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
@ -5728,10 +5700,6 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
random-seed@0.3.0:
resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==}
engines: {node: '>= 0.6.0'}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -11001,8 +10969,6 @@ snapshots:
jmespath@0.16.0: {}
jpeg-js@0.4.4: {}
js-beautify@1.15.1:
dependencies:
config-chain: 1.1.13
@ -11074,8 +11040,6 @@ snapshots:
json-schema@0.4.0: {}
json-stringify-safe@5.0.1: {}
json5@2.2.3: {}
jsonfile@4.0.0:
@ -11684,8 +11648,6 @@ snapshots:
opencollective-postinstall@2.0.3: {}
opentype.js@0.4.11: {}
os-tmpdir@1.0.2: {}
otpauth@9.3.1:
@ -11874,8 +11836,6 @@ snapshots:
pngjs@5.0.0: {}
pngjs@7.0.0: {}
pnpm@9.6.0: {}
possible-typed-array-names@1.0.0: {}
@ -12063,12 +12023,6 @@ snapshots:
pure-rand@6.1.0: {}
pureimage@0.4.13:
dependencies:
jpeg-js: 0.4.4
opentype.js: 0.4.11
pngjs: 7.0.0
qrcode-generator@1.4.4: {}
qrcode-vue3@1.6.8:
@ -12100,10 +12054,6 @@ snapshots:
quick-lru@5.1.1: {}
random-seed@0.3.0:
dependencies:
json-stringify-safe: 5.0.1
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1