@kecan0406/ttheme 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -20
- package/bin/ttheme.js +1352 -209
- package/dist/manifest.json +1 -1
- package/dist/shell/palettes.zsh +29 -103
- package/package.json +1 -1
- package/shell/adapters/ghostty.zsh +1 -1
- package/shell/ttheme.zsh +7 -2
package/bin/ttheme.js
CHANGED
|
@@ -6144,7 +6144,7 @@ var program = new Command;
|
|
|
6144
6144
|
// package.json
|
|
6145
6145
|
var package_default = {
|
|
6146
6146
|
name: "@kecan0406/ttheme",
|
|
6147
|
-
version: "0.1.
|
|
6147
|
+
version: "0.1.3",
|
|
6148
6148
|
type: "module",
|
|
6149
6149
|
bin: {
|
|
6150
6150
|
ttheme: "bin/ttheme.js"
|
|
@@ -6804,6 +6804,42 @@ var CONFIG_SETTINGS = {
|
|
|
6804
6804
|
TTHEME_SORT: {
|
|
6805
6805
|
doc: "# series and palettes in ttheme and preview: abc sorts them by name, series keeps the order they were added (default abc)",
|
|
6806
6806
|
default: "abc"
|
|
6807
|
+
},
|
|
6808
|
+
TTHEME_FIND_RATING: {
|
|
6809
|
+
doc: "# the ratings find lists, any of safe, questionable and explicit, each booru read in its own rating vocabulary (default safe)",
|
|
6810
|
+
default: "safe"
|
|
6811
|
+
},
|
|
6812
|
+
TTHEME_FIND_BLOCK: {
|
|
6813
|
+
doc: '# the posts find drops by tag: nudity, underwear, both, or none to keep them all (default "nudity underwear")',
|
|
6814
|
+
default: "nudity underwear"
|
|
6815
|
+
},
|
|
6816
|
+
TTHEME_FIND_POSTS: {
|
|
6817
|
+
doc: "# what find lists first: all is every post of the character, cutouts are the transparent ones (default all)",
|
|
6818
|
+
default: "all"
|
|
6819
|
+
},
|
|
6820
|
+
TTHEME_FIND_CUTOUTS: {
|
|
6821
|
+
doc: '# the tags find calls a transparent cutout, per site as key=tag,tag pairs — e.g. "safebooru=transparent_background yande=transparent_png,vector" (default the built-in tags)',
|
|
6822
|
+
default: ""
|
|
6823
|
+
},
|
|
6824
|
+
TTHEME_FIND_ORDER: {
|
|
6825
|
+
doc: "# the order find lists posts in: newest or score (default newest)",
|
|
6826
|
+
default: "newest"
|
|
6827
|
+
},
|
|
6828
|
+
TTHEME_FIND_SETS: {
|
|
6829
|
+
doc: "# runs of the same picture at the same size from one uploader: fold shows them as one tile, show lists each (default fold)",
|
|
6830
|
+
default: "fold"
|
|
6831
|
+
},
|
|
6832
|
+
TTHEME_FIND_REMOVE_BG: {
|
|
6833
|
+
doc: "# an opaque picture tried on in find: on cuts the character out with macOS Vision, off leaves it as it is (default on)",
|
|
6834
|
+
default: "on"
|
|
6835
|
+
},
|
|
6836
|
+
TTHEME_FIND_UNBLOCK: {
|
|
6837
|
+
doc: "# when a network blocks a booru by name, 1 sends find through a local proxy that splits the TLS handshake (default 0)",
|
|
6838
|
+
default: "0"
|
|
6839
|
+
},
|
|
6840
|
+
TTHEME_FIND_HOSTS: {
|
|
6841
|
+
doc: '# send a find site somewhere else, as key=https://host pairs — e.g. "konachan=https://konachan.com danbooru=https://danbooru.donmai.us" (default none)',
|
|
6842
|
+
default: ""
|
|
6807
6843
|
}
|
|
6808
6844
|
};
|
|
6809
6845
|
function settingLine(name, value) {
|
|
@@ -6831,6 +6867,17 @@ ${settingLine(name, s.default)}`);
|
|
|
6831
6867
|
`)}
|
|
6832
6868
|
`;
|
|
6833
6869
|
}
|
|
6870
|
+
function withSetting(content, name, value) {
|
|
6871
|
+
const line = settingLine(name, value);
|
|
6872
|
+
const pattern = settingPattern(name);
|
|
6873
|
+
if (pattern.test(content)) {
|
|
6874
|
+
return content.replace(pattern, line);
|
|
6875
|
+
}
|
|
6876
|
+
return `${content.replace(/\n*$/, `
|
|
6877
|
+
`)}
|
|
6878
|
+
${line}
|
|
6879
|
+
`;
|
|
6880
|
+
}
|
|
6834
6881
|
function configFile(content) {
|
|
6835
6882
|
if (content === "") {
|
|
6836
6883
|
return configTemplate();
|
|
@@ -6848,7 +6895,7 @@ import = ["${themePath}"]` : "";
|
|
|
6848
6895
|
// src/emit/shell.ts
|
|
6849
6896
|
function palettesZsh(palettes) {
|
|
6850
6897
|
for (const p of palettes) {
|
|
6851
|
-
for (const field of [p.name, p.group, p.native ?? "", p.ansiSource
|
|
6898
|
+
for (const field of [p.name, p.group, p.native ?? "", p.ansiSource]) {
|
|
6852
6899
|
if (/["$`\\]/.test(field)) {
|
|
6853
6900
|
throw new Error(`${p.name}: "${field}" contains a character that breaks zsh quoting`);
|
|
6854
6901
|
}
|
|
@@ -6890,11 +6937,6 @@ function palettesZsh(palettes) {
|
|
|
6890
6937
|
"typeset -gA TTHEME_SRC=(",
|
|
6891
6938
|
...palettes.map((p) => entry(p, p.ansiSource)),
|
|
6892
6939
|
")",
|
|
6893
|
-
"",
|
|
6894
|
-
"# the safebooru tag preview searches when a palette has no background yet",
|
|
6895
|
-
"typeset -gA TTHEME_BOORU=(",
|
|
6896
|
-
...palettes.filter((p) => p.booru).map((p) => entry(p, p.booru ?? "")),
|
|
6897
|
-
")",
|
|
6898
6940
|
""
|
|
6899
6941
|
].join(`
|
|
6900
6942
|
`);
|
|
@@ -6999,7 +7041,8 @@ ${themes.length} themes (${rotation(themes).length} in the new-tab rotation) ->
|
|
|
6999
7041
|
}
|
|
7000
7042
|
|
|
7001
7043
|
// src/find.ts
|
|
7002
|
-
import {
|
|
7044
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7045
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7003
7046
|
import { tmpdir } from "node:os";
|
|
7004
7047
|
import { dirname as dirname3, join as join7 } from "node:path";
|
|
7005
7048
|
|
|
@@ -7023,12 +7066,21 @@ function decodePng(bytes) {
|
|
|
7023
7066
|
const png = import_pngjs.PNG.sync.read(Buffer.from(bytes));
|
|
7024
7067
|
return { width: png.width, height: png.height, data: new Uint8Array(png.data) };
|
|
7025
7068
|
}
|
|
7026
|
-
function decodeImage(bytes) {
|
|
7069
|
+
function decodeImage(bytes, limit) {
|
|
7027
7070
|
if (isPng(bytes)) {
|
|
7071
|
+
const head = pngHead(bytes);
|
|
7072
|
+
if (head && head.width * head.height > limit) {
|
|
7073
|
+
throw new Error(`${head.width}×${head.height} is over ${limit / 1e6} megapixels`);
|
|
7074
|
+
}
|
|
7028
7075
|
return decodePng(bytes);
|
|
7029
7076
|
}
|
|
7030
7077
|
if (bytes[0] === 255 && bytes[1] === 216) {
|
|
7031
|
-
const jpeg = import_jpeg_js.decode(bytes, {
|
|
7078
|
+
const jpeg = import_jpeg_js.decode(bytes, {
|
|
7079
|
+
useTArray: true,
|
|
7080
|
+
formatAsRGBA: true,
|
|
7081
|
+
maxResolutionInMP: limit / 1e6,
|
|
7082
|
+
maxMemoryUsageInMB: 1024
|
|
7083
|
+
});
|
|
7032
7084
|
return { width: jpeg.width, height: jpeg.height, data: jpeg.data };
|
|
7033
7085
|
}
|
|
7034
7086
|
throw new Error("not a PNG or JPEG image");
|
|
@@ -7060,9 +7112,8 @@ function pngHead(bytes) {
|
|
|
7060
7112
|
if (bytes[25] === 4 || bytes[25] === 6) {
|
|
7061
7113
|
return { width, height, alpha: true };
|
|
7062
7114
|
}
|
|
7063
|
-
const trns = find(bytes, "tRNS");
|
|
7064
7115
|
const idat = find(bytes, "IDAT");
|
|
7065
|
-
return { width, height, alpha:
|
|
7116
|
+
return { width, height, alpha: find(idat === -1 ? bytes : bytes.subarray(0, idat), "tRNS") !== -1 };
|
|
7066
7117
|
}
|
|
7067
7118
|
function alphaBox(image, threshold = CLEAR) {
|
|
7068
7119
|
let x0 = image.width;
|
|
@@ -7364,14 +7415,30 @@ function origins(configHome) {
|
|
|
7364
7415
|
}
|
|
7365
7416
|
|
|
7366
7417
|
// src/booru.ts
|
|
7418
|
+
import { setDefaultAutoSelectFamilyAttemptTimeout } from "node:net";
|
|
7367
7419
|
import { homedir } from "node:os";
|
|
7368
7420
|
import { join as join4 } from "node:path";
|
|
7421
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
7369
7422
|
var PAGE = 100;
|
|
7370
|
-
var
|
|
7423
|
+
var MAX_PIXELS = 25000000;
|
|
7424
|
+
var RATINGS = ["safe", "questionable", "explicit"];
|
|
7425
|
+
var BLOCKS = ["nudity", "underwear"];
|
|
7426
|
+
var EXPOSED = {
|
|
7427
|
+
nudity: new Set(["nude", "naked", "topless", "bottomless", "nipples", "naked_towel", "undressing"]),
|
|
7428
|
+
underwear: new Set(["underwear", "panties", "pantsu", "bra", "lingerie", "pantyshot"])
|
|
7429
|
+
};
|
|
7430
|
+
var MOEBOORU = { safe: "s", questionable: "q", explicit: "e" };
|
|
7371
7431
|
var MIRRORS = new Set(["danbooru", "gelbooru", "konachan", "yande.re", "sankaku"]);
|
|
7372
7432
|
var HEAD = 8191;
|
|
7373
7433
|
var TIMEOUT = 20000;
|
|
7434
|
+
var CONNECT = 3000;
|
|
7435
|
+
var MAX_WAIT = 60000;
|
|
7436
|
+
var TRIES = 3;
|
|
7374
7437
|
var AGENT = `ttheme/${package_default.version} (+${package_default.homepage})`;
|
|
7438
|
+
var paused = new Map;
|
|
7439
|
+
var HOSTS = hostMap(process.env.TTHEME_FIND_HOSTS);
|
|
7440
|
+
var CUTOUTS = cutoutMap(process.env.TTHEME_FIND_CUTOUTS);
|
|
7441
|
+
setDefaultAutoSelectFamilyAttemptTimeout(CONNECT);
|
|
7375
7442
|
function absolute(url) {
|
|
7376
7443
|
return url.startsWith("//") ? `https:${url}` : url;
|
|
7377
7444
|
}
|
|
@@ -7384,26 +7451,45 @@ function records(text) {
|
|
|
7384
7451
|
return [];
|
|
7385
7452
|
}
|
|
7386
7453
|
const raw = JSON.parse(body);
|
|
7387
|
-
|
|
7454
|
+
if (Array.isArray(raw)) {
|
|
7455
|
+
return raw;
|
|
7456
|
+
}
|
|
7457
|
+
const posts = raw.posts;
|
|
7458
|
+
return Array.isArray(posts) ? posts : [];
|
|
7459
|
+
}
|
|
7460
|
+
function tagTypes(text) {
|
|
7461
|
+
const body = text.trim();
|
|
7462
|
+
const raw = body ? JSON.parse(body) : {};
|
|
7463
|
+
const tags = raw.tags ?? {};
|
|
7464
|
+
return new Map(Object.entries(tags).map(([name, type]) => [name, String(type)]));
|
|
7388
7465
|
}
|
|
7389
|
-
function
|
|
7390
|
-
const
|
|
7391
|
-
|
|
7466
|
+
function version(url, width, height) {
|
|
7467
|
+
const file = absolute(String(url ?? ""));
|
|
7468
|
+
return { file, width: Number(width) || 0, height: Number(height) || 0, ext: extension(file) };
|
|
7469
|
+
}
|
|
7470
|
+
function post(p, raw) {
|
|
7471
|
+
const preview = absolute(String(raw.preview ?? ""));
|
|
7472
|
+
if (!raw.file || !preview || !Number(p.id)) {
|
|
7392
7473
|
return [];
|
|
7393
7474
|
}
|
|
7475
|
+
const width = Number(p.width ?? p.image_width) || 0;
|
|
7476
|
+
const height = Number(p.height ?? p.image_height) || 0;
|
|
7394
7477
|
return [
|
|
7395
7478
|
{
|
|
7396
7479
|
id: Number(p.id),
|
|
7397
|
-
width
|
|
7398
|
-
height
|
|
7399
|
-
file,
|
|
7480
|
+
width,
|
|
7481
|
+
height,
|
|
7482
|
+
file: raw.file,
|
|
7400
7483
|
preview,
|
|
7401
|
-
ext,
|
|
7402
|
-
owner: String(owner ?? ""),
|
|
7484
|
+
ext: raw.ext,
|
|
7485
|
+
owner: String(raw.owner ?? ""),
|
|
7486
|
+
artist: raw.artist,
|
|
7487
|
+
score: Number(p.score) || 0,
|
|
7403
7488
|
rating: String(p.rating ?? ""),
|
|
7404
|
-
md5: String(md5 ?? ""),
|
|
7489
|
+
md5: String(raw.md5 ?? ""),
|
|
7405
7490
|
source: String(p.source ?? ""),
|
|
7406
|
-
tags:
|
|
7491
|
+
tags: raw.tags.split(/\s+/).filter(Boolean),
|
|
7492
|
+
smaller: raw.versions.filter((v) => v.file && v.width > 0 && v.height > 0 && v.width * v.height < width * height)
|
|
7407
7493
|
}
|
|
7408
7494
|
];
|
|
7409
7495
|
}
|
|
@@ -7411,54 +7497,239 @@ function parseGelbooru(text, origin = "https://safebooru.org") {
|
|
|
7411
7497
|
return records(text).flatMap((p) => {
|
|
7412
7498
|
const stored = p.directory && p.image ? `${origin}/images/${p.directory}/${p.image}` : "";
|
|
7413
7499
|
const file = absolute(String(p.file_url || stored));
|
|
7414
|
-
return post(p,
|
|
7500
|
+
return post(p, {
|
|
7501
|
+
preview: p.preview_url,
|
|
7502
|
+
file,
|
|
7503
|
+
ext: extension(file),
|
|
7504
|
+
owner: p.owner,
|
|
7505
|
+
artist: "",
|
|
7506
|
+
md5: p.hash ?? p.md5,
|
|
7507
|
+
tags: String(p.tags ?? ""),
|
|
7508
|
+
versions: [version(p.sample_url, p.sample_width, p.sample_height)]
|
|
7509
|
+
});
|
|
7415
7510
|
});
|
|
7416
7511
|
}
|
|
7417
7512
|
function parseMoebooru(text) {
|
|
7513
|
+
const types = tagTypes(text);
|
|
7418
7514
|
return records(text).flatMap((p) => {
|
|
7419
7515
|
const file = absolute(String(p.file_url ?? ""));
|
|
7420
|
-
|
|
7516
|
+
const tags = String(p.tags ?? "");
|
|
7517
|
+
return post(p, {
|
|
7518
|
+
preview: p.preview_url,
|
|
7519
|
+
file,
|
|
7520
|
+
ext: String(p.file_ext || extension(file)).toLowerCase(),
|
|
7521
|
+
owner: p.author,
|
|
7522
|
+
artist: tags.split(/\s+/).find((tag) => types.get(tag) === "artist") ?? "",
|
|
7523
|
+
md5: p.md5,
|
|
7524
|
+
tags,
|
|
7525
|
+
versions: [
|
|
7526
|
+
version(p.jpeg_url, p.jpeg_width, p.jpeg_height),
|
|
7527
|
+
version(p.sample_url, p.sample_width, p.sample_height)
|
|
7528
|
+
]
|
|
7529
|
+
});
|
|
7530
|
+
});
|
|
7531
|
+
}
|
|
7532
|
+
function parseDanbooru(text) {
|
|
7533
|
+
return records(text).flatMap((p) => {
|
|
7534
|
+
const file = absolute(String(p.file_url ?? ""));
|
|
7535
|
+
const asset = p.media_asset ?? {};
|
|
7536
|
+
return post(p, {
|
|
7537
|
+
preview: p.preview_file_url,
|
|
7538
|
+
file,
|
|
7539
|
+
ext: String(p.file_ext || extension(file)).toLowerCase(),
|
|
7540
|
+
owner: "",
|
|
7541
|
+
artist: String(p.tag_string_artist ?? "").split(/\s+/)[0] ?? "",
|
|
7542
|
+
md5: p.md5,
|
|
7543
|
+
tags: String(p.tag_string ?? ""),
|
|
7544
|
+
versions: (asset.variants ?? []).map((v) => version(v.url, v.width, v.height)).filter((v) => v.ext === "jpg" || v.ext === "png").sort((a, b) => b.width * b.height - a.width * a.height)
|
|
7545
|
+
});
|
|
7421
7546
|
});
|
|
7422
7547
|
}
|
|
7423
7548
|
function parseCount(xml) {
|
|
7424
7549
|
return Number(/count="(\d+)"/.exec(xml)?.[1] ?? 0);
|
|
7425
7550
|
}
|
|
7426
|
-
function
|
|
7427
|
-
const
|
|
7551
|
+
function parseCounts(text) {
|
|
7552
|
+
const raw = text.trim() ? JSON.parse(text) : {};
|
|
7553
|
+
return Number(raw.counts?.posts) || 0;
|
|
7554
|
+
}
|
|
7555
|
+
function cutoutMap(spec) {
|
|
7556
|
+
const cutouts = new Map;
|
|
7557
|
+
for (const entry of (spec ?? "").split(/\s+/).filter(Boolean)) {
|
|
7558
|
+
const at2 = entry.indexOf("=");
|
|
7559
|
+
if (at2 > 0) {
|
|
7560
|
+
cutouts.set(entry.slice(0, at2), entry.slice(at2 + 1).split(",").filter(Boolean));
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
return cutouts;
|
|
7564
|
+
}
|
|
7565
|
+
function hostMap(spec) {
|
|
7566
|
+
const hosts = new Map;
|
|
7567
|
+
for (const entry of (spec ?? "").split(/[\s,]+/).filter(Boolean)) {
|
|
7568
|
+
const at2 = entry.indexOf("=");
|
|
7569
|
+
const key = entry.slice(0, at2);
|
|
7570
|
+
try {
|
|
7571
|
+
const url = new URL(entry.slice(at2 + 1));
|
|
7572
|
+
if (key && url.protocol === "https:") {
|
|
7573
|
+
hosts.set(key, url.origin);
|
|
7574
|
+
}
|
|
7575
|
+
} catch {}
|
|
7576
|
+
}
|
|
7577
|
+
return hosts;
|
|
7578
|
+
}
|
|
7579
|
+
function chosen(value, all, fallback, none) {
|
|
7580
|
+
const words = (value ?? "").split(/\s+/);
|
|
7581
|
+
if (none !== undefined && words.includes(none)) {
|
|
7582
|
+
return [];
|
|
7583
|
+
}
|
|
7584
|
+
const picked = all.filter((item) => words.includes(item));
|
|
7585
|
+
return picked.length > 0 ? picked : [...fallback];
|
|
7586
|
+
}
|
|
7587
|
+
function ratingSet(value) {
|
|
7588
|
+
return chosen(value, RATINGS, ["safe"]);
|
|
7589
|
+
}
|
|
7590
|
+
function blockSet(value) {
|
|
7591
|
+
return chosen(value, BLOCKS, BLOCKS, "none");
|
|
7592
|
+
}
|
|
7593
|
+
function tiers(safe, questionable, explicit) {
|
|
7594
|
+
return { safe: new Set(safe), questionable: new Set(questionable), explicit: new Set(explicit) };
|
|
7595
|
+
}
|
|
7596
|
+
function based(spec) {
|
|
7597
|
+
const origin = HOSTS.get(spec.key);
|
|
7598
|
+
const cutouts = CUTOUTS.get(spec.key);
|
|
7428
7599
|
return {
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
origin,
|
|
7432
|
-
cutouts,
|
|
7433
|
-
vouched
|
|
7434
|
-
|
|
7600
|
+
...spec,
|
|
7601
|
+
origin: origin ?? spec.origin,
|
|
7602
|
+
moved: origin !== undefined && origin !== spec.origin,
|
|
7603
|
+
cutouts: cutouts ?? spec.cutouts,
|
|
7604
|
+
vouched: spec.vouched && cutouts === undefined
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
function anyOf(tags, grouped) {
|
|
7608
|
+
if (tags.length < 2) {
|
|
7609
|
+
return tags.join("");
|
|
7610
|
+
}
|
|
7611
|
+
return grouped ? `( ${tags.join(" ~ ")} )` : tags.map((tag) => `~${tag}`).join(" ");
|
|
7612
|
+
}
|
|
7613
|
+
function gelbooru(raw) {
|
|
7614
|
+
const spec = based(raw);
|
|
7615
|
+
const api = (params) => `${spec.origin}/index.php?${new URLSearchParams({ page: "dapi", s: "post", q: "index", ...params })}`;
|
|
7616
|
+
return {
|
|
7617
|
+
...spec,
|
|
7618
|
+
cutouts: anyOf(spec.cutouts, true),
|
|
7619
|
+
ratings: tiers(["safe", "general"], ["questionable"], ["explicit"]),
|
|
7620
|
+
rate: () => "",
|
|
7621
|
+
best: "sort:score:desc",
|
|
7622
|
+
tagBudget: Number.POSITIVE_INFINITY,
|
|
7435
7623
|
postsUrl: (tags, page) => api({ json: "1", limit: String(PAGE), pid: String(page), tags }),
|
|
7436
7624
|
countUrl: (tags) => api({ limit: "0", tags }),
|
|
7437
7625
|
postUrl: (id) => api({ json: "1", id: String(id) }),
|
|
7438
|
-
pageUrl: (id) => `${origin}/index.php?page=post&s=view&id=${id}`,
|
|
7439
|
-
parse: (text) => parseGelbooru(text, origin)
|
|
7626
|
+
pageUrl: (id) => `${spec.origin}/index.php?page=post&s=view&id=${id}`,
|
|
7627
|
+
parse: (text) => parseGelbooru(text, spec.origin),
|
|
7628
|
+
count: parseCount
|
|
7440
7629
|
};
|
|
7441
7630
|
}
|
|
7442
|
-
function moebooru(
|
|
7443
|
-
const
|
|
7631
|
+
function moebooru(raw) {
|
|
7632
|
+
const spec = based(raw);
|
|
7633
|
+
const params = (rest) => new URLSearchParams({ api_version: "2", include_tags: "1", ...rest });
|
|
7444
7634
|
return {
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7635
|
+
...spec,
|
|
7636
|
+
cutouts: anyOf(spec.cutouts, false),
|
|
7637
|
+
ratings: tiers([MOEBOORU.safe], [MOEBOORU.questionable], [MOEBOORU.explicit]),
|
|
7638
|
+
rate: (levels) => {
|
|
7639
|
+
const [only] = levels;
|
|
7640
|
+
if (levels.length === 1 && only) {
|
|
7641
|
+
return `rating:${MOEBOORU[only]}`;
|
|
7642
|
+
}
|
|
7643
|
+
const missing = RATINGS.filter((level) => !levels.includes(level));
|
|
7644
|
+
const [left] = missing;
|
|
7645
|
+
return missing.length === 1 && left ? `-rating:${MOEBOORU[left]}` : "";
|
|
7646
|
+
},
|
|
7647
|
+
best: "order:score",
|
|
7648
|
+
tagBudget: Number.POSITIVE_INFINITY,
|
|
7649
|
+
postsUrl: (tags, page) => `${spec.origin}/post.json?${params({ limit: String(PAGE), page: String(page + 1), tags })}`,
|
|
7650
|
+
countUrl: (tags) => `${spec.origin}/post.xml?${new URLSearchParams({ limit: "1", tags })}`,
|
|
7651
|
+
postUrl: (id) => `${spec.origin}/post.json?${params({ tags: `id:${id}` })}`,
|
|
7652
|
+
pageUrl: (id) => `${spec.origin}/post/show/${id}`,
|
|
7653
|
+
parse: parseMoebooru,
|
|
7654
|
+
count: parseCount
|
|
7456
7655
|
};
|
|
7457
7656
|
}
|
|
7657
|
+
function danbooru(raw) {
|
|
7658
|
+
const spec = based(raw);
|
|
7659
|
+
return {
|
|
7660
|
+
...spec,
|
|
7661
|
+
cutouts: anyOf(spec.cutouts, false),
|
|
7662
|
+
ratings: tiers(["g"], ["s", "q"], ["e"]),
|
|
7663
|
+
rate: () => "",
|
|
7664
|
+
best: "order:score",
|
|
7665
|
+
tagBudget: 2,
|
|
7666
|
+
postsUrl: (tags, page) => `${spec.origin}/posts.json?${new URLSearchParams({ limit: String(PAGE), page: String(page + 1), tags })}`,
|
|
7667
|
+
countUrl: (tags) => `${spec.origin}/counts/posts.json?${new URLSearchParams({ tags })}`,
|
|
7668
|
+
postUrl: (id) => `${spec.origin}/posts.json?${new URLSearchParams({ limit: "1", tags: `id:${id}` })}`,
|
|
7669
|
+
pageUrl: (id) => `${spec.origin}/posts/${id}`,
|
|
7670
|
+
parse: parseDanbooru,
|
|
7671
|
+
count: parseCounts
|
|
7672
|
+
};
|
|
7673
|
+
}
|
|
7674
|
+
function siteNamed(name) {
|
|
7675
|
+
return SITES.find((site) => site.key === name || site.name === name || new URL(site.origin).host === name);
|
|
7676
|
+
}
|
|
7677
|
+
function tagsOf(query) {
|
|
7678
|
+
return query.split(/\s+/).filter(Boolean).length;
|
|
7679
|
+
}
|
|
7680
|
+
function postRef(text, fallback) {
|
|
7681
|
+
const query = text.trim();
|
|
7682
|
+
if (/^\d+$/.test(query)) {
|
|
7683
|
+
return { site: fallback, id: Number(query) };
|
|
7684
|
+
}
|
|
7685
|
+
const named = /^([\w.]+):(\d+)$/.exec(query);
|
|
7686
|
+
if (named) {
|
|
7687
|
+
const site2 = SITES.find((s) => s.key === named[1] || s.name === named[1]);
|
|
7688
|
+
return site2 && { site: site2, id: Number(named[2]) };
|
|
7689
|
+
}
|
|
7690
|
+
let url;
|
|
7691
|
+
try {
|
|
7692
|
+
url = new URL(query);
|
|
7693
|
+
} catch {
|
|
7694
|
+
return;
|
|
7695
|
+
}
|
|
7696
|
+
const site = siteNamed(url.host);
|
|
7697
|
+
const id = Number(url.searchParams.get("id") ?? url.pathname.split("/").filter(Boolean).at(-1));
|
|
7698
|
+
return site && id > 0 ? { site, id } : undefined;
|
|
7699
|
+
}
|
|
7458
7700
|
var SITES = [
|
|
7459
|
-
gelbooru(
|
|
7460
|
-
|
|
7461
|
-
|
|
7701
|
+
gelbooru({
|
|
7702
|
+
key: "safebooru",
|
|
7703
|
+
name: "safebooru",
|
|
7704
|
+
origin: "https://safebooru.org",
|
|
7705
|
+
cutouts: ["transparent_background", "vector_trace"],
|
|
7706
|
+
vouched: false,
|
|
7707
|
+
ansi: 4
|
|
7708
|
+
}),
|
|
7709
|
+
moebooru({
|
|
7710
|
+
key: "yande",
|
|
7711
|
+
name: "yande.re",
|
|
7712
|
+
origin: "https://yande.re",
|
|
7713
|
+
cutouts: ["transparent_png"],
|
|
7714
|
+
vouched: true,
|
|
7715
|
+
ansi: 5
|
|
7716
|
+
}),
|
|
7717
|
+
moebooru({
|
|
7718
|
+
key: "konachan",
|
|
7719
|
+
name: "konachan",
|
|
7720
|
+
origin: "https://konachan.net",
|
|
7721
|
+
cutouts: ["transparent", "vector"],
|
|
7722
|
+
vouched: false,
|
|
7723
|
+
ansi: 6
|
|
7724
|
+
}),
|
|
7725
|
+
danbooru({
|
|
7726
|
+
key: "danbooru",
|
|
7727
|
+
name: "danbooru",
|
|
7728
|
+
origin: "https://safebooru.donmai.us",
|
|
7729
|
+
cutouts: ["transparent_background"],
|
|
7730
|
+
vouched: false,
|
|
7731
|
+
ansi: 2
|
|
7732
|
+
})
|
|
7462
7733
|
];
|
|
7463
7734
|
function originHost(source) {
|
|
7464
7735
|
let host;
|
|
@@ -7469,8 +7740,17 @@ function originHost(source) {
|
|
|
7469
7740
|
}
|
|
7470
7741
|
return host.split(".").slice(-2).join(".");
|
|
7471
7742
|
}
|
|
7472
|
-
function
|
|
7473
|
-
return
|
|
7743
|
+
function exposed(post2, blocks = BLOCKS) {
|
|
7744
|
+
return post2.tags.filter((tag) => blocks.some((block2) => EXPOSED[block2].has(tag)));
|
|
7745
|
+
}
|
|
7746
|
+
function rated(site, post2, levels = ["safe"]) {
|
|
7747
|
+
return levels.some((level) => site.ratings[level].has(post2.rating));
|
|
7748
|
+
}
|
|
7749
|
+
function rendition(post2) {
|
|
7750
|
+
return [post2, ...post2.smaller].find((v) => v.width * v.height <= MAX_PIXELS);
|
|
7751
|
+
}
|
|
7752
|
+
function mirrored(owner) {
|
|
7753
|
+
return MIRRORS.has(owner);
|
|
7474
7754
|
}
|
|
7475
7755
|
function mates(owners) {
|
|
7476
7756
|
const byOwner = new Map;
|
|
@@ -7485,21 +7765,50 @@ function mates(owners) {
|
|
|
7485
7765
|
function cacheDir(site) {
|
|
7486
7766
|
return join4(process.env.XDG_CACHE_HOME ?? join4(homedir(), ".cache"), "ttheme", site.key);
|
|
7487
7767
|
}
|
|
7768
|
+
function retryAfter(value, now) {
|
|
7769
|
+
if (value !== null && /^\s*\d+\s*$/.test(value)) {
|
|
7770
|
+
return Number(value) * 1000;
|
|
7771
|
+
}
|
|
7772
|
+
const at2 = value === null ? Number.NaN : Date.parse(value);
|
|
7773
|
+
return Number.isNaN(at2) ? MAX_WAIT : Math.max(0, at2 - now);
|
|
7774
|
+
}
|
|
7775
|
+
function pausedUntil(site) {
|
|
7776
|
+
return paused.get(site.key) ?? 0;
|
|
7777
|
+
}
|
|
7778
|
+
function challenged(response) {
|
|
7779
|
+
return (response.headers.get("server") ?? "").startsWith("cloudflare") && response.headers.get("cf-mitigated") === "challenge";
|
|
7780
|
+
}
|
|
7488
7781
|
async function get(site, url, signal, headers = {}, timeout = TIMEOUT) {
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7782
|
+
for (let tries = 1;; tries++) {
|
|
7783
|
+
const wait = pausedUntil(site) - Date.now();
|
|
7784
|
+
if (wait > 0) {
|
|
7785
|
+
await sleep(wait, undefined, { signal });
|
|
7786
|
+
}
|
|
7787
|
+
const response = await fetch(url, {
|
|
7788
|
+
headers: { "User-Agent": AGENT, Referer: `${site.origin}/`, ...headers },
|
|
7789
|
+
signal: timeout ? AbortSignal.any([signal, AbortSignal.timeout(timeout)]) : signal
|
|
7790
|
+
});
|
|
7791
|
+
if (response.status === 429 && tries < TRIES) {
|
|
7792
|
+
await response.body?.cancel();
|
|
7793
|
+
const delay = retryAfter(response.headers.get("retry-after"), Date.now());
|
|
7794
|
+
if (delay > MAX_WAIT) {
|
|
7795
|
+
throw new Error(`${site.name} asks to wait ${Math.ceil(delay / 1000)}s`);
|
|
7796
|
+
}
|
|
7797
|
+
paused.set(site.key, Math.max(pausedUntil(site), Date.now() + delay));
|
|
7798
|
+
continue;
|
|
7799
|
+
}
|
|
7800
|
+
if (!response.ok) {
|
|
7801
|
+
await response.body?.cancel();
|
|
7802
|
+
throw new Error(challenged(response) ? `${site.name} is behind a Cloudflare challenge` : `${site.name} answered ${response.status}`);
|
|
7803
|
+
}
|
|
7804
|
+
return response;
|
|
7495
7805
|
}
|
|
7496
|
-
return response;
|
|
7497
7806
|
}
|
|
7498
7807
|
async function fetchPosts(site, tags, page, signal) {
|
|
7499
7808
|
return site.parse(await (await get(site, site.postsUrl(tags, page), signal)).text());
|
|
7500
7809
|
}
|
|
7501
7810
|
async function fetchCount(site, tags, signal) {
|
|
7502
|
-
return
|
|
7811
|
+
return site.count(await (await get(site, site.countUrl(tags), signal)).text());
|
|
7503
7812
|
}
|
|
7504
7813
|
async function fetchPost(site, id, signal) {
|
|
7505
7814
|
const [found] = site.parse(await (await get(site, site.postUrl(id), signal)).text());
|
|
@@ -7614,8 +7923,66 @@ function search(palettes, query) {
|
|
|
7614
7923
|
return palettes.filter((p) => [p.name, p.group, p.native ?? "", p.ansiSource].some((field) => field.toLowerCase().includes(needle)));
|
|
7615
7924
|
}
|
|
7616
7925
|
|
|
7926
|
+
// src/cutout.ts
|
|
7927
|
+
import { spawn } from "node:child_process";
|
|
7928
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7929
|
+
var TIMEOUT2 = 30000;
|
|
7930
|
+
var LEAST = 3;
|
|
7931
|
+
var MOST = 97;
|
|
7932
|
+
var SCRIPT = `ObjC.import('Vision')
|
|
7933
|
+
ObjC.import('CoreImage')
|
|
7934
|
+
ObjC.import('CoreGraphics')
|
|
7935
|
+
function run(argv) {
|
|
7936
|
+
const error = Ref()
|
|
7937
|
+
const image = $.CIImage.imageWithContentsOfURL($.NSURL.fileURLWithPath(argv[0]))
|
|
7938
|
+
if (!image) throw new Error('unreadable image')
|
|
7939
|
+
const handler = $.VNImageRequestHandler.alloc.initWithCIImageOptions(image, $({}))
|
|
7940
|
+
const request = $.VNGenerateForegroundInstanceMaskRequest.alloc.init
|
|
7941
|
+
if (!handler.performRequestsError($([request]), error)) throw new Error('vision failed')
|
|
7942
|
+
if (request.results.count === 0) throw new Error('no foreground')
|
|
7943
|
+
const found = request.results.objectAtIndex(0)
|
|
7944
|
+
const buffer = found.generateMaskedImageOfInstancesFromRequestHandlerCroppedToInstancesExtentError(
|
|
7945
|
+
found.allInstances, handler, false, error)
|
|
7946
|
+
if (!buffer) throw new Error('mask failed')
|
|
7947
|
+
const written = $.CIContext.context.writePNGRepresentationOfImageToURLFormatColorSpaceOptionsError(
|
|
7948
|
+
$.CIImage.imageWithCVPixelBuffer(buffer), $.NSURL.fileURLWithPath(argv[1]), $.kCIFormatRGBA8,
|
|
7949
|
+
$.CGColorSpaceCreateWithName($.kCGColorSpaceSRGB), $({}), error)
|
|
7950
|
+
if (!written) throw new Error('write failed')
|
|
7951
|
+
}`;
|
|
7952
|
+
function canRemoveBackground() {
|
|
7953
|
+
return process.platform === "darwin";
|
|
7954
|
+
}
|
|
7955
|
+
function keepable(clear) {
|
|
7956
|
+
return clear >= LEAST && clear <= MOST;
|
|
7957
|
+
}
|
|
7958
|
+
function removeBackground(input, output, signal) {
|
|
7959
|
+
return new Promise((resolve, reject) => {
|
|
7960
|
+
const child = spawn("osascript", ["-l", "JavaScript", "-e", SCRIPT, input, output], {
|
|
7961
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
7962
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT2)])
|
|
7963
|
+
});
|
|
7964
|
+
let failure = "";
|
|
7965
|
+
child.stderr.on("data", (chunk) => {
|
|
7966
|
+
failure += chunk;
|
|
7967
|
+
});
|
|
7968
|
+
child.on("error", reject);
|
|
7969
|
+
child.on("close", (code) => {
|
|
7970
|
+
if (code !== 0) {
|
|
7971
|
+
reject(new Error(failure.trim().split(`
|
|
7972
|
+
`).at(-1) || `osascript exited ${code}`));
|
|
7973
|
+
return;
|
|
7974
|
+
}
|
|
7975
|
+
try {
|
|
7976
|
+
resolve(decodePng(new Uint8Array(readFileSync3(output))));
|
|
7977
|
+
} catch (error) {
|
|
7978
|
+
reject(error);
|
|
7979
|
+
}
|
|
7980
|
+
});
|
|
7981
|
+
});
|
|
7982
|
+
}
|
|
7983
|
+
|
|
7617
7984
|
// src/find-screen.ts
|
|
7618
|
-
var TILE = { pitch: 25, cols: 22, rows: 9, height:
|
|
7985
|
+
var TILE = { pitch: 25, cols: 22, rows: 9, height: 13 };
|
|
7619
7986
|
var MIN = { cols: 25, rows: 16 };
|
|
7620
7987
|
var TRY_ID = 2 ** 31;
|
|
7621
7988
|
var BELOW_BG = -1073741826;
|
|
@@ -7652,14 +8019,14 @@ function decodeKeys(input) {
|
|
|
7652
8019
|
if (m) {
|
|
7653
8020
|
keys.push(CSI[`${m[1] ?? ""}${m[2] ?? m[3] ?? ""}`] ?? "nop");
|
|
7654
8021
|
i += 1 + m[0].length;
|
|
7655
|
-
|
|
7656
|
-
keys.push("esc");
|
|
7657
|
-
i++;
|
|
8022
|
+
continue;
|
|
7658
8023
|
}
|
|
8024
|
+
keys.push("esc");
|
|
8025
|
+
i++;
|
|
7659
8026
|
continue;
|
|
7660
8027
|
}
|
|
7661
8028
|
keys.push(c === "\r" || c === `
|
|
7662
|
-
` ? "enter" : c === "\t" ? "tab" : c === "\x03" ? "ctrl-c" : c);
|
|
8029
|
+
` ? "enter" : c === "\t" ? "tab" : c === "\x03" ? "ctrl-c" : c === "" || c === "\b" ? "backspace" : c);
|
|
7663
8030
|
i++;
|
|
7664
8031
|
}
|
|
7665
8032
|
return keys;
|
|
@@ -7730,8 +8097,14 @@ function progress(got, size) {
|
|
|
7730
8097
|
}
|
|
7731
8098
|
return size >= 1e6 ? `${(got / 1e6).toFixed(1)}/${(size / 1e6).toFixed(1)} MB` : `${Math.round(got / 1000)}/${Math.round(size / 1000)} KB`;
|
|
7732
8099
|
}
|
|
8100
|
+
function plain(artist) {
|
|
8101
|
+
return artist.replace(/_\([^)]*\)?$/, "");
|
|
8102
|
+
}
|
|
7733
8103
|
function dims(tile) {
|
|
7734
|
-
return [
|
|
8104
|
+
return [
|
|
8105
|
+
`${tile.reduced ? "↓" : ""}${tile.width}×${tile.height}`,
|
|
8106
|
+
Math.max(tile.width, tile.height) < SMALL ? YELLOW : D
|
|
8107
|
+
];
|
|
7735
8108
|
}
|
|
7736
8109
|
function foot(line, cols, accent, spec) {
|
|
7737
8110
|
const keys = [...spec.keys ?? []];
|
|
@@ -7854,23 +8227,61 @@ function frameBox(lines, r0, c0, h, w, sgr) {
|
|
|
7854
8227
|
}
|
|
7855
8228
|
lines[r0 + h - 1]?.put(c0, `╰${"─".repeat(w - 2)}╯`, sgr);
|
|
7856
8229
|
}
|
|
7857
|
-
function badge(view) {
|
|
7858
|
-
return [` ${
|
|
8230
|
+
function badge(view, label = view.site) {
|
|
8231
|
+
return [` ${label} `, `\x1B[7;${30 + view.siteAnsi}m`];
|
|
8232
|
+
}
|
|
8233
|
+
function tabs(line, cols, view) {
|
|
8234
|
+
const strip = SITES.flatMap((site, i) => {
|
|
8235
|
+
const label = `${site.name}${site.moved ? "*" : ""}`;
|
|
8236
|
+
const tab = site.name === view.site ? badge(view, label) : [` ${label} `, D];
|
|
8237
|
+
return i ? [[" ", ""], tab] : [tab];
|
|
8238
|
+
});
|
|
8239
|
+
const fits = strip.reduce((n, [text]) => n + width(text), 0) <= cols;
|
|
8240
|
+
line.run(0, fits ? strip : [badge(view)]);
|
|
7859
8241
|
}
|
|
7860
8242
|
function query(line, cols, view, accent) {
|
|
8243
|
+
if (view.editing !== undefined) {
|
|
8244
|
+
const c2 = line.run(0, [
|
|
8245
|
+
["⌕ ", accent],
|
|
8246
|
+
[view.editing, ""],
|
|
8247
|
+
["█", accent]
|
|
8248
|
+
]);
|
|
8249
|
+
line.put(c2, view.editing ? " enter searches" : ` a tag, a post url or an id — ${view.tag || "esc leaves"}`, D);
|
|
8250
|
+
return;
|
|
8251
|
+
}
|
|
7861
8252
|
const c = line.run(0, [
|
|
7862
8253
|
["⌕ ", accent],
|
|
7863
|
-
[view.tag, ""]
|
|
8254
|
+
[view.tag || "nothing yet — / searches", view.tag ? "" : D]
|
|
7864
8255
|
]);
|
|
7865
|
-
|
|
8256
|
+
const allowed = BLOCKS.filter((block2) => !view.block.includes(block2));
|
|
8257
|
+
const state = [
|
|
8258
|
+
view.preset,
|
|
8259
|
+
view.order,
|
|
8260
|
+
...view.rating.join("+") === "safe" ? [] : [view.rating.join("+")],
|
|
8261
|
+
...allowed.length > 0 ? [`allows ${allowed.join("+")}`] : [],
|
|
8262
|
+
...view.unblocked ? ["unblock"] : []
|
|
8263
|
+
];
|
|
8264
|
+
line.put(c, ` ${state.join(" ")}`, D);
|
|
7866
8265
|
if (view.total > 0 || !view.searching) {
|
|
7867
|
-
const counter = [
|
|
8266
|
+
const counter = [[`${view.tiles.length}/${view.checked}`, ""]];
|
|
7868
8267
|
if (view.checked < view.total) {
|
|
7869
8268
|
counter.push([` of ${view.total}`, D]);
|
|
7870
8269
|
}
|
|
7871
8270
|
line.right(cols, counter);
|
|
7872
8271
|
}
|
|
7873
8272
|
}
|
|
8273
|
+
function transparent(shown) {
|
|
8274
|
+
if (shown.cut === "on") {
|
|
8275
|
+
return [[`cut out · ${shown.clear}% transparent`, GREEN]];
|
|
8276
|
+
}
|
|
8277
|
+
if (shown.cut === "off") {
|
|
8278
|
+
return [["opaque · x cuts out", YELLOW]];
|
|
8279
|
+
}
|
|
8280
|
+
if (shown.cut === "failed") {
|
|
8281
|
+
return [["opaque · no cut-out", YELLOW]];
|
|
8282
|
+
}
|
|
8283
|
+
return [shown.clear > 0 ? [`${shown.clear}% transparent`, GREEN] : ["opaque", YELLOW]];
|
|
8284
|
+
}
|
|
7874
8285
|
function status(view) {
|
|
7875
8286
|
if (view.installing !== undefined) {
|
|
7876
8287
|
return [`installing ${view.palette} ← ${view.site} ${view.installing}`, YELLOW];
|
|
@@ -7878,18 +8289,27 @@ function status(view) {
|
|
|
7878
8289
|
if (view.error) {
|
|
7879
8290
|
return [view.error, YELLOW];
|
|
7880
8291
|
}
|
|
8292
|
+
if (view.waiting !== undefined) {
|
|
8293
|
+
return [`${view.site} asked to slow down · ${view.waiting}s`, YELLOW];
|
|
8294
|
+
}
|
|
7881
8295
|
if (view.fetching) {
|
|
7882
8296
|
return [`fetching ${view.fetching.id} · ${progress(view.fetching.got, view.fetching.size)}`, YELLOW];
|
|
7883
8297
|
}
|
|
8298
|
+
if (view.cutting !== undefined) {
|
|
8299
|
+
return [`cutting out ${view.cutting}`, YELLOW];
|
|
8300
|
+
}
|
|
7884
8301
|
if (view.preparing !== undefined) {
|
|
7885
8302
|
return [`preparing ${view.preparing}`, YELLOW];
|
|
7886
8303
|
}
|
|
8304
|
+
if (view.note) {
|
|
8305
|
+
return [view.note, D];
|
|
8306
|
+
}
|
|
7887
8307
|
return;
|
|
7888
8308
|
}
|
|
7889
8309
|
function grid(lines, images, cols, rows, view, accent) {
|
|
7890
8310
|
const { perRow, rowsVis } = gridShape(cols, rows);
|
|
7891
8311
|
query(lines[0], cols, view, accent);
|
|
7892
|
-
lines[1]
|
|
8312
|
+
tabs(lines[1], cols, view);
|
|
7893
8313
|
for (let k = 0;k < perRow * rowsVis; k++) {
|
|
7894
8314
|
const i = view.top * perRow + k;
|
|
7895
8315
|
const tile = view.tiles[i];
|
|
@@ -7909,6 +8329,20 @@ function grid(lines, images, cols, rows, view, accent) {
|
|
|
7909
8329
|
if (tile.mates.length > 0) {
|
|
7910
8330
|
lines[r0 + 10]?.put(c0 + TILE.cols, "≈", accent);
|
|
7911
8331
|
}
|
|
8332
|
+
const credit = tile.artist ? plain(tile.artist) : tile.owner && `@${tile.owner}`;
|
|
8333
|
+
if (credit) {
|
|
8334
|
+
lines[r0 + 11]?.put(c0 + 1, credit.slice(0, TILE.cols - 8), D);
|
|
8335
|
+
}
|
|
8336
|
+
const marks = [];
|
|
8337
|
+
if (tile.score > 0) {
|
|
8338
|
+
marks.push([`★${tile.score}`, D]);
|
|
8339
|
+
}
|
|
8340
|
+
if (tile.variants > 1) {
|
|
8341
|
+
marks.push([` ×${tile.variants}`, accent]);
|
|
8342
|
+
}
|
|
8343
|
+
if (marks.length > 0) {
|
|
8344
|
+
lines[r0 + 11]?.right(c0 + TILE.cols + 1, marks);
|
|
8345
|
+
}
|
|
7912
8346
|
}
|
|
7913
8347
|
const above = view.top * perRow;
|
|
7914
8348
|
const below = view.tiles.length - (view.top + rowsVis) * perRow;
|
|
@@ -7917,7 +8351,7 @@ function grid(lines, images, cols, rows, view, accent) {
|
|
|
7917
8351
|
lines[rows - 2]?.put(0, scroll.join(" "), D);
|
|
7918
8352
|
}
|
|
7919
8353
|
if (!view.searching && view.tiles.length === 0 && !view.error) {
|
|
7920
|
-
lines[3]?.put(0, view.preset === "cutouts" ? `no transparent cutouts of ${view.tag} on ${view.site} —
|
|
8354
|
+
lines[3]?.put(0, view.preset === "cutouts" ? `no transparent cutouts of ${view.tag} on ${view.site} — c searches every post, tab tries ${view.nextSite}` : `no posts of ${view.tag} on ${view.site} — tab tries ${view.nextSite}`, D);
|
|
7921
8355
|
}
|
|
7922
8356
|
const line = lines[rows - 1];
|
|
7923
8357
|
const lead = status(view);
|
|
@@ -7935,8 +8369,9 @@ function grid(lines, images, cols, rows, view, accent) {
|
|
|
7935
8369
|
keys: [
|
|
7936
8370
|
["←↑↓→", "move"],
|
|
7937
8371
|
["enter", "try on"],
|
|
7938
|
-
["tab",
|
|
7939
|
-
["
|
|
8372
|
+
["tab", "site"],
|
|
8373
|
+
["s", "settings"],
|
|
8374
|
+
["/", "search"],
|
|
7940
8375
|
["?", "keys"]
|
|
7941
8376
|
],
|
|
7942
8377
|
right: ["esc", "back"]
|
|
@@ -7949,6 +8384,12 @@ function trial(lines, images, cols, rows, view, accent) {
|
|
|
7949
8384
|
}
|
|
7950
8385
|
const shown = view.shown?.id === tile.id ? view.shown : undefined;
|
|
7951
8386
|
const meta2 = [badge(view), [" ", ""], [String(tile.id), B], [" ", ""], dims(tile)];
|
|
8387
|
+
if (tile.artist) {
|
|
8388
|
+
meta2.push([` · ${plain(tile.artist)}`, ""]);
|
|
8389
|
+
}
|
|
8390
|
+
if (tile.score > 0) {
|
|
8391
|
+
meta2.push([` · ★${tile.score}`, D]);
|
|
8392
|
+
}
|
|
7952
8393
|
if (shown) {
|
|
7953
8394
|
meta2.push([` · ${megabytes(shown.bytes)}`, D]);
|
|
7954
8395
|
}
|
|
@@ -7957,7 +8398,7 @@ function trial(lines, images, cols, rows, view, accent) {
|
|
|
7957
8398
|
meta2.push([` · ${tile.origin}`, D]);
|
|
7958
8399
|
}
|
|
7959
8400
|
if (shown) {
|
|
7960
|
-
meta2.push([" ", ""], shown
|
|
8401
|
+
meta2.push([" ", ""], ...transparent(shown));
|
|
7961
8402
|
}
|
|
7962
8403
|
lines[0]?.run(0, meta2);
|
|
7963
8404
|
if (tile.mates.length > 0) {
|
|
@@ -7977,6 +8418,7 @@ function trial(lines, images, cols, rows, view, accent) {
|
|
|
7977
8418
|
keys: view.installing === undefined ? [
|
|
7978
8419
|
["←→", "browse"],
|
|
7979
8420
|
["enter", "install"],
|
|
8421
|
+
...view.shown?.cut === "on" || view.shown?.cut === "off" ? [["x", "cut out"]] : [],
|
|
7980
8422
|
["?", "keys"]
|
|
7981
8423
|
] : [],
|
|
7982
8424
|
right: view.installing === undefined ? ["esc", "grid"] : undefined
|
|
@@ -7986,18 +8428,60 @@ var KEYS = {
|
|
|
7986
8428
|
grid: [
|
|
7987
8429
|
["move", "←↑↓→ home end pgup pgdn"],
|
|
7988
8430
|
["try on", "enter"],
|
|
7989
|
-
["
|
|
7990
|
-
["
|
|
8431
|
+
["site", `tab ${SITES.map((site) => site.name).join(", ")}`],
|
|
8432
|
+
["posts", "c cutouts or every post"],
|
|
8433
|
+
["search", "/ a tag, a post url or an id"],
|
|
8434
|
+
["unfold", "space a set of ×N"],
|
|
8435
|
+
["open", "o the post page in a browser"],
|
|
8436
|
+
["settings", "s rating, block, posts, order, sets, remove bg"],
|
|
7991
8437
|
["back", "esc returns to preview"],
|
|
7992
8438
|
["close", "? esc"]
|
|
7993
8439
|
],
|
|
7994
8440
|
try: [
|
|
7995
8441
|
["browse", "←→"],
|
|
7996
8442
|
["install", "enter"],
|
|
8443
|
+
["cut out", "x the background off or on, on an opaque picture"],
|
|
8444
|
+
["open", "o the post page in a browser"],
|
|
7997
8445
|
["grid", "esc"],
|
|
7998
8446
|
["close", "? esc"]
|
|
7999
8447
|
]
|
|
8000
8448
|
};
|
|
8449
|
+
function panel(lines, cols, rows, view, accent) {
|
|
8450
|
+
const at2 = view.panel ?? 0;
|
|
8451
|
+
const label = Math.max(...view.settings.map((row) => row.label.length));
|
|
8452
|
+
const widest = Math.max(...view.settings.map((row) => row.choices.reduce((n, c) => n + c.length + (row.multi ? 6 : 3), 0)));
|
|
8453
|
+
const w = Math.min(cols - 2, Math.max(28, label + widest + 8));
|
|
8454
|
+
const h = view.settings.length + 4;
|
|
8455
|
+
const x = Math.floor((cols - w) / 2);
|
|
8456
|
+
const y = Math.max(2, Math.floor((rows - h) / 2));
|
|
8457
|
+
lines[y]?.put(x, `╭─ settings ${"─".repeat(Math.max(0, w - 13))}╮`);
|
|
8458
|
+
for (let r = y + 1;r < y + h - 1; r++) {
|
|
8459
|
+
lines[r]?.put(x, `│${" ".repeat(w - 2)}│`);
|
|
8460
|
+
}
|
|
8461
|
+
view.settings.forEach((row, i) => {
|
|
8462
|
+
const line = lines[y + 2 + i];
|
|
8463
|
+
line.put(x + 3, row.label, i === at2 ? B : D);
|
|
8464
|
+
let c = x + 5 + label;
|
|
8465
|
+
const on = row.value.split(" ");
|
|
8466
|
+
row.choices.forEach((choice, k) => {
|
|
8467
|
+
const lit = on.includes(choice);
|
|
8468
|
+
const under = row.multi && i === at2 && k === row.cursor ? "4;" : "";
|
|
8469
|
+
const text = row.multi ? ` ${lit ? "[x]" : "[ ]"} ${choice} ` : ` ${choice} `;
|
|
8470
|
+
c = line.put(c, text, lit ? `\x1B[${under}7;${30 + view.siteAnsi}m` : under ? `\x1B[4m${D}` : D) + 1;
|
|
8471
|
+
});
|
|
8472
|
+
});
|
|
8473
|
+
lines[y + h - 1]?.put(x, `╰${"─".repeat(w - 2)}╯`);
|
|
8474
|
+
foot(lines[rows - 1], cols, accent, {
|
|
8475
|
+
badge: "SET",
|
|
8476
|
+
keys: [
|
|
8477
|
+
["↑↓", "setting"],
|
|
8478
|
+
["←→", "value"],
|
|
8479
|
+
["space", "toggle"],
|
|
8480
|
+
["enter", "save"]
|
|
8481
|
+
],
|
|
8482
|
+
right: ["esc", "undo"]
|
|
8483
|
+
});
|
|
8484
|
+
}
|
|
8001
8485
|
function help(lines, cols, rows, view, accent) {
|
|
8002
8486
|
const keys = KEYS[view.mode];
|
|
8003
8487
|
const w = Math.min(cols - 2, 50);
|
|
@@ -8030,19 +8514,23 @@ function renderFind(view, cols, rows) {
|
|
|
8030
8514
|
} else {
|
|
8031
8515
|
grid(lines, images, cols, rows, view, accent);
|
|
8032
8516
|
}
|
|
8033
|
-
if (view.help) {
|
|
8517
|
+
if (view.help || view.panel !== undefined) {
|
|
8034
8518
|
const keep = view.mode === "try" ? images : [];
|
|
8035
8519
|
for (let r = 0;r < rows; r++) {
|
|
8036
8520
|
lines[r] = new Line(cols);
|
|
8037
8521
|
}
|
|
8038
|
-
|
|
8522
|
+
if (view.help) {
|
|
8523
|
+
help(lines, cols, rows, view, accent);
|
|
8524
|
+
} else {
|
|
8525
|
+
panel(lines, cols, rows, view, accent);
|
|
8526
|
+
}
|
|
8039
8527
|
return { lines: lines.map((l) => l.render()), images: keep };
|
|
8040
8528
|
}
|
|
8041
8529
|
return { lines: lines.map((l) => l.render()), images };
|
|
8042
8530
|
}
|
|
8043
8531
|
|
|
8044
8532
|
// src/palettes.ts
|
|
8045
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as
|
|
8533
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8046
8534
|
import { homedir as homedir2 } from "node:os";
|
|
8047
8535
|
import { dirname as dirname2, join as join6 } from "node:path";
|
|
8048
8536
|
var EMITTERS = { ghostty, kitty, alacritty };
|
|
@@ -8057,7 +8545,7 @@ function readInstalled(configHome2) {
|
|
|
8057
8545
|
if (!existsSync2(path2)) {
|
|
8058
8546
|
throw new Error(`nothing installed yet at ${path2} — run \`ttheme init\` first`);
|
|
8059
8547
|
}
|
|
8060
|
-
const doc = JSON.parse(
|
|
8548
|
+
const doc = JSON.parse(readFileSync4(path2, "utf8"));
|
|
8061
8549
|
if (!Array.isArray(doc?.palettes) || !Array.isArray(doc.terminals)) {
|
|
8062
8550
|
throw new Error(`${path2} has no palettes or terminals`);
|
|
8063
8551
|
}
|
|
@@ -8152,7 +8640,7 @@ function sync(configHome2, catalog, state) {
|
|
|
8152
8640
|
}
|
|
8153
8641
|
}
|
|
8154
8642
|
const { file, body } = blockFor(terminal, configHome2, startup);
|
|
8155
|
-
const current = existsSync2(file) ?
|
|
8643
|
+
const current = existsSync2(file) ? readFileSync4(file, "utf8") : "";
|
|
8156
8644
|
if (terminal === "alacritty" && current !== "" && !current.includes("# ttheme begin")) {
|
|
8157
8645
|
continue;
|
|
8158
8646
|
}
|
|
@@ -8179,11 +8667,160 @@ function forget(configHome2, catalog, terminals, names) {
|
|
|
8179
8667
|
return removed;
|
|
8180
8668
|
}
|
|
8181
8669
|
|
|
8670
|
+
// src/unblock.ts
|
|
8671
|
+
import { connect, createServer } from "node:net";
|
|
8672
|
+
var SPLIT = 20;
|
|
8673
|
+
var HANDSHAKE = 22;
|
|
8674
|
+
function joined(parts) {
|
|
8675
|
+
const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
|
|
8676
|
+
let at2 = 0;
|
|
8677
|
+
for (const part of parts) {
|
|
8678
|
+
out.set(part, at2);
|
|
8679
|
+
at2 += part.length;
|
|
8680
|
+
}
|
|
8681
|
+
return out;
|
|
8682
|
+
}
|
|
8683
|
+
function reframe(record, at2 = SPLIT) {
|
|
8684
|
+
const body = record.subarray(5);
|
|
8685
|
+
if (record[0] !== HANDSHAKE || body.length < 2) {
|
|
8686
|
+
return [record];
|
|
8687
|
+
}
|
|
8688
|
+
const cut = Math.max(1, Math.min(at2, body.length - 1));
|
|
8689
|
+
const framed = (part) => joined([
|
|
8690
|
+
Uint8Array.from([
|
|
8691
|
+
record[0],
|
|
8692
|
+
record[1],
|
|
8693
|
+
record[2],
|
|
8694
|
+
part.length >> 8,
|
|
8695
|
+
part.length & 255
|
|
8696
|
+
]),
|
|
8697
|
+
part
|
|
8698
|
+
]);
|
|
8699
|
+
return [framed(body.subarray(0, cut)), framed(body.subarray(cut))];
|
|
8700
|
+
}
|
|
8701
|
+
function tunnelTo(client, host, port, rest) {
|
|
8702
|
+
const upstream = connect(port, host, () => {
|
|
8703
|
+
upstream.setNoDelay(true);
|
|
8704
|
+
client.write(`HTTP/1.1 200 Connection Established\r
|
|
8705
|
+
\r
|
|
8706
|
+
`);
|
|
8707
|
+
let held = rest;
|
|
8708
|
+
let framed = false;
|
|
8709
|
+
const forward = (chunk) => {
|
|
8710
|
+
if (framed) {
|
|
8711
|
+
upstream.write(chunk);
|
|
8712
|
+
return;
|
|
8713
|
+
}
|
|
8714
|
+
held = joined([held, chunk]);
|
|
8715
|
+
if (held.length < 5) {
|
|
8716
|
+
return;
|
|
8717
|
+
}
|
|
8718
|
+
const length = held[3] * 256 + held[4];
|
|
8719
|
+
if (held[0] !== HANDSHAKE) {
|
|
8720
|
+
framed = true;
|
|
8721
|
+
upstream.write(held);
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
if (held.length < 5 + length) {
|
|
8725
|
+
return;
|
|
8726
|
+
}
|
|
8727
|
+
framed = true;
|
|
8728
|
+
const [first, second] = reframe(held.subarray(0, 5 + length));
|
|
8729
|
+
upstream.write(first);
|
|
8730
|
+
setTimeout(() => {
|
|
8731
|
+
if (second) {
|
|
8732
|
+
upstream.write(second);
|
|
8733
|
+
}
|
|
8734
|
+
upstream.write(held.subarray(5 + length));
|
|
8735
|
+
}, 10);
|
|
8736
|
+
};
|
|
8737
|
+
client.on("data", forward);
|
|
8738
|
+
if (held.length > 0) {
|
|
8739
|
+
const pending = held;
|
|
8740
|
+
held = new Uint8Array(0);
|
|
8741
|
+
forward(pending);
|
|
8742
|
+
}
|
|
8743
|
+
upstream.pipe(client);
|
|
8744
|
+
});
|
|
8745
|
+
upstream.on("error", () => client.destroy());
|
|
8746
|
+
client.on("error", () => upstream.destroy());
|
|
8747
|
+
}
|
|
8748
|
+
function serve(client) {
|
|
8749
|
+
client.setNoDelay(true);
|
|
8750
|
+
let head = new Uint8Array(0);
|
|
8751
|
+
const onData = (chunk) => {
|
|
8752
|
+
head = joined([head, chunk]);
|
|
8753
|
+
const text = Buffer.from(head).toString("latin1");
|
|
8754
|
+
const end = text.indexOf(`\r
|
|
8755
|
+
\r
|
|
8756
|
+
`);
|
|
8757
|
+
if (end === -1) {
|
|
8758
|
+
return;
|
|
8759
|
+
}
|
|
8760
|
+
client.off("data", onData);
|
|
8761
|
+
const target = /^CONNECT ([^ :]+):(\d+)/.exec(text);
|
|
8762
|
+
if (!target) {
|
|
8763
|
+
client.end(`HTTP/1.1 405 Method Not Allowed\r
|
|
8764
|
+
\r
|
|
8765
|
+
`);
|
|
8766
|
+
return;
|
|
8767
|
+
}
|
|
8768
|
+
tunnelTo(client, target[1], Number(target[2]), head.subarray(end + 4));
|
|
8769
|
+
};
|
|
8770
|
+
client.on("data", onData);
|
|
8771
|
+
client.on("error", () => client.destroy());
|
|
8772
|
+
}
|
|
8773
|
+
function tunnel() {
|
|
8774
|
+
const server = createServer(serve);
|
|
8775
|
+
return new Promise((resolve2, reject) => {
|
|
8776
|
+
server.once("error", reject);
|
|
8777
|
+
server.listen(0, "127.0.0.1", () => {
|
|
8778
|
+
resolve2({
|
|
8779
|
+
port: server.address().port,
|
|
8780
|
+
close: () => {
|
|
8781
|
+
server.close();
|
|
8782
|
+
server.unref();
|
|
8783
|
+
}
|
|
8784
|
+
});
|
|
8785
|
+
});
|
|
8786
|
+
});
|
|
8787
|
+
}
|
|
8788
|
+
|
|
8182
8789
|
// src/find.ts
|
|
8790
|
+
var SETTINGS = [
|
|
8791
|
+
{ name: "TTHEME_FIND_RATING", label: "rating", choices: RATINGS, multi: { read: ratingSet } },
|
|
8792
|
+
{ name: "TTHEME_FIND_BLOCK", label: "block", choices: BLOCKS, multi: { read: blockSet, none: "none" } },
|
|
8793
|
+
{ name: "TTHEME_FIND_POSTS", label: "posts", choices: ["all", "cutouts"] },
|
|
8794
|
+
{ name: "TTHEME_FIND_ORDER", label: "order", choices: ["newest", "score"] },
|
|
8795
|
+
{ name: "TTHEME_FIND_SETS", label: "sets", choices: ["fold", "show"] },
|
|
8796
|
+
...canRemoveBackground() ? [{ name: "TTHEME_FIND_REMOVE_BG", label: "remove bg", choices: ["on", "off"] }] : []
|
|
8797
|
+
];
|
|
8798
|
+
function initial(setting, raw) {
|
|
8799
|
+
if (setting.multi) {
|
|
8800
|
+
return setting.multi.read(raw).join(" ") || setting.multi.none;
|
|
8801
|
+
}
|
|
8802
|
+
return setting.choices.find((choice) => choice === raw) ?? setting.choices[0];
|
|
8803
|
+
}
|
|
8183
8804
|
var WORKERS = 4;
|
|
8805
|
+
var PRELOAD = 2;
|
|
8184
8806
|
var SETTLE = 150;
|
|
8807
|
+
var ESCAPE = 30;
|
|
8185
8808
|
var TRY_WIDTH = 1280;
|
|
8186
8809
|
var LOOKAHEAD = 1;
|
|
8810
|
+
function blank(key, searching) {
|
|
8811
|
+
return {
|
|
8812
|
+
key,
|
|
8813
|
+
groups: [],
|
|
8814
|
+
sources: [],
|
|
8815
|
+
queue: [],
|
|
8816
|
+
seen: new Set,
|
|
8817
|
+
checked: 0,
|
|
8818
|
+
total: 0,
|
|
8819
|
+
focus: 0,
|
|
8820
|
+
top: 0,
|
|
8821
|
+
searching
|
|
8822
|
+
};
|
|
8823
|
+
}
|
|
8187
8824
|
function describe(error) {
|
|
8188
8825
|
if (!(error instanceof Error)) {
|
|
8189
8826
|
return String(error);
|
|
@@ -8195,14 +8832,34 @@ function describe(error) {
|
|
|
8195
8832
|
return describe(error.cause);
|
|
8196
8833
|
}
|
|
8197
8834
|
const code = error.code;
|
|
8198
|
-
|
|
8835
|
+
if (typeof code === "string") {
|
|
8836
|
+
return code;
|
|
8837
|
+
}
|
|
8838
|
+
const first = (error.message || error.name).split(".")[0] ?? "";
|
|
8839
|
+
return first.length > 56 ? `${first.slice(0, 55)}…` : first;
|
|
8199
8840
|
}
|
|
8200
8841
|
function reason(site, error) {
|
|
8201
8842
|
return `${site.name}: ${describe(error)}`;
|
|
8202
8843
|
}
|
|
8844
|
+
function incomplete(input) {
|
|
8845
|
+
const at2 = input.lastIndexOf("\x1B");
|
|
8846
|
+
return at2 !== -1 && /^\[?[0-9;]*$/.test(input.slice(at2 + 1));
|
|
8847
|
+
}
|
|
8203
8848
|
function tick() {
|
|
8204
8849
|
return new Promise((resolve2) => setImmediate(resolve2));
|
|
8205
8850
|
}
|
|
8851
|
+
function readCache(site, name) {
|
|
8852
|
+
try {
|
|
8853
|
+
return JSON.parse(readFileSync5(join7(cacheDir(site), name), "utf8"));
|
|
8854
|
+
} catch {
|
|
8855
|
+
return {};
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
function writeCache(site, name, data) {
|
|
8859
|
+
mkdirSync4(cacheDir(site), { recursive: true });
|
|
8860
|
+
writeFileSync4(join7(cacheDir(site), name), `${JSON.stringify(data)}
|
|
8861
|
+
`);
|
|
8862
|
+
}
|
|
8206
8863
|
|
|
8207
8864
|
class Finder {
|
|
8208
8865
|
view;
|
|
@@ -8213,12 +8870,18 @@ class Finder {
|
|
|
8213
8870
|
rows = process.stdout.rows || 24;
|
|
8214
8871
|
gen = 0;
|
|
8215
8872
|
pumping = 0;
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
seen = new Set;
|
|
8873
|
+
boards = new Map;
|
|
8874
|
+
board;
|
|
8219
8875
|
posts = new Map;
|
|
8876
|
+
thumbPath = new Map;
|
|
8220
8877
|
siteIndex = 0;
|
|
8878
|
+
values = new Map(SETTINGS.map((setting) => [setting.name, initial(setting, process.env[setting.name])]));
|
|
8879
|
+
direction = 1;
|
|
8880
|
+
prefetching = 0;
|
|
8881
|
+
inflight = new Map;
|
|
8221
8882
|
owners = new Map;
|
|
8883
|
+
probed = new Map;
|
|
8884
|
+
unsaved = new Set;
|
|
8222
8885
|
thumbQueue = [];
|
|
8223
8886
|
thumbing = 0;
|
|
8224
8887
|
sent = new Map;
|
|
@@ -8226,6 +8889,7 @@ class Finder {
|
|
|
8226
8889
|
current;
|
|
8227
8890
|
fetch;
|
|
8228
8891
|
settle;
|
|
8892
|
+
partial;
|
|
8229
8893
|
input = "";
|
|
8230
8894
|
probeWait;
|
|
8231
8895
|
done;
|
|
@@ -8245,17 +8909,32 @@ class Finder {
|
|
|
8245
8909
|
site: this.site.name,
|
|
8246
8910
|
siteAnsi: this.site.ansi,
|
|
8247
8911
|
nextSite: this.nextSite.name,
|
|
8248
|
-
preset: "cutouts",
|
|
8912
|
+
preset: this.setting("TTHEME_FIND_POSTS") === "all" ? "all" : "cutouts",
|
|
8913
|
+
order: this.setting("TTHEME_FIND_ORDER") === "score" ? "score" : "newest",
|
|
8914
|
+
rating: ratingSet(this.setting("TTHEME_FIND_RATING")),
|
|
8915
|
+
block: blockSet(this.setting("TTHEME_FIND_BLOCK")),
|
|
8916
|
+
sets: this.setting("TTHEME_FIND_SETS") === "show" ? "show" : "fold",
|
|
8917
|
+
unblocked: process.env.TTHEME_FIND_PROXY !== undefined,
|
|
8918
|
+
settings: SETTINGS.map((setting) => ({
|
|
8919
|
+
label: setting.label,
|
|
8920
|
+
choices: [...setting.choices],
|
|
8921
|
+
value: this.setting(setting.name),
|
|
8922
|
+
multi: setting.multi && { none: setting.multi.none },
|
|
8923
|
+
cursor: 0
|
|
8924
|
+
})),
|
|
8249
8925
|
colors: { cursor: entry.cursor, selection: entry.selection, ansi: entry.ansi },
|
|
8250
8926
|
tiles: [],
|
|
8251
8927
|
checked: 0,
|
|
8252
8928
|
total: 0,
|
|
8253
|
-
searching:
|
|
8929
|
+
searching: tag !== "",
|
|
8254
8930
|
focus: 0,
|
|
8255
8931
|
top: 0,
|
|
8256
8932
|
mode: "grid",
|
|
8257
|
-
help: false
|
|
8933
|
+
help: false,
|
|
8934
|
+
editing: tag === "" ? "" : undefined
|
|
8258
8935
|
};
|
|
8936
|
+
this.board = blank(this.boardKey, tag !== "");
|
|
8937
|
+
this.boards.set(this.board.key, this.board);
|
|
8259
8938
|
}
|
|
8260
8939
|
get signal() {
|
|
8261
8940
|
return this.session.signal;
|
|
@@ -8266,6 +8945,52 @@ class Finder {
|
|
|
8266
8945
|
get nextSite() {
|
|
8267
8946
|
return SITES[(this.siteIndex + 1) % SITES.length];
|
|
8268
8947
|
}
|
|
8948
|
+
setting(name) {
|
|
8949
|
+
return this.values.get(name);
|
|
8950
|
+
}
|
|
8951
|
+
get boardKey() {
|
|
8952
|
+
return `${this.site.key}|${this.view.preset}|${this.view.order}`;
|
|
8953
|
+
}
|
|
8954
|
+
mark(site, id) {
|
|
8955
|
+
return `${site.key}:${id}`;
|
|
8956
|
+
}
|
|
8957
|
+
origPath(site, id, ext) {
|
|
8958
|
+
return join7(this.scratch, "orig", `${site.key}-${id}.${ext}`);
|
|
8959
|
+
}
|
|
8960
|
+
tileOf(site, post2, variants) {
|
|
8961
|
+
const version2 = rendition(post2) ?? post2;
|
|
8962
|
+
return {
|
|
8963
|
+
id: post2.id,
|
|
8964
|
+
width: version2.width,
|
|
8965
|
+
height: version2.height,
|
|
8966
|
+
reduced: version2 !== post2,
|
|
8967
|
+
owner: mirrored(post2.owner) ? "" : post2.owner,
|
|
8968
|
+
artist: post2.artist,
|
|
8969
|
+
score: post2.score,
|
|
8970
|
+
variants,
|
|
8971
|
+
origin: originHost(post2.source),
|
|
8972
|
+
mates: this.owners.get(site.key)?.get(post2.owner) ?? [],
|
|
8973
|
+
thumb: this.thumbPath.get(this.mark(site, post2.id))
|
|
8974
|
+
};
|
|
8975
|
+
}
|
|
8976
|
+
show() {
|
|
8977
|
+
const site = this.site;
|
|
8978
|
+
const view = this.view;
|
|
8979
|
+
const board = this.board;
|
|
8980
|
+
view.tiles = board.groups.flatMap((group) => group.open ? group.posts.map((post2) => this.tileOf(site, post2, 0)) : [this.tileOf(site, group.posts[0], group.posts.length)]);
|
|
8981
|
+
view.checked = board.checked;
|
|
8982
|
+
view.total = board.total;
|
|
8983
|
+
view.searching = board.searching;
|
|
8984
|
+
view.note = board.note;
|
|
8985
|
+
view.focus = Math.max(0, Math.min(board.focus, view.tiles.length - 1));
|
|
8986
|
+
view.top = board.top;
|
|
8987
|
+
}
|
|
8988
|
+
syncSite() {
|
|
8989
|
+
const view = this.view;
|
|
8990
|
+
view.site = this.site.name;
|
|
8991
|
+
view.siteAnsi = this.site.ansi;
|
|
8992
|
+
view.nextSite = this.nextSite.name;
|
|
8993
|
+
}
|
|
8269
8994
|
async run() {
|
|
8270
8995
|
const { stdin, stdout } = process;
|
|
8271
8996
|
stdin.setRawMode(true);
|
|
@@ -8276,10 +9001,13 @@ class Finder {
|
|
|
8276
9001
|
const exit = new Promise((resolve2) => {
|
|
8277
9002
|
this.done = resolve2;
|
|
8278
9003
|
});
|
|
9004
|
+
const clock = setInterval(this.onClock, 250);
|
|
8279
9005
|
const cell = await this.probe();
|
|
8280
9006
|
if (cell) {
|
|
8281
9007
|
this.cell = cell;
|
|
8282
|
-
this.
|
|
9008
|
+
if (this.view.tag) {
|
|
9009
|
+
this.search();
|
|
9010
|
+
}
|
|
8283
9011
|
} else {
|
|
8284
9012
|
this.view.searching = false;
|
|
8285
9013
|
this.view.error = "this terminal does not report its cell size — find needs kitty graphics";
|
|
@@ -8289,6 +9017,8 @@ class Finder {
|
|
|
8289
9017
|
this.session.abort();
|
|
8290
9018
|
this.fetch?.abort();
|
|
8291
9019
|
clearTimeout(this.settle);
|
|
9020
|
+
clearTimeout(this.partial);
|
|
9021
|
+
clearInterval(clock);
|
|
8292
9022
|
stdin.off("data", this.onData);
|
|
8293
9023
|
stdout.off("resize", this.onResize);
|
|
8294
9024
|
this.write("\x1B_Ga=d,d=A,q=2\x1B\\\x1B[H\x1B[J");
|
|
@@ -8328,12 +9058,28 @@ class Finder {
|
|
|
8328
9058
|
this.input = this.input.slice(0, at2) + this.input.slice(at2 + 1 + m[0].length);
|
|
8329
9059
|
this.probeWait({ h: Number(m[1]), w: Number(m[2]) });
|
|
8330
9060
|
}
|
|
9061
|
+
clearTimeout(this.partial);
|
|
9062
|
+
if (incomplete(this.input)) {
|
|
9063
|
+
this.partial = setTimeout(this.flushKeys, ESCAPE);
|
|
9064
|
+
return;
|
|
9065
|
+
}
|
|
9066
|
+
this.flushKeys();
|
|
9067
|
+
};
|
|
9068
|
+
flushKeys = () => {
|
|
8331
9069
|
const keys = decodeKeys(this.input);
|
|
8332
9070
|
this.input = "";
|
|
8333
9071
|
for (const key of keys) {
|
|
8334
9072
|
this.key(key);
|
|
8335
9073
|
}
|
|
8336
9074
|
};
|
|
9075
|
+
onClock = () => {
|
|
9076
|
+
const left = Math.ceil((pausedUntil(this.site) - Date.now()) / 1000);
|
|
9077
|
+
const waiting = left > 0 ? left : undefined;
|
|
9078
|
+
if (waiting !== this.view.waiting) {
|
|
9079
|
+
this.view.waiting = waiting;
|
|
9080
|
+
this.draw();
|
|
9081
|
+
}
|
|
9082
|
+
};
|
|
8337
9083
|
onResize = () => {
|
|
8338
9084
|
this.cols = process.stdout.columns || this.cols;
|
|
8339
9085
|
this.rows = process.stdout.rows || this.rows;
|
|
@@ -8353,6 +9099,14 @@ class Finder {
|
|
|
8353
9099
|
if (view.installing !== undefined) {
|
|
8354
9100
|
return;
|
|
8355
9101
|
}
|
|
9102
|
+
if (view.editing !== undefined) {
|
|
9103
|
+
this.editKey(key);
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
if (view.panel !== undefined) {
|
|
9107
|
+
this.panelKey(key);
|
|
9108
|
+
return;
|
|
9109
|
+
}
|
|
8356
9110
|
if (view.help) {
|
|
8357
9111
|
if (key === "?" || key === "esc") {
|
|
8358
9112
|
view.help = false;
|
|
@@ -8401,29 +9155,265 @@ class Finder {
|
|
|
8401
9155
|
this.select();
|
|
8402
9156
|
return;
|
|
8403
9157
|
}
|
|
8404
|
-
if (key === "
|
|
9158
|
+
if (key === "c") {
|
|
8405
9159
|
view.preset = view.preset === "cutouts" ? "all" : "cutouts";
|
|
8406
|
-
this.
|
|
9160
|
+
this.turn();
|
|
8407
9161
|
return;
|
|
8408
9162
|
}
|
|
8409
|
-
if (key === "
|
|
9163
|
+
if (key === "tab") {
|
|
8410
9164
|
this.siteIndex = (this.siteIndex + 1) % SITES.length;
|
|
8411
|
-
this.
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
9165
|
+
this.syncSite();
|
|
9166
|
+
this.turn();
|
|
9167
|
+
return;
|
|
9168
|
+
}
|
|
9169
|
+
if (key === "/") {
|
|
9170
|
+
view.editing = "";
|
|
9171
|
+
this.draw();
|
|
9172
|
+
return;
|
|
9173
|
+
}
|
|
9174
|
+
if (key === " ") {
|
|
9175
|
+
this.unfold();
|
|
9176
|
+
return;
|
|
9177
|
+
}
|
|
9178
|
+
if (key === "o") {
|
|
9179
|
+
this.openPage();
|
|
9180
|
+
return;
|
|
9181
|
+
}
|
|
9182
|
+
if (key === "s") {
|
|
9183
|
+
view.panel = 0;
|
|
9184
|
+
this.draw();
|
|
8417
9185
|
return;
|
|
8418
9186
|
}
|
|
8419
9187
|
if (key === "esc") {
|
|
8420
9188
|
this.finish(2);
|
|
8421
9189
|
}
|
|
8422
9190
|
}
|
|
9191
|
+
panelKey(key) {
|
|
9192
|
+
const view = this.view;
|
|
9193
|
+
const at2 = view.panel ?? 0;
|
|
9194
|
+
const row = view.settings[at2];
|
|
9195
|
+
if (key === "up" || key === "down") {
|
|
9196
|
+
view.panel = Math.max(0, Math.min(view.settings.length - 1, at2 + (key === "up" ? -1 : 1)));
|
|
9197
|
+
this.draw();
|
|
9198
|
+
return;
|
|
9199
|
+
}
|
|
9200
|
+
if ((key === "left" || key === "right") && row) {
|
|
9201
|
+
const step = key === "left" ? -1 : 1;
|
|
9202
|
+
if (row.multi) {
|
|
9203
|
+
row.cursor = (row.cursor + step + row.choices.length) % row.choices.length;
|
|
9204
|
+
} else {
|
|
9205
|
+
const index = row.choices.indexOf(row.value);
|
|
9206
|
+
row.value = row.choices[(index + step + row.choices.length) % row.choices.length];
|
|
9207
|
+
}
|
|
9208
|
+
this.draw();
|
|
9209
|
+
return;
|
|
9210
|
+
}
|
|
9211
|
+
if (key === " " && row?.multi) {
|
|
9212
|
+
const on = row.value.split(" ");
|
|
9213
|
+
const next = row.choices.filter((choice, k) => k === row.cursor ? !on.includes(choice) : on.includes(choice));
|
|
9214
|
+
const empty = row.multi.none;
|
|
9215
|
+
if (next.length > 0 || empty !== undefined) {
|
|
9216
|
+
row.value = next.length > 0 ? next.join(" ") : empty;
|
|
9217
|
+
this.draw();
|
|
9218
|
+
}
|
|
9219
|
+
return;
|
|
9220
|
+
}
|
|
9221
|
+
if (key === "esc") {
|
|
9222
|
+
view.settings.forEach((setting, i) => {
|
|
9223
|
+
setting.value = this.setting(SETTINGS[i]?.name ?? "");
|
|
9224
|
+
});
|
|
9225
|
+
view.panel = undefined;
|
|
9226
|
+
this.draw();
|
|
9227
|
+
return;
|
|
9228
|
+
}
|
|
9229
|
+
if (key === "enter" || key === "alt-c") {
|
|
9230
|
+
view.panel = undefined;
|
|
9231
|
+
this.adopt();
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
adopt() {
|
|
9235
|
+
const view = this.view;
|
|
9236
|
+
const before = SETTINGS.map((setting) => this.setting(setting.name));
|
|
9237
|
+
view.settings.forEach((row, i) => {
|
|
9238
|
+
const setting = SETTINGS[i];
|
|
9239
|
+
if (setting) {
|
|
9240
|
+
this.values.set(setting.name, row.value);
|
|
9241
|
+
}
|
|
9242
|
+
});
|
|
9243
|
+
const changed = SETTINGS.filter((setting, i) => this.setting(setting.name) !== before[i]);
|
|
9244
|
+
if (changed.length === 0) {
|
|
9245
|
+
this.draw();
|
|
9246
|
+
return;
|
|
9247
|
+
}
|
|
9248
|
+
this.save(changed);
|
|
9249
|
+
view.rating = ratingSet(this.setting("TTHEME_FIND_RATING"));
|
|
9250
|
+
view.block = blockSet(this.setting("TTHEME_FIND_BLOCK"));
|
|
9251
|
+
view.sets = this.setting("TTHEME_FIND_SETS") === "show" ? "show" : "fold";
|
|
9252
|
+
view.preset = this.setting("TTHEME_FIND_POSTS") === "all" ? "all" : "cutouts";
|
|
9253
|
+
view.order = this.setting("TTHEME_FIND_ORDER") === "score" ? "score" : "newest";
|
|
9254
|
+
this.boards.clear();
|
|
9255
|
+
this.current = undefined;
|
|
9256
|
+
view.shown = undefined;
|
|
9257
|
+
view.mode = "grid";
|
|
9258
|
+
this.search();
|
|
9259
|
+
}
|
|
9260
|
+
save(changed) {
|
|
9261
|
+
const path2 = join7(this.home, "ttheme", "config.zsh");
|
|
9262
|
+
try {
|
|
9263
|
+
const before = existsSync3(path2) ? readFileSync5(path2, "utf8") : "";
|
|
9264
|
+
const after = changed.reduce((text, setting) => withSetting(text, setting.name, this.setting(setting.name)), before);
|
|
9265
|
+
writeFileSync4(path2, after);
|
|
9266
|
+
} catch (error) {
|
|
9267
|
+
this.view.error = error instanceof Error ? error.message : String(error);
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
editKey(key) {
|
|
9271
|
+
const view = this.view;
|
|
9272
|
+
const text = view.editing ?? "";
|
|
9273
|
+
if (key === "esc") {
|
|
9274
|
+
view.editing = undefined;
|
|
9275
|
+
if (view.tag === "") {
|
|
9276
|
+
this.finish(2);
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
this.draw();
|
|
9280
|
+
return;
|
|
9281
|
+
}
|
|
9282
|
+
if (key === "enter") {
|
|
9283
|
+
view.editing = undefined;
|
|
9284
|
+
if (text.trim()) {
|
|
9285
|
+
this.commit(text.trim());
|
|
9286
|
+
return;
|
|
9287
|
+
}
|
|
9288
|
+
if (view.tag === "") {
|
|
9289
|
+
this.finish(2);
|
|
9290
|
+
return;
|
|
9291
|
+
}
|
|
9292
|
+
this.draw();
|
|
9293
|
+
return;
|
|
9294
|
+
}
|
|
9295
|
+
if (key === "backspace") {
|
|
9296
|
+
view.editing = text.slice(0, -1);
|
|
9297
|
+
this.draw();
|
|
9298
|
+
return;
|
|
9299
|
+
}
|
|
9300
|
+
if (key.length === 1 && key >= " ") {
|
|
9301
|
+
view.editing = text + key;
|
|
9302
|
+
this.draw();
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9305
|
+
async commit(text) {
|
|
9306
|
+
const ref = postRef(text, this.site);
|
|
9307
|
+
if (ref) {
|
|
9308
|
+
await this.jump(ref.site, ref.id);
|
|
9309
|
+
return;
|
|
9310
|
+
}
|
|
9311
|
+
this.view.tag = text;
|
|
9312
|
+
this.boards.clear();
|
|
9313
|
+
await this.search();
|
|
9314
|
+
}
|
|
9315
|
+
async jump(site, id) {
|
|
9316
|
+
const gen = ++this.gen;
|
|
9317
|
+
this.siteIndex = SITES.indexOf(site);
|
|
9318
|
+
this.syncSite();
|
|
9319
|
+
this.current = undefined;
|
|
9320
|
+
this.view.shown = undefined;
|
|
9321
|
+
this.view.error = undefined;
|
|
9322
|
+
const board = blank(`${site.key}|post:${id}`, true);
|
|
9323
|
+
this.board = board;
|
|
9324
|
+
this.boards.set(board.key, board);
|
|
9325
|
+
this.show();
|
|
9326
|
+
this.draw();
|
|
9327
|
+
try {
|
|
9328
|
+
const post2 = await fetchPost(site, id, this.signal);
|
|
9329
|
+
if (gen !== this.gen) {
|
|
9330
|
+
return;
|
|
9331
|
+
}
|
|
9332
|
+
board.searching = false;
|
|
9333
|
+
if (!post2) {
|
|
9334
|
+
board.error = `${site.name} has no post ${id}`;
|
|
9335
|
+
this.view.error = board.error;
|
|
9336
|
+
} else {
|
|
9337
|
+
this.posts.set(this.mark(site, post2.id), post2);
|
|
9338
|
+
board.groups = [{ posts: [post2], open: false }];
|
|
9339
|
+
board.checked = 1;
|
|
9340
|
+
board.total = 1;
|
|
9341
|
+
this.thumbQueue.push({ site, post: post2 });
|
|
9342
|
+
this.thumbs();
|
|
9343
|
+
this.view.mode = "try";
|
|
9344
|
+
}
|
|
9345
|
+
this.show();
|
|
9346
|
+
if (post2) {
|
|
9347
|
+
this.select();
|
|
9348
|
+
}
|
|
9349
|
+
this.draw();
|
|
9350
|
+
} catch (error) {
|
|
9351
|
+
this.fail(gen, error);
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
async turn() {
|
|
9355
|
+
this.current = undefined;
|
|
9356
|
+
this.view.shown = undefined;
|
|
9357
|
+
const known = this.boards.get(this.boardKey);
|
|
9358
|
+
if (!known) {
|
|
9359
|
+
await this.search();
|
|
9360
|
+
return;
|
|
9361
|
+
}
|
|
9362
|
+
this.gen++;
|
|
9363
|
+
this.board = known;
|
|
9364
|
+
this.view.error = known.error;
|
|
9365
|
+
this.show();
|
|
9366
|
+
this.draw();
|
|
9367
|
+
await this.pump();
|
|
9368
|
+
}
|
|
9369
|
+
unfold() {
|
|
9370
|
+
const view = this.view;
|
|
9371
|
+
let index = 0;
|
|
9372
|
+
for (const group of this.board.groups) {
|
|
9373
|
+
const size = group.open ? group.posts.length : 1;
|
|
9374
|
+
if (view.focus < index + size) {
|
|
9375
|
+
if (group.posts.length < 2) {
|
|
9376
|
+
return;
|
|
9377
|
+
}
|
|
9378
|
+
group.open = !group.open;
|
|
9379
|
+
this.board.focus = index;
|
|
9380
|
+
for (const post2 of group.posts) {
|
|
9381
|
+
if (!this.thumbPath.has(this.mark(this.site, post2.id))) {
|
|
9382
|
+
this.thumbQueue.push({ site: this.site, post: post2 });
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
this.thumbs();
|
|
9386
|
+
this.show();
|
|
9387
|
+
this.scroll();
|
|
9388
|
+
this.draw();
|
|
9389
|
+
return;
|
|
9390
|
+
}
|
|
9391
|
+
index += size;
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
openPage() {
|
|
9395
|
+
const tile = this.view.tiles[this.view.focus];
|
|
9396
|
+
if (!tile) {
|
|
9397
|
+
return;
|
|
9398
|
+
}
|
|
9399
|
+
const opener = process.platform === "darwin" ? "open" : "xdg-open";
|
|
9400
|
+
try {
|
|
9401
|
+
spawn2(opener, [this.site.pageUrl(tile.id)], { stdio: "ignore", detached: true }).unref();
|
|
9402
|
+
} catch {}
|
|
9403
|
+
}
|
|
8423
9404
|
tryKey(key) {
|
|
8424
9405
|
const view = this.view;
|
|
8425
9406
|
if (key === "left" || key === "right") {
|
|
8426
|
-
this.
|
|
9407
|
+
this.direction = key === "left" ? -1 : 1;
|
|
9408
|
+
this.focus(view.focus + this.direction);
|
|
9409
|
+
return;
|
|
9410
|
+
}
|
|
9411
|
+
if (key === "o") {
|
|
9412
|
+
this.openPage();
|
|
9413
|
+
return;
|
|
9414
|
+
}
|
|
9415
|
+
if (key === "x") {
|
|
9416
|
+
this.swap();
|
|
8427
9417
|
return;
|
|
8428
9418
|
}
|
|
8429
9419
|
if (key === "enter") {
|
|
@@ -8449,6 +9439,7 @@ class Finder {
|
|
|
8449
9439
|
return;
|
|
8450
9440
|
}
|
|
8451
9441
|
view.focus = next;
|
|
9442
|
+
this.board.focus = next;
|
|
8452
9443
|
this.scroll();
|
|
8453
9444
|
this.pump();
|
|
8454
9445
|
if (view.mode === "try") {
|
|
@@ -8466,28 +9457,50 @@ class Finder {
|
|
|
8466
9457
|
if (row > view.top + rowsVis - 1) {
|
|
8467
9458
|
view.top = row - rowsVis + 1;
|
|
8468
9459
|
}
|
|
9460
|
+
this.board.top = view.top;
|
|
9461
|
+
}
|
|
9462
|
+
query(site) {
|
|
9463
|
+
const view = this.view;
|
|
9464
|
+
const wanted = [
|
|
9465
|
+
view.tag,
|
|
9466
|
+
site.rate(this.view.rating),
|
|
9467
|
+
view.preset === "cutouts" ? site.cutouts : "",
|
|
9468
|
+
view.order === "score" ? site.best : ""
|
|
9469
|
+
].filter(Boolean);
|
|
9470
|
+
const kept = [];
|
|
9471
|
+
const dropped = [];
|
|
9472
|
+
for (const part of wanted) {
|
|
9473
|
+
if (tagsOf([...kept, part].join(" ")) <= site.tagBudget) {
|
|
9474
|
+
kept.push(part);
|
|
9475
|
+
} else {
|
|
9476
|
+
dropped.push(part);
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
this.board.note = dropped.length > 0 ? `${site.name} takes ${site.tagBudget} tags — ${dropped.join(" ")} left out` : undefined;
|
|
9480
|
+
return kept.join(" ");
|
|
8469
9481
|
}
|
|
8470
9482
|
async search() {
|
|
8471
9483
|
const gen = ++this.gen;
|
|
8472
|
-
const view = this.view;
|
|
8473
|
-
Object.assign(view, { tiles: [], checked: 0, total: 0, searching: true, focus: 0, top: 0, error: undefined });
|
|
8474
|
-
this.seen.clear();
|
|
8475
|
-
this.queue = [];
|
|
8476
|
-
this.sources = [];
|
|
8477
|
-
this.thumbQueue = [];
|
|
8478
|
-
this.draw();
|
|
8479
9484
|
const site = this.site;
|
|
8480
|
-
const
|
|
9485
|
+
const board = blank(this.boardKey, true);
|
|
9486
|
+
this.board = board;
|
|
9487
|
+
this.boards.set(board.key, board);
|
|
9488
|
+
this.view.error = undefined;
|
|
9489
|
+
this.show();
|
|
9490
|
+
this.draw();
|
|
9491
|
+
const tags = this.query(site);
|
|
8481
9492
|
try {
|
|
8482
9493
|
const [total, owners] = await Promise.all([fetchCount(site, tags, this.signal), this.mateOwners(site)]);
|
|
8483
9494
|
if (gen !== this.gen) {
|
|
8484
9495
|
return;
|
|
8485
9496
|
}
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
9497
|
+
board.total = total;
|
|
9498
|
+
const room = tagsOf(tags) + 1 <= site.tagBudget;
|
|
9499
|
+
board.sources = [
|
|
9500
|
+
...room ? [...owners.keys()].map((owner) => ({ tags: `${tags} user:${owner}`, page: 0, done: false })) : [],
|
|
8489
9501
|
{ tags, page: 0, done: false }
|
|
8490
9502
|
];
|
|
9503
|
+
this.show();
|
|
8491
9504
|
this.draw();
|
|
8492
9505
|
await this.pump();
|
|
8493
9506
|
} catch (error) {
|
|
@@ -8498,62 +9511,64 @@ class Finder {
|
|
|
8498
9511
|
if (gen !== this.gen || this.signal.aborted) {
|
|
8499
9512
|
return;
|
|
8500
9513
|
}
|
|
8501
|
-
this.
|
|
8502
|
-
this.
|
|
9514
|
+
this.board.error = reason(this.site, error);
|
|
9515
|
+
this.board.searching = false;
|
|
9516
|
+
this.view.error = this.board.error;
|
|
9517
|
+
this.show();
|
|
8503
9518
|
this.draw();
|
|
8504
9519
|
}
|
|
8505
9520
|
wants() {
|
|
8506
|
-
const view = this.view;
|
|
8507
|
-
if (view.checked < Math.min(view.total, PAGE)) {
|
|
8508
|
-
return true;
|
|
8509
|
-
}
|
|
8510
9521
|
const { perRow, rowsVis } = gridShape(this.cols, this.rows);
|
|
8511
|
-
return view.tiles.length < (
|
|
9522
|
+
return this.view.tiles.length < (this.board.top + rowsVis + LOOKAHEAD) * perRow;
|
|
8512
9523
|
}
|
|
8513
9524
|
async pump() {
|
|
8514
9525
|
const gen = this.gen;
|
|
8515
|
-
|
|
9526
|
+
const board = this.board;
|
|
9527
|
+
if (this.pumping === gen || board.sources.length === 0) {
|
|
8516
9528
|
return;
|
|
8517
9529
|
}
|
|
8518
9530
|
this.pumping = gen;
|
|
8519
|
-
const
|
|
9531
|
+
const site = this.site;
|
|
8520
9532
|
try {
|
|
8521
|
-
|
|
9533
|
+
board.searching = true;
|
|
8522
9534
|
while (gen === this.gen && this.wants()) {
|
|
8523
|
-
if (
|
|
8524
|
-
const source =
|
|
9535
|
+
if (board.queue.length === 0) {
|
|
9536
|
+
const source = board.sources.find((s) => !s.done);
|
|
8525
9537
|
if (!source) {
|
|
8526
9538
|
break;
|
|
8527
9539
|
}
|
|
8528
|
-
const posts = await fetchPosts(
|
|
9540
|
+
const posts = await fetchPosts(site, source.tags, source.page, this.signal);
|
|
8529
9541
|
if (gen !== this.gen) {
|
|
8530
9542
|
return;
|
|
8531
9543
|
}
|
|
8532
9544
|
source.page++;
|
|
8533
9545
|
source.done = posts.length < PAGE;
|
|
8534
9546
|
for (const post2 of posts) {
|
|
8535
|
-
if (!
|
|
8536
|
-
|
|
8537
|
-
|
|
9547
|
+
if (!board.seen.has(post2.id)) {
|
|
9548
|
+
board.seen.add(post2.id);
|
|
9549
|
+
board.queue.push(post2);
|
|
8538
9550
|
}
|
|
8539
9551
|
}
|
|
8540
9552
|
continue;
|
|
8541
9553
|
}
|
|
8542
|
-
const batch =
|
|
8543
|
-
const passed = await Promise.all(batch.map((post2) => this.passes(post2)));
|
|
9554
|
+
const batch = board.queue.splice(0, WORKERS);
|
|
9555
|
+
const passed = await Promise.all(batch.map((post2) => this.passes(site, post2)));
|
|
9556
|
+
this.saveProbes(site);
|
|
8544
9557
|
if (gen !== this.gen) {
|
|
8545
9558
|
return;
|
|
8546
9559
|
}
|
|
8547
9560
|
batch.forEach((post2, i) => {
|
|
8548
|
-
|
|
9561
|
+
board.checked++;
|
|
8549
9562
|
if (passed[i]) {
|
|
8550
|
-
this.admit(post2);
|
|
9563
|
+
this.admit(site, post2);
|
|
8551
9564
|
}
|
|
8552
9565
|
});
|
|
9566
|
+
this.show();
|
|
8553
9567
|
this.draw();
|
|
8554
9568
|
}
|
|
8555
9569
|
if (gen === this.gen) {
|
|
8556
|
-
|
|
9570
|
+
board.searching = false;
|
|
9571
|
+
this.show();
|
|
8557
9572
|
this.draw();
|
|
8558
9573
|
}
|
|
8559
9574
|
} catch (error) {
|
|
@@ -8564,37 +9579,62 @@ class Finder {
|
|
|
8564
9579
|
}
|
|
8565
9580
|
}
|
|
8566
9581
|
}
|
|
8567
|
-
async passes(post2) {
|
|
8568
|
-
|
|
9582
|
+
async passes(site, post2) {
|
|
9583
|
+
const version2 = rendition(post2);
|
|
9584
|
+
if (!rated(site, post2, this.view.rating) || exposed(post2, this.view.block).length > 0 || !version2) {
|
|
8569
9585
|
return false;
|
|
8570
9586
|
}
|
|
8571
9587
|
if (this.view.preset === "all") {
|
|
8572
|
-
return ["png", "jpg", "jpeg"].includes(
|
|
9588
|
+
return ["png", "jpg", "jpeg"].includes(version2.ext);
|
|
8573
9589
|
}
|
|
8574
9590
|
if (post2.ext !== "png") {
|
|
8575
9591
|
return false;
|
|
8576
9592
|
}
|
|
8577
|
-
|
|
8578
|
-
|
|
9593
|
+
return site.vouched || this.transparent(site, post2);
|
|
9594
|
+
}
|
|
9595
|
+
probes(site) {
|
|
9596
|
+
let known = this.probed.get(site.key);
|
|
9597
|
+
if (!known) {
|
|
9598
|
+
known = readCache(site, "probes.json");
|
|
9599
|
+
this.probed.set(site.key, known);
|
|
9600
|
+
}
|
|
9601
|
+
return known;
|
|
9602
|
+
}
|
|
9603
|
+
async transparent(site, post2) {
|
|
9604
|
+
const known = this.probes(site);
|
|
9605
|
+
const cached = known[post2.id];
|
|
9606
|
+
if (cached !== undefined) {
|
|
9607
|
+
return cached;
|
|
8579
9608
|
}
|
|
8580
9609
|
try {
|
|
8581
|
-
|
|
9610
|
+
const alpha = (await headOf(site, post2.file, this.signal))?.alpha === true;
|
|
9611
|
+
known[post2.id] = alpha;
|
|
9612
|
+
this.unsaved.add(site.key);
|
|
9613
|
+
return alpha;
|
|
8582
9614
|
} catch {
|
|
8583
9615
|
return false;
|
|
8584
9616
|
}
|
|
8585
9617
|
}
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
9618
|
+
saveProbes(site) {
|
|
9619
|
+
if (this.unsaved.delete(site.key)) {
|
|
9620
|
+
writeCache(site, "probes.json", this.probes(site));
|
|
9621
|
+
}
|
|
9622
|
+
}
|
|
9623
|
+
admit(site, post2) {
|
|
9624
|
+
this.posts.set(this.mark(site, post2.id), post2);
|
|
9625
|
+
const last = this.view.sets === "fold" ? this.board.groups.at(-1) : undefined;
|
|
9626
|
+
const head = last?.posts[0];
|
|
9627
|
+
const credit = post2.owner || post2.artist;
|
|
9628
|
+
if (last && head && credit && (head.owner || head.artist) === credit && head.width === post2.width && head.height === post2.height) {
|
|
9629
|
+
last.posts.push(post2);
|
|
9630
|
+
if (last.open) {
|
|
9631
|
+
this.thumbQueue.push({ site, post: post2 });
|
|
9632
|
+
this.thumbs();
|
|
9633
|
+
}
|
|
9634
|
+
return;
|
|
9635
|
+
}
|
|
9636
|
+
this.board.groups.push({ posts: [post2], open: false });
|
|
9637
|
+
this.thumbQueue.push({ site, post: post2 });
|
|
8598
9638
|
this.thumbs();
|
|
8599
9639
|
}
|
|
8600
9640
|
async mateOwners(site) {
|
|
@@ -8603,11 +9643,7 @@ class Finder {
|
|
|
8603
9643
|
return cached;
|
|
8604
9644
|
}
|
|
8605
9645
|
const found = origins(this.home);
|
|
8606
|
-
const
|
|
8607
|
-
let known = {};
|
|
8608
|
-
try {
|
|
8609
|
-
known = JSON.parse(readFileSync4(cachePath, "utf8"));
|
|
8610
|
-
} catch {}
|
|
9646
|
+
const known = readCache(site, "owners.json");
|
|
8611
9647
|
const byPalette = new Map;
|
|
8612
9648
|
for (const sibling of this.catalog.palettes) {
|
|
8613
9649
|
const origin = found.get(sibling.name);
|
|
@@ -8625,48 +9661,54 @@ class Finder {
|
|
|
8625
9661
|
}
|
|
8626
9662
|
byPalette.set(sibling.name, owner);
|
|
8627
9663
|
}
|
|
8628
|
-
|
|
8629
|
-
writeFileSync4(cachePath, `${JSON.stringify(known)}
|
|
8630
|
-
`);
|
|
9664
|
+
writeCache(site, "owners.json", known);
|
|
8631
9665
|
const owners = mates(byPalette);
|
|
8632
9666
|
this.owners.set(site.key, owners);
|
|
8633
9667
|
return owners;
|
|
8634
9668
|
}
|
|
8635
|
-
async cached(site, path2, url) {
|
|
9669
|
+
async cached(site, path2, url, progress2) {
|
|
8636
9670
|
if (existsSync3(path2)) {
|
|
8637
|
-
return new Uint8Array(
|
|
9671
|
+
return new Uint8Array(readFileSync5(path2));
|
|
9672
|
+
}
|
|
9673
|
+
const running = this.inflight.get(path2);
|
|
9674
|
+
if (running) {
|
|
9675
|
+
return running;
|
|
9676
|
+
}
|
|
9677
|
+
const job = (async () => {
|
|
9678
|
+
const bytes = await fetchBytes(site, url, this.signal, progress2);
|
|
9679
|
+
mkdirSync4(dirname3(path2), { recursive: true });
|
|
9680
|
+
writeFileSync4(path2, bytes);
|
|
9681
|
+
return bytes;
|
|
9682
|
+
})();
|
|
9683
|
+
this.inflight.set(path2, job);
|
|
9684
|
+
try {
|
|
9685
|
+
return await job;
|
|
9686
|
+
} finally {
|
|
9687
|
+
this.inflight.delete(path2);
|
|
8638
9688
|
}
|
|
8639
|
-
const bytes = await fetchBytes(site, url, this.signal);
|
|
8640
|
-
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8641
|
-
writeFileSync4(path2, bytes);
|
|
8642
|
-
return bytes;
|
|
8643
9689
|
}
|
|
8644
9690
|
thumbs() {
|
|
8645
9691
|
while (this.thumbing < WORKERS && this.thumbQueue.length > 0) {
|
|
8646
|
-
const
|
|
9692
|
+
const { site, post: post2 } = this.thumbQueue.shift();
|
|
8647
9693
|
this.thumbing++;
|
|
8648
|
-
this.thumb(
|
|
9694
|
+
this.thumb(site, post2).catch(() => {}).finally(() => {
|
|
8649
9695
|
this.thumbing--;
|
|
8650
9696
|
this.thumbs();
|
|
8651
9697
|
});
|
|
8652
9698
|
}
|
|
8653
9699
|
}
|
|
8654
|
-
async thumb(
|
|
8655
|
-
const post2 = this.posts.get(tile.id);
|
|
8656
|
-
const site = this.site;
|
|
8657
|
-
if (!post2) {
|
|
8658
|
-
return;
|
|
8659
|
-
}
|
|
9700
|
+
async thumb(site, post2) {
|
|
8660
9701
|
const w = TILE.cols * this.cell.w;
|
|
8661
9702
|
const h = TILE.rows * this.cell.h;
|
|
8662
|
-
const path2 = join7(
|
|
9703
|
+
const path2 = join7(this.scratch, "tile", `${site.key}-${post2.id}-${w}x${h}.png`);
|
|
8663
9704
|
if (!existsSync3(path2)) {
|
|
8664
|
-
const thumb = join7(cacheDir(site), "thumb", `${
|
|
9705
|
+
const thumb = join7(cacheDir(site), "thumb", `${post2.id}.${extension(post2.preview)}`);
|
|
8665
9706
|
const bytes = await this.cached(site, thumb, post2.preview);
|
|
8666
9707
|
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8667
|
-
writeFileSync4(path2, encodePng(contain(decodeImage(bytes), w, h)));
|
|
9708
|
+
writeFileSync4(path2, encodePng(contain(decodeImage(bytes, MAX_PIXELS), w, h)));
|
|
8668
9709
|
}
|
|
8669
|
-
|
|
9710
|
+
this.thumbPath.set(this.mark(site, post2.id), path2);
|
|
9711
|
+
this.show();
|
|
8670
9712
|
this.draw();
|
|
8671
9713
|
}
|
|
8672
9714
|
select() {
|
|
@@ -8688,31 +9730,26 @@ class Finder {
|
|
|
8688
9730
|
}
|
|
8689
9731
|
async load(tile) {
|
|
8690
9732
|
const view = this.view;
|
|
8691
|
-
const post2 = this.posts.get(tile.id);
|
|
8692
|
-
|
|
9733
|
+
const post2 = this.posts.get(this.mark(this.site, tile.id));
|
|
9734
|
+
const version2 = post2 && rendition(post2);
|
|
9735
|
+
if (!version2) {
|
|
8693
9736
|
return;
|
|
8694
9737
|
}
|
|
8695
9738
|
const site = this.site;
|
|
8696
9739
|
const control = new AbortController;
|
|
8697
9740
|
this.fetch = control;
|
|
8698
|
-
const signal = AbortSignal.any([this.signal, control.signal]);
|
|
8699
9741
|
try {
|
|
8700
|
-
const path2 =
|
|
8701
|
-
|
|
8702
|
-
if (existsSync3(path2)) {
|
|
8703
|
-
bytes = new Uint8Array(readFileSync4(path2));
|
|
8704
|
-
} else {
|
|
9742
|
+
const path2 = this.origPath(site, tile.id, version2.ext);
|
|
9743
|
+
if (!existsSync3(path2)) {
|
|
8705
9744
|
view.fetching = { id: tile.id, got: 0, size: 0 };
|
|
8706
9745
|
this.draw();
|
|
8707
|
-
bytes = await fetchBytes(site, post2.file, signal, (got, size) => {
|
|
8708
|
-
if (view.fetching?.id === tile.id) {
|
|
8709
|
-
view.fetching = { id: tile.id, got, size };
|
|
8710
|
-
this.draw();
|
|
8711
|
-
}
|
|
8712
|
-
});
|
|
8713
|
-
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8714
|
-
writeFileSync4(path2, bytes);
|
|
8715
9746
|
}
|
|
9747
|
+
const bytes = await this.cached(site, path2, version2.file, (got, size) => {
|
|
9748
|
+
if (view.fetching?.id === tile.id) {
|
|
9749
|
+
view.fetching = { id: tile.id, got, size };
|
|
9750
|
+
this.draw();
|
|
9751
|
+
}
|
|
9752
|
+
});
|
|
8716
9753
|
if (control.signal.aborted) {
|
|
8717
9754
|
return;
|
|
8718
9755
|
}
|
|
@@ -8720,14 +9757,41 @@ class Finder {
|
|
|
8720
9757
|
view.preparing = tile.id;
|
|
8721
9758
|
this.flush();
|
|
8722
9759
|
await tick();
|
|
8723
|
-
const image = decodeImage(bytes);
|
|
9760
|
+
const image = decodeImage(bytes, MAX_PIXELS);
|
|
8724
9761
|
if (site !== this.site) {
|
|
8725
9762
|
return;
|
|
8726
9763
|
}
|
|
8727
|
-
|
|
9764
|
+
const current = {
|
|
9765
|
+
site,
|
|
9766
|
+
id: tile.id,
|
|
9767
|
+
image,
|
|
9768
|
+
plain: image,
|
|
9769
|
+
failed: false,
|
|
9770
|
+
bytes,
|
|
9771
|
+
ext: version2.ext,
|
|
9772
|
+
clear: transparency(image)
|
|
9773
|
+
};
|
|
9774
|
+
if (current.clear === 0 && canRemoveBackground() && this.setting("TTHEME_FIND_REMOVE_BG") !== "off") {
|
|
9775
|
+
view.preparing = undefined;
|
|
9776
|
+
view.cutting = tile.id;
|
|
9777
|
+
this.flush();
|
|
9778
|
+
const cut = await this.cutOut(site, tile.id, path2, control.signal);
|
|
9779
|
+
if (control.signal.aborted || site !== this.site) {
|
|
9780
|
+
return;
|
|
9781
|
+
}
|
|
9782
|
+
if (cut) {
|
|
9783
|
+
current.cut = cut;
|
|
9784
|
+
current.image = cut;
|
|
9785
|
+
current.clear = transparency(cut);
|
|
9786
|
+
} else {
|
|
9787
|
+
current.failed = true;
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
this.current = current;
|
|
8728
9791
|
if (view.tiles[view.focus]?.id === tile.id) {
|
|
8729
9792
|
this.present();
|
|
8730
9793
|
}
|
|
9794
|
+
this.preload();
|
|
8731
9795
|
} catch (error) {
|
|
8732
9796
|
if (!control.signal.aborted && !this.signal.aborted) {
|
|
8733
9797
|
view.error = reason(site, error);
|
|
@@ -8739,9 +9803,59 @@ class Finder {
|
|
|
8739
9803
|
if (view.preparing === tile.id) {
|
|
8740
9804
|
view.preparing = undefined;
|
|
8741
9805
|
}
|
|
9806
|
+
if (view.cutting === tile.id) {
|
|
9807
|
+
view.cutting = undefined;
|
|
9808
|
+
}
|
|
8742
9809
|
this.draw();
|
|
8743
9810
|
}
|
|
8744
9811
|
}
|
|
9812
|
+
async cutOut(site, id, path2, signal) {
|
|
9813
|
+
const out = join7(this.scratch, "cut", `${site.key}-${id}.png`);
|
|
9814
|
+
try {
|
|
9815
|
+
mkdirSync4(dirname3(out), { recursive: true });
|
|
9816
|
+
const cut = existsSync3(out) ? decodeImage(new Uint8Array(readFileSync5(out)), MAX_PIXELS) : await removeBackground(path2, out, signal);
|
|
9817
|
+
return keepable(transparency(cut)) ? cut : undefined;
|
|
9818
|
+
} catch {
|
|
9819
|
+
rmSync4(out, { force: true });
|
|
9820
|
+
return;
|
|
9821
|
+
}
|
|
9822
|
+
}
|
|
9823
|
+
swap() {
|
|
9824
|
+
const current = this.current;
|
|
9825
|
+
if (!current?.cut || this.view.shown?.id !== current.id) {
|
|
9826
|
+
return;
|
|
9827
|
+
}
|
|
9828
|
+
current.image = current.image === current.cut ? current.plain : current.cut;
|
|
9829
|
+
current.clear = transparency(current.image);
|
|
9830
|
+
this.present();
|
|
9831
|
+
this.draw();
|
|
9832
|
+
}
|
|
9833
|
+
preload() {
|
|
9834
|
+
const view = this.view;
|
|
9835
|
+
if (view.mode !== "try") {
|
|
9836
|
+
return;
|
|
9837
|
+
}
|
|
9838
|
+
const site = this.site;
|
|
9839
|
+
for (const index of [view.focus + this.direction, view.focus - this.direction]) {
|
|
9840
|
+
if (this.prefetching >= PRELOAD) {
|
|
9841
|
+
return;
|
|
9842
|
+
}
|
|
9843
|
+
const tile = view.tiles[index];
|
|
9844
|
+
const post2 = tile && this.posts.get(this.mark(site, tile.id));
|
|
9845
|
+
const version2 = post2 && rendition(post2);
|
|
9846
|
+
if (!version2) {
|
|
9847
|
+
continue;
|
|
9848
|
+
}
|
|
9849
|
+
const path2 = this.origPath(site, post2.id, version2.ext);
|
|
9850
|
+
if (existsSync3(path2)) {
|
|
9851
|
+
continue;
|
|
9852
|
+
}
|
|
9853
|
+
this.prefetching++;
|
|
9854
|
+
this.cached(site, path2, version2.file).catch(() => {}).finally(() => {
|
|
9855
|
+
this.prefetching--;
|
|
9856
|
+
});
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
8745
9859
|
present() {
|
|
8746
9860
|
const current = this.current;
|
|
8747
9861
|
if (!current) {
|
|
@@ -8751,11 +9865,12 @@ class Finder {
|
|
|
8751
9865
|
const H = this.rows * this.cell.h;
|
|
8752
9866
|
const width2 = Math.min(W, TRY_WIDTH);
|
|
8753
9867
|
const height = Math.max(1, Math.round(H * width2 / W));
|
|
8754
|
-
const
|
|
9868
|
+
const cut = current.cut ? current.image === current.cut ? "on" : "off" : current.failed ? "failed" : "none";
|
|
9869
|
+
const path2 = join7(this.scratch, `${current.id}${cut === "on" ? "c" : ""}-${width2}x${height}.png`);
|
|
8755
9870
|
if (!existsSync3(path2)) {
|
|
8756
9871
|
writeFileSync4(path2, encodePng(tryOn(current.image, this.entry, this.tone, width2, height)));
|
|
8757
9872
|
}
|
|
8758
|
-
this.view.shown = { id: current.id, clear: current.clear, bytes: current.bytes.length, path: path2 };
|
|
9873
|
+
this.view.shown = { id: current.id, clear: current.clear, bytes: current.bytes.length, path: path2, cut };
|
|
8759
9874
|
}
|
|
8760
9875
|
async install() {
|
|
8761
9876
|
const view = this.view;
|
|
@@ -8825,17 +9940,45 @@ class Finder {
|
|
|
8825
9940
|
this.write(`${out}\x1B[?2026l`);
|
|
8826
9941
|
}
|
|
8827
9942
|
}
|
|
9943
|
+
function routable() {
|
|
9944
|
+
if (process.versions.bun) {
|
|
9945
|
+
return true;
|
|
9946
|
+
}
|
|
9947
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
9948
|
+
return major > 22 || major === 22 && minor >= 21;
|
|
9949
|
+
}
|
|
9950
|
+
async function relaunch() {
|
|
9951
|
+
if (!routable()) {
|
|
9952
|
+
process.stderr.write(`unblock needs node 22.21 or newer — this is ${process.versions.node}
|
|
9953
|
+
`);
|
|
9954
|
+
return 1;
|
|
9955
|
+
}
|
|
9956
|
+
const proxy = await tunnel();
|
|
9957
|
+
const child = spawn2(process.execPath, process.argv.slice(1), {
|
|
9958
|
+
stdio: "inherit",
|
|
9959
|
+
env: {
|
|
9960
|
+
...process.env,
|
|
9961
|
+
NODE_USE_ENV_PROXY: "1",
|
|
9962
|
+
NODE_NO_WARNINGS: "1",
|
|
9963
|
+
HTTPS_PROXY: `http://127.0.0.1:${proxy.port}`,
|
|
9964
|
+
TTHEME_FIND_PROXY: String(proxy.port)
|
|
9965
|
+
}
|
|
9966
|
+
});
|
|
9967
|
+
const code = await new Promise((resolve2) => child.on("exit", (status2) => resolve2(status2 ?? 1)));
|
|
9968
|
+
proxy.close();
|
|
9969
|
+
return code;
|
|
9970
|
+
}
|
|
8828
9971
|
async function runFind(name) {
|
|
9972
|
+
if (process.env.TTHEME_FIND_UNBLOCK === "1" && !process.env.TTHEME_FIND_PROXY) {
|
|
9973
|
+
return relaunch();
|
|
9974
|
+
}
|
|
8829
9975
|
const home = configHome();
|
|
8830
9976
|
const catalog = readCatalog(home);
|
|
8831
9977
|
const entry = find2(catalog.palettes, name);
|
|
8832
|
-
if (!entry.booru) {
|
|
8833
|
-
throw new Error(`${name} has no booru tag to search for`);
|
|
8834
|
-
}
|
|
8835
9978
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
8836
9979
|
throw new Error("ttheme find needs a terminal");
|
|
8837
9980
|
}
|
|
8838
|
-
return new Finder(home, catalog, entry, entry.booru).run();
|
|
9981
|
+
return new Finder(home, catalog, entry, entry.booru ?? "").run();
|
|
8839
9982
|
}
|
|
8840
9983
|
|
|
8841
9984
|
// src/init.ts
|
|
@@ -8845,7 +9988,7 @@ import {
|
|
|
8845
9988
|
existsSync as existsSync5,
|
|
8846
9989
|
mkdirSync as mkdirSync5,
|
|
8847
9990
|
readdirSync as readdirSync3,
|
|
8848
|
-
readFileSync as
|
|
9991
|
+
readFileSync as readFileSync6,
|
|
8849
9992
|
rmSync as rmSync5,
|
|
8850
9993
|
writeFileSync as writeFileSync5
|
|
8851
9994
|
} from "node:fs";
|
|
@@ -10794,7 +11937,7 @@ function copyDir(copies, from, to) {
|
|
|
10794
11937
|
}
|
|
10795
11938
|
}
|
|
10796
11939
|
function loadManifest(root2) {
|
|
10797
|
-
return JSON.parse(
|
|
11940
|
+
return JSON.parse(readFileSync6(join8(root2, "dist", "manifest.json"), "utf8"));
|
|
10798
11941
|
}
|
|
10799
11942
|
function planInit(opts, paths) {
|
|
10800
11943
|
const dist = join8(paths.root, "dist");
|
|
@@ -10810,7 +11953,7 @@ function planInit(opts, paths) {
|
|
|
10810
11953
|
const configPath = join8(home, "config.zsh");
|
|
10811
11954
|
const settings2 = {
|
|
10812
11955
|
file: configPath,
|
|
10813
|
-
content: configFile(existsSync5(configPath) ?
|
|
11956
|
+
content: configFile(existsSync5(configPath) ? readFileSync6(configPath, "utf8") : "")
|
|
10814
11957
|
};
|
|
10815
11958
|
const notes = [];
|
|
10816
11959
|
if (opts.terminals.includes("ghostty")) {
|
|
@@ -10818,7 +11961,7 @@ function planInit(opts, paths) {
|
|
|
10818
11961
|
}
|
|
10819
11962
|
if (opts.terminals.includes("alacritty")) {
|
|
10820
11963
|
const config = join8(paths.configHome, "alacritty", "alacritty.toml");
|
|
10821
|
-
if (existsSync5(config) && !
|
|
11964
|
+
if (existsSync5(config) && !readFileSync6(config, "utf8").includes("# ttheme begin")) {
|
|
10822
11965
|
notes.push("alacritty.toml already exists — ttheme left it alone; add its themes/ import yourself");
|
|
10823
11966
|
}
|
|
10824
11967
|
}
|
|
@@ -10843,7 +11986,7 @@ function applyInit(plan) {
|
|
|
10843
11986
|
sync(configHome2, plan.catalog, plan.installed);
|
|
10844
11987
|
for (const e of plan.edits) {
|
|
10845
11988
|
mkdirSync5(dirname4(e.file), { recursive: true });
|
|
10846
|
-
const current = existsSync5(e.file) ?
|
|
11989
|
+
const current = existsSync5(e.file) ? readFileSync6(e.file, "utf8") : "";
|
|
10847
11990
|
writeFileSync5(e.file, upsertBlock(current, e.block));
|
|
10848
11991
|
}
|
|
10849
11992
|
}
|
|
@@ -10866,7 +12009,7 @@ function verify(plan) {
|
|
|
10866
12009
|
const missing = [
|
|
10867
12010
|
...plan.copies.filter((c2) => !existsSync5(c2.to)).map((c2) => c2.to),
|
|
10868
12011
|
...existsSync5(plan.settings.file) ? [] : [plan.settings.file],
|
|
10869
|
-
...plan.edits.filter((e) => !
|
|
12012
|
+
...plan.edits.filter((e) => !readFileSync6(e.file, "utf8").includes("# ttheme begin")).map((e) => e.file)
|
|
10870
12013
|
];
|
|
10871
12014
|
if (missing.length > 0) {
|
|
10872
12015
|
throw new Error(`init left gaps:
|