Browse Source

Merge remote-tracking branch 'coomdev/中出し' into feat-black-posts

Anonymous 2 years ago
parent
commit
c9b871cd4f
  1. 15
      README.md
  2. 2
      main.meta.js
  3. 314
      main.user.js
  4. 4
      src/App.svelte
  5. 244
      src/Embedding.svelte
  6. 3
      src/gif.ts
  7. 15
      src/main.ts
  8. 3
      src/png.ts
  9. 33
      src/pngv3.ts
  10. 8
      src/pomf.ts
  11. 1
      src/stores.ts
  12. 14
      src/thirdeye.ts
  13. 53
      src/utils.ts
  14. 2
      src/webm.ts

15
README.md

@ -1,3 +1,14 @@
Fork of PNG Extra Embedder (PEE)
========================
This is a very minor fork of https://git.coom.tech/coomdev/PEE.git.
There are two branches:
- feat-black-posts: which maintains an extra feature to highlight blacklisted posts and a general 'Tags' button.
- main branch: merges feat-black-posts and includes an extra IntersectionObserver to delay initial processing of
posts until they are in the users viewport.
PNG Extra Embedder (PEE)
========================
@ -10,7 +21,7 @@ How to Install
Ok retard, listen up, just click on these things out of your grasp:
- [Install ViolentMonkey](https://violentmonkey.github.io/get-it/) (it is preferable to TamperMonkey(closed source) and GreaseMonkey(abandonned shit))
- [Install 4chanX](https://www.4chan-x.net/builds/4chan-X.user.js)
- Use the prebuilt [main.user.js](https://shoujo.coom.tech/main.user.js)
- Use the prebuilt [main.user.js](https://git.coom.tech/coomdev/PEE/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/main.user.js)
How to Build
============
@ -111,4 +122,4 @@ Bugs
====
- more to come
- not having a feature isn't a bug you retard
- not having a feature isn't a bug you retard

2
main.meta.js

@ -1,7 +1,7 @@
// ==UserScript==
// @name PNGExtraEmbed2
// @namespace https://coom.tech/
// @version 0.120
// @version 0.126
// @description uhh
// @author You
// @match https://boards.4channel.org/*

314
main.user.js

@ -1,7 +1,7 @@
// ==UserScript==
// @name PNGExtraEmbed2
// @namespace https://coom.tech/
// @version 0.120
// @version 0.126
// @description uhh
// @author You
// @match https://boards.4channel.org/*
@ -11005,6 +11005,7 @@
sh: false,
ep: false,
expte: false,
hotlink: false,
conc: 8,
ho: false,
blacklist: ["guro", "scat", "ryona", "gore"],
@ -11105,25 +11106,6 @@
async dtor() {
}
};
var PNGEncoder = class {
constructor(bytes) {
this.writer = bytes.getWriter();
this.writer.write(import_buffer.Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
}
async insertchunk(chunk) {
const b = import_buffer.Buffer.alloc(4);
b.writeInt32BE(chunk[1].length - 4, 0);
await this.writer.write(b);
const buff = chunk[1];
await this.writer.write(buff);
b.writeInt32BE((0, import_crc_32.buf)(buff), 0);
await this.writer.write(b);
}
async dtor() {
this.writer.releaseLock();
await this.writer.close();
}
};
var CUM0 = import_buffer.Buffer.from("CUM\x000");
var BufferReadStream = (b) => {
const ret = new ReadableStream({
@ -11173,43 +11155,6 @@
reader.releaseLock();
}
};
var buildChunk = (tag, data) => {
const ret = import_buffer.Buffer.alloc(data.byteLength + 4);
ret.write(tag.slice(0, 4), 0);
data.copy(ret, 4);
return ret;
};
var BufferWriteStream = () => {
let b = import_buffer.Buffer.from([]);
const ret = new WritableStream({
write(chunk) {
b = import_buffer.Buffer.concat([b, chunk]);
}
});
return [ret, () => b];
};
var inject = async (container, inj) => {
const [writestream, extract6] = BufferWriteStream();
const encoder = new PNGEncoder(writestream);
const decoder = new PNGDecoder(container.stream().getReader());
let magic2 = false;
for await (const [name, chunk, crc, offset] of decoder.chunks()) {
if (magic2 && name != "IDAT")
break;
if (!magic2 && name == "IDAT") {
await encoder.insertchunk(["tEXt", buildChunk("tEXt", CUM0), 0, 0]);
magic2 = true;
}
await encoder.insertchunk([name, chunk, crc, offset]);
}
const injb = import_buffer.Buffer.alloc(4 + inj.name.length + inj.size);
injb.writeInt32LE(inj.name.length, 0);
injb.write(inj.name, 4);
import_buffer.Buffer.from(await inj.arrayBuffer()).copy(injb, 4 + inj.name.length);
await encoder.insertchunk(["IDAT", buildChunk("IDAT", injb), 0, 0]);
await encoder.insertchunk(["IEND", buildChunk("IEND", import_buffer.Buffer.from([])), 0, 0]);
return extract6();
};
var has_embed = async (png) => {
const reader = BufferReadStream(png).getReader();
const sneed = new PNGDecoder(reader);
@ -11240,7 +11185,6 @@
var png_default = {
extract,
has_embed,
inject,
match: (fn) => !!fn.match(/\.png$/)
};
@ -11332,7 +11276,7 @@
if (chk.type == "b" && chk.name == "TagBinary")
return [{ embed_type: 1 /* MEDIA_EMBED */, filename: "string", data: chk.data }];
};
var inject2 = async (container, inj) => embed(import_buffer2.Buffer.from(await container.arrayBuffer()), import_buffer2.Buffer.from(await inj.arrayBuffer()));
var inject = async (container, [inj]) => embed(import_buffer2.Buffer.from(await container.arrayBuffer()), import_buffer2.Buffer.from(await inj.arrayBuffer()));
var has_embed2 = (webm) => {
const dec = new ebml.Decoder();
const chunks = dec.decode(webm);
@ -11347,7 +11291,7 @@
var webm_default = {
extract: extract2,
has_embed: has_embed2,
inject: inject2,
inject,
match: (fn) => !!fn.match(/\.webm$/)
};
@ -11399,53 +11343,6 @@
throw "Shouldn't happen";
};
var extract3 = extractBuff;
var write_data = async (writer, inj) => {
await writer.write(magic);
const byte = import_buffer3.Buffer.from([0]);
let size = inj.byteLength;
let ws;
let offset = 0;
while (size != 0) {
ws = size >= 255 ? 255 : size;
byte.writeUInt8(ws, 0);
await writer.write(byte);
await writer.write(inj.slice(offset, offset + ws));
size -= ws;
offset += ws;
}
byte.writeUInt8(0, 0);
await writer.write(byte);
};
var write_embedding = async (writer, inj) => {
const b = import_buffer3.Buffer.alloc(4);
b.writeInt32LE(inj.byteLength, 0);
await write_data(writer, b);
let size = inj.byteLength;
let offset = 0;
while (size != 0) {
const ws = size >= 3 << 13 ? 3 << 13 : size;
await write_data(writer, inj.slice(offset, offset + ws));
offset += ws;
size -= ws;
}
};
var inject3 = async (container, inj) => {
const [writestream, extract6] = BufferWriteStream();
const writer = writestream.getWriter();
const contbuff = import_buffer3.Buffer.from(await container.arrayBuffer());
debugger;
const field = contbuff.readUInt8(10);
const gcte = !!(field & 1 << 7);
let endo = 13;
if (gcte)
endo += 3 * (1 << (field & 7) + 1);
if (netscape.compare(contbuff, endo, endo + netscape.byteLength) == 0)
endo += 19;
await writer.write(contbuff.slice(0, endo));
await write_embedding(writer, import_buffer3.Buffer.from(await inj.arrayBuffer()));
await writer.write(contbuff.slice(endo));
return extract6();
};
var has_embed3 = (gif) => {
const field = gif.readUInt8(10);
const gcte = !!(field & 1 << 7);
@ -11473,7 +11370,6 @@
var gif_default = {
extract: extract3,
has_embed: has_embed3,
inject: inject3,
match: (fn) => !!fn.match(/\.gif$/)
};
@ -11584,6 +11480,10 @@
// src/thirdeye.ts
var import_buffer4 = __toESM(require_buffer(), 1);
var csettings2;
settings.subscribe((b) => {
csettings2 = b;
});
var gelquirk = (prefix) => (a) => (a.post || a).map((e) => ({
full_url: e.file_url,
preview_url: e.preview_url || e.preview_url,
@ -11698,7 +11598,7 @@
cachedPrev = await (await GM_fetch(prev || full)).arrayBuffer();
return cachedPrev;
},
data: async (lsn) => {
data: csettings2.hotlink ? full || prev : async (lsn) => {
if (!cachedFull)
cachedFull = await (await GM_fetch(full || prev, void 0, lsn)).arrayBuffer();
return cachedFull;
@ -11739,6 +11639,10 @@
{ host: "Litter", prefix: "https://litter.catbox.moe/" },
{ host: "Pomf", prefix: "https://a.pomf.cat/" }
];
var csettings3;
settings.subscribe((b) => {
csettings3 = b;
});
var getExt = (fn) => {
const isDum = fn.match(/^([a-z0-9]{6}\.(?:jpe?g|png|webm|gif))/gi);
const isB64 = fn.match(/^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))?\.(gif|jpe?g|png|webm)/);
@ -11767,7 +11671,7 @@
return [{
embed_type: 1 /* MEDIA_EMBED */,
filename: ext,
data: async (lsn) => {
data: csettings3.hotlink ? rsource : async (lsn) => {
try {
return (await GM_fetch(rsource, void 0, lsn)).arrayBuffer();
} catch (e) {
@ -12097,14 +12001,14 @@
}
function get_each_context(ctx, list, i) {
const child_ctx = ctx.slice();
child_ctx[33] = list[i];
child_ctx[35] = i;
child_ctx[34] = list[i];
child_ctx[36] = i;
return child_ctx;
}
function get_each_context_1(ctx, list, i) {
const child_ctx = ctx.slice();
child_ctx[36] = list[i];
child_ctx[35] = i;
child_ctx[37] = list[i];
child_ctx[36] = i;
return child_ctx;
}
function create_if_block_1(ctx) {
@ -12180,7 +12084,7 @@
$$scope: { ctx }
};
dialog = new Dialog_default({ props: dialog_props });
ctx[29](dialog);
ctx[30](dialog);
let each_value = ctx[3].blacklist;
let each_blocks = [];
for (let i = 0; i < each_value.length; i += 1) {
@ -12259,9 +12163,9 @@
current = true;
if (!mounted) {
dispose = [
listen(input0, "change", ctx[20]),
listen(button, "click", ctx[24]),
listen(input1, "keydown", ctx[31])
listen(input0, "change", ctx[21]),
listen(button, "click", ctx[25]),
listen(input1, "keydown", ctx[32])
];
mounted = true;
}
@ -12292,7 +12196,7 @@
check_outros();
}
const dialog_changes = {};
if (dirty[0] & 1 | dirty[1] & 128) {
if (dirty[0] & 1 | dirty[1] & 256) {
dialog_changes.$$scope = { dirty, ctx: ctx2 };
}
dialog.$set(dialog_changes);
@ -12360,7 +12264,7 @@
detach(button);
if (detaching)
detach(t7);
ctx[29](null);
ctx[30](null);
destroy_component(dialog, detaching);
if (detaching)
detach(t8);
@ -12388,17 +12292,17 @@
let tag;
let current;
function func(...args) {
return ctx[21](ctx[36], ...args);
return ctx[22](ctx[37], ...args);
}
function remove_handler() {
return ctx[22](ctx[36]);
return ctx[23](ctx[37]);
}
function toggle_handler() {
return ctx[23](ctx[36]);
return ctx[24](ctx[37]);
}
tag = new Tag_default({
props: {
tag: ctx[36].name,
tag: ctx[37].name,
toggleable: true,
toggled: !ctx[3].rsources.find(func)?.disabled
}
@ -12417,7 +12321,7 @@
ctx = new_ctx;
const tag_changes = {};
if (dirty[0] & 8)
tag_changes.tag = ctx[36].name;
tag_changes.tag = ctx[37].name;
if (dirty[0] & 8)
tag_changes.toggled = !ctx[3].rsources.find(func)?.disabled;
tag.$set(tag_changes);
@ -12518,10 +12422,10 @@
append(div, button);
if (!mounted) {
dispose = [
listen(input0, "input", ctx[25]),
listen(input1, "input", ctx[26]),
listen(input2, "input", ctx[27]),
listen(input3, "input", ctx[28]),
listen(input0, "input", ctx[26]),
listen(input1, "input", ctx[27]),
listen(input2, "input", ctx[28]),
listen(input3, "input", ctx[29]),
listen(button, "click", ctx[4])
];
mounted = true;
@ -12553,9 +12457,9 @@
let tag;
let current;
function toggle_handler_1() {
return ctx[30](ctx[33]);
return ctx[31](ctx[34]);
}
tag = new Tag_default({ props: { tag: ctx[33] } });
tag = new Tag_default({ props: { tag: ctx[34] } });
tag.$on("toggle", toggle_handler_1);
return {
c() {
@ -12569,7 +12473,7 @@
ctx = new_ctx;
const tag_changes = {};
if (dirty[0] & 8)
tag_changes.tag = ctx[33];
tag_changes.tag = ctx[34];
tag.$set(tag_changes);
},
i(local) {
@ -12634,12 +12538,16 @@
let label9;
let input9;
let t22;
let a;
let t24;
let t23;
let label10;
let input10;
let t25;
let t24;
let a;
let t26;
let label11;
let input11;
let t27;
let t28;
let current;
let mounted;
let dispose;
@ -12687,22 +12595,26 @@
t17 = space();
label7 = element("label");
input7 = element("input");
t18 = text("\r\n Control audio on videos with mouse wheel.");
t18 = text("\r\n Hotlink content.");
t19 = space();
label8 = element("label");
input8 = element("input");
t20 = text("\r\n Show Minimap");
t20 = text("\r\n Control audio on videos with mouse wheel.");
t21 = space();
label9 = element("label");
input9 = element("input");
t22 = text("\r\n \r\n Disable embedded file preloading");
a = element("a");
a.textContent = "?";
t24 = space();
t22 = text("\r\n Show Minimap");
t23 = space();
label10 = element("label");
input10 = element("input");
t25 = text("\r\n Disable third-eye.");
t24 = text("\r\n \r\n Disable embedded file preloading");
a = element("a");
a.textContent = "?";
t26 = space();
label11 = element("label");
input11 = element("input");
t27 = text("\r\n Disable third-eye.");
t28 = space();
if (if_block1)
if_block1.c();
attr(h1, "class", "svelte-14cwalg");
@ -12717,8 +12629,9 @@
attr(input7, "type", "checkbox");
attr(input8, "type", "checkbox");
attr(input9, "type", "checkbox");
attr(a, "title", "You might still want to enable 'preload external files'");
attr(input10, "type", "checkbox");
attr(a, "title", "You might still want to enable 'preload external files'");
attr(input11, "type", "checkbox");
attr(div0, "class", "content svelte-14cwalg");
attr(div1, "class", "backpanel svelte-14cwalg");
toggle_class(div1, "enabled", ctx[2]);
@ -12771,25 +12684,30 @@
append(div0, t17);
append(div0, label7);
append(label7, input7);
input7.checked = ctx[3].ca;
input7.checked = ctx[3].hotlink;
append(label7, t18);
append(div0, t19);
append(div0, label8);
append(label8, input8);
input8.checked = ctx[3].sh;
input8.checked = ctx[3].ca;
append(label8, t20);
append(div0, t21);
append(div0, label9);
append(label9, input9);
input9.checked = ctx[3].ep;
input9.checked = ctx[3].sh;
append(label9, t22);
append(label9, a);
append(div0, t24);
append(div0, t23);
append(div0, label10);
append(label10, input10);
input10.checked = ctx[3].te;
append(label10, t25);
input10.checked = ctx[3].ep;
append(label10, t24);
append(label10, a);
append(div0, t26);
append(div0, label11);
append(label11, input11);
input11.checked = ctx[3].te;
append(label11, t27);
append(div0, t28);
if (if_block1)
if_block1.m(div0, null);
current = true;
@ -12805,7 +12723,8 @@
listen(input7, "change", ctx[16]),
listen(input8, "change", ctx[17]),
listen(input9, "change", ctx[18]),
listen(input10, "change", ctx[19])
listen(input10, "change", ctx[19]),
listen(input11, "change", ctx[20])
];
mounted = true;
}
@ -12845,16 +12764,19 @@
input6.checked = ctx2[3].prev;
}
if (dirty[0] & 8) {
input7.checked = ctx2[3].ca;
input7.checked = ctx2[3].hotlink;
}
if (dirty[0] & 8) {
input8.checked = ctx2[3].sh;
input8.checked = ctx2[3].ca;
}
if (dirty[0] & 8) {
input9.checked = ctx2[3].ep;
input9.checked = ctx2[3].sh;
}
if (dirty[0] & 8) {
input10.checked = ctx2[3].te;
input10.checked = ctx2[3].ep;
}
if (dirty[0] & 8) {
input11.checked = ctx2[3].te;
}
if (!ctx2[3].te) {
if (if_block1) {
@ -12975,18 +12897,22 @@
settings.set($settings);
}
function input7_change_handler() {
$settings.ca = this.checked;
$settings.hotlink = this.checked;
settings.set($settings);
}
function input8_change_handler() {
$settings.sh = this.checked;
$settings.ca = this.checked;
settings.set($settings);
}
function input9_change_handler() {
$settings.ep = this.checked;
$settings.sh = this.checked;
settings.set($settings);
}
function input10_change_handler() {
$settings.ep = this.checked;
settings.set($settings);
}
function input11_change_handler() {
$settings.te = this.checked;
settings.set($settings);
}
@ -13051,6 +12977,7 @@
input8_change_handler,
input9_change_handler,
input10_change_handler,
input11_change_handler,
input0_change_handler_1,
func,
remove_handler,
@ -15408,6 +15335,7 @@
return {
c() {
img = element("img");
attr(img, "referrerpolicy", "no-referrer");
attr(img, "alt", img_alt_value = ctx[0].filename);
if (!src_url_equal(img.src, img_src_value = ctx[14] || ctx[5]))
attr(img, "src", img_src_value);
@ -15446,6 +15374,7 @@
if (!src_url_equal(source.src, source_src_value = ctx[14] || ctx[5]))
attr(source, "src", source_src_value);
attr(source, "type", ctx[8]);
attr(audio, "referrerpolicy", "no-referrer");
audio.controls = true;
if (!src_url_equal(audio.src, audio_src_value = ctx[14] || ctx[5]))
attr(audio, "src", audio_src_value);
@ -15486,6 +15415,7 @@
return {
c() {
video = element("video");
attr(video, "referrerpolicy", "no-referrer");
video.loop = video_loop_value = ctx[18].loop;
if (!src_url_equal(video.src, video_src_value = ctx[14] || ctx[5]))
attr(video, "src", video_src_value);
@ -15556,6 +15486,7 @@
return {
c() {
img = element("img");
attr(img, "referrerpolicy", "no-referrer");
attr(img, "alt", img_alt_value = ctx[0].filename);
if (!src_url_equal(img.src, img_src_value = ctx[14] || ctx[5]))
attr(img, "src", img_src_value);
@ -15585,6 +15516,7 @@
return {
c() {
video = element("video");
attr(video, "referrerpolicy", "no-referrer");
video.loop = video_loop_value = ctx[18].loop;
if (!src_url_equal(video.src, video_src_value = ctx[14] || ctx[5]))
attr(video, "src", video_src_value);
@ -15690,9 +15622,20 @@
if (settled)
return;
settled = true;
const thumb = file.thumbnail ? await file.thumbnail() : file.data;
const type = await fileTypeFromBuffer(thumb);
$$invalidate(5, url = URL.createObjectURL(new Blob([thumb], { type: type?.mime })));
if (file.thumbnail == null)
return;
const thumb = typeof file.thumbnail === "function" ? await file.thumbnail() : file.data;
let type;
if (thumb instanceof Buffer2) {
type = await fileTypeFromBuffer(thumb);
$$invalidate(5, url = URL.createObjectURL(new Blob([thumb], { type: type?.mime })));
} else if (typeof thumb === "string") {
let head = headerStringToObject(await GM_head(thumb, void 0));
type = {
ext: "",
mime: head["content-type"].split(";")[0].trim()
};
}
if (!type)
return;
$$invalidate(8, ftype = type.mime);
@ -15736,25 +15679,38 @@
return;
if (unzipping)
return;
$$invalidate(16, unzipping = true);
let lisn = new EventTarget();
lisn.addEventListener("progress", (e) => {
$$invalidate(17, progress = e.detail);
});
let full = await file.data(lisn);
const type = await fileTypeFromBuffer(full);
$$invalidate(14, furl = URL.createObjectURL(new Blob([full], { type: type?.mime })));
$$invalidate(16, unzipping = false);
let type;
if (typeof file.data === "function") {
$$invalidate(16, unzipping = true);
let lisn = new EventTarget();
lisn.addEventListener("progress", (e) => {
$$invalidate(17, progress = e.detail);
});
let full = await file.data(lisn);
const type2 = await fileTypeFromBuffer(full);
$$invalidate(14, furl = URL.createObjectURL(new Blob([full], { type: type2?.mime })));
} else if (typeof file.data === "string") {
$$invalidate(5, url = file.data);
$$invalidate(14, furl = file.data);
let head = headerStringToObject(await GM_head(file.data, void 0));
type = {
ext: "",
mime: head["content-type"].split(";")[0].trim()
};
}
if (!type)
return;
$$invalidate(2, isVideo = type.mime.startsWith("video/"));
$$invalidate(4, isAudio = type.mime.startsWith("audio/"));
$$invalidate(3, isImage = type.mime.startsWith("image/"));
$$invalidate(16, unzipping = false);
dispatch("fileinfo", { type });
if (hovering) {
setTimeout(() => {
recompute();
hoverUpdate();
setTimeout(async () => {
while (dims[0] == 0 && dims[1] == 0) {
hoverUpdate();
await new Promise((_) => setTimeout(_, 20));
}
}, 20);
}
}
@ -15851,6 +15807,7 @@
return;
if (!contracted)
return;
recompute();
const [sw, sh] = [visualViewport.width, visualViewport.height];
if (dims[0] == 0 && dims[1] == 0)
recompute();
@ -16774,13 +16731,13 @@
var EyeButton_default = EyeButton;
// src/main.ts
var csettings2;
var csettings4;
var processors = [thirdeye_default, pomf_default, png_default, webm_default, gif_default];
var cappState;
settings.subscribe((b) => {
csettings2 = b;
csettings4 = b;
processors = [
...!csettings2.te ? [thirdeye_default] : [],
...!csettings4.te ? [thirdeye_default] : [],
png_default,
pomf_default,
webm_default,
@ -16977,20 +16934,21 @@
const input = document.createElement("input");
input.setAttribute("type", "file");
const type = file.type;
input.multiple = true;
input.onchange = async (ev) => {
if (input.files) {
try {
const proc = processors.filter((e3) => e3.inject).find((e3) => e3.match(file.name));
if (!proc)
throw new Error("Container filetype not supported");
const buff = await proc.inject(file, input.files[0]);
const buff = await proc.inject(file, [...input.files]);
document.dispatchEvent(new CustomEvent("QRSetFile", {
detail: { file: new Blob([buff], { type }), name: file.name }
}));
document.dispatchEvent(new CustomEvent("CreateNotification", {
detail: {
type: "success",
content: "File successfully embedded!",
content: `File${input.files.length > 1 ? "s" : ""} successfully embedded!`,
lifetime: 3
}
}));

4
src/App.svelte

@ -85,6 +85,10 @@
<input type="checkbox" bind:checked={$settings.prev} />
Preload external files when they are in view.
</label>
<label>
<input type="checkbox" bind:checked={$settings.hotlink} />
Hotlink content.
</label>
<label>
<input type="checkbox" bind:checked={$settings.ca} />
Control audio on videos with mouse wheel.

244
src/Embedding.svelte

@ -1,11 +1,12 @@
<script lang="ts">
import { fileTypeFromBuffer } from 'file-type'
import { fileTypeFromBuffer, FileTypeResult } from 'file-type'
import { settings, appState } from './stores'
import { beforeUpdate, tick } from 'svelte'
import { EmbeddedFile, EMBED_TYPES } from './main.js';
import { createEventDispatcher } from 'svelte';
import { GM_head, headerStringToObject } from './requests.js';
export const dispatch = createEventDispatcher();
export const dispatch = createEventDispatcher()
export let file: EmbeddedFile
// export let embed_enum: EMBED_ENUM = EMBED_ENUM.NONE;
@ -14,9 +15,9 @@
let isAudio = false
let url = ''
let settled = false
let contracted = true;
let contracted = true
let hovering = false
let ftype = '';
let ftype = ''
let place: HTMLDivElement
let hoverElem: HTMLDivElement
@ -24,19 +25,18 @@
let videoElem: HTMLVideoElement
let hoverVideo: HTMLVideoElement
let dims: [number, number] = [0, 0]
let furl: string | undefined = undefined;
let furl: string | undefined = undefined
let visible = false;
export const isNotChrome = !navigator.userAgent.includes("Chrome/");
let visible = false
export const isNotChrome = !navigator.userAgent.includes('Chrome/')
export let id = '';
document.addEventListener("reveal", (e: any) => {
if (e.detail.id == id)
visible = !visible;
});
export let id = ''
document.addEventListener('reveal', (e: any) => {
if (e.detail.id == id) visible = !visible
})
export function isContracted() {
return contracted;
return contracted
}
beforeUpdate(async () => {
@ -44,85 +44,109 @@
if (settled) return
settled = true
const thumb = file.thumbnail ? (await file.thumbnail()) : file.data;
const type = await fileTypeFromBuffer(thumb);
url = URL.createObjectURL(new Blob([thumb], { type: type?.mime }))
if (!type)
return;
if(file.thumbnail == null) return
const thumb = typeof file.thumbnail === 'function' ? (await file.thumbnail()) : file.data;
// const thumb = file.thumbnail || file.data
let type: FileTypeResult | undefined
if (thumb instanceof Buffer) {
type = await fileTypeFromBuffer(thumb);
url = URL.createObjectURL(new Blob([thumb], { type: type?.mime }))
// if (!type) return; // extracted out below for TypeScript
} else if (typeof thumb === 'string'){
let head = headerStringToObject(await GM_head(thumb, undefined))
// TODO write a proper getExt() and getMime()
type = { ext: '' as any, mime: head['content-type'].split(';')[0].trim() }
}
if (!type) return
ftype = type.mime;
isVideo = type.mime.startsWith('video/')
isAudio = type.mime.startsWith('audio/')
isImage = type.mime.startsWith('image/')
dispatch("fileinfo", {type})
dispatch('fileinfo', { type })
if (isImage) {
contracted = !$settings.xpi;
contracted = !$settings.xpi
}
if (isVideo) {
contracted = !$settings.xpv && !$appState.isCatalog
}
if ($appState.isCatalog)
contracted = true;
if ($appState.isCatalog) contracted = true
if ($settings.pre) {
unzip(); // not awaiting on purpose
unzip() // not awaiting on purpose
}
if ($settings.prev) {
let obs = new IntersectionObserver((entries, obs) => {
for(const item of entries) {
if(!item.isIntersecting) continue;
unzip();
obs.unobserve(place);
}
}, {root:null, rootMargin: '1000px', threshold: 0.01});
obs.observe(place);
let obs = new IntersectionObserver(
(entries, obs) => {
for (const item of entries) {
if (!item.isIntersecting) continue
unzip()
obs.unobserve(place)
}
},
{ root: null, rootMargin: '1000px', threshold: 0.01 },
)
obs.observe(place)
}
});
})
let unzipping = false;
let unzipping = false
let progress = [0, 0]
async function unzip() {
if(file?.isBlacklisted === true) return
if (!file.thumbnail)
return;
if (unzipping)
return;
unzipping = true;
let lisn = new EventTarget();
lisn.addEventListener("progress", (e: any) => {
progress = e.detail
});
let full = await file.data(lisn);
const type = await fileTypeFromBuffer(full);
furl = URL.createObjectURL(new Blob([full], { type: type?.mime }));
unzipping = false;
if (!type)
return;
if (!file.thumbnail) return
if (unzipping) return
let type: FileTypeResult | undefined
if (typeof file.data === 'function') {
unzipping = true;
let lisn = new EventTarget();
lisn.addEventListener("progress", (e: any) => {
progress = e.detail
});
let full = await file.data(lisn);
const type = await fileTypeFromBuffer(full);
furl = URL.createObjectURL(new Blob([full], { type: type?.mime }));
} else if (typeof file.data === 'string') {
url = file.data
furl = file.data
let head = headerStringToObject(await GM_head(file.data, undefined))
// TODO write a proper getExt() and getMime()
type = { ext: '' as any, mime: head['content-type'].split(';')[0].trim() } as FileTypeResult
}
// unzipping = false;
if (!type) return
isVideo = type.mime.startsWith('video/')
isAudio = type.mime.startsWith('audio/')
isImage = type.mime.startsWith('image/')
dispatch("fileinfo", {type})
unzipping = false
dispatch('fileinfo', { type })
if (hovering) {
// reset hovering to recompute proper image coordinates
setTimeout(() => {
recompute();
hoverUpdate();
}, 20);
setTimeout(async () => {
while (dims[0] == 0 && dims[1] == 0) {
hoverUpdate()
await new Promise(_ => setTimeout(_, 20));
}
}, 20)
}
}
function hasAudio(video: any) {
return (
video.mozHasAudio ||
!!(video.webkitAudioDecodedByteCount) ||
!!video.webkitAudioDecodedByteCount ||
!!(video.audioTracks && video.audioTracks.length)
)
}
export async function bepis(ev: MouseEvent) {
if (file?.isBlacklisted === true) return
if ($appState.isCatalog) return;
if ($appState.isCatalog) return
if (ev.button == 0) {
contracted = !contracted
@ -135,7 +159,7 @@
videoElem.controls = true
// has to be delayed
setTimeout(async () => {
videoElem.currentTime = hoverVideo.currentTime || 0;
videoElem.currentTime = hoverVideo.currentTime || 0
await videoElem.play()
}, 10)
}
@ -143,27 +167,33 @@
// don't know how you managed to click before hovering but oh well
unzip()
}
ev.preventDefault();
} else if (ev.button == 1) { // middle click
let src = furl || url;
ev.preventDefault()
} else if (ev.button == 1) {
// middle click
let src = furl || url
if (ev.altKey && file.source) {
src = file.source;
src = file.source
}
if (ev.shiftKey && file.page) {
src = file.page.url;
src = file.page.url
}
ev.preventDefault();
ev.preventDefault()
if (isNotChrome) {
window.open(src, '_blank');
} else
await GM.openInTab(src, {active: false, insert: true});
window.open(src, '_blank')
} else await GM.openInTab(src, { active: false, insert: true })
}
}
const getViewport = () => (typeof visualViewport != "undefined" ? () => [visualViewport.width, visualViewport.height] : () => [document.documentElement.clientWidth, document.documentElement.clientHeight])();
const getViewport = () =>
(typeof visualViewport != 'undefined'
? () => [visualViewport.width, visualViewport.height]
: () => [
document.documentElement.clientWidth,
document.documentElement.clientHeight,
])()
function recompute() {
const [sw, sh] = getViewport();
const [sw, sh] = getViewport()
let [iw, ih] = [0, 0]
if (isImage) {
@ -179,48 +209,49 @@
}
async function hoverStart(ev?: MouseEvent) {
if (file?.isBlacklisted === true) return
if ($settings.dh)return;
if (file?.isBlacklisted === true) return;
if ($settings.dh) return
if (file.thumbnail && !furl) {
unzip();
unzip()
}
if (!isImage && !isVideo) return
if (!contracted) return
recompute();
recompute()
hovering = true
if (isVideo){
if (isVideo) {
try {
await hoverVideo.play()
} catch (e) {
// probably didn't interact with document error, mute the video and try again?
hoverVideo.muted = true;
hoverVideo.volume = 0;
await hoverVideo.play()
}
await hoverVideo.play()
} catch (e) {
// probably didn't interact with document error, mute the video and try again?
hoverVideo.muted = true
hoverVideo.volume = 0
await hoverVideo.play()
}
}
}
function hoverStop(ev?: MouseEvent) {
if ($settings.dh) return;
if ($settings.dh) return
hovering = false
if (isVideo) hoverVideo.pause()
}
let lastev: MouseEvent | undefined;
let lastev: MouseEvent | undefined
function hoverUpdate(ev?: MouseEvent) {
lastev = lastev || ev;
if ($settings.dh) return;
lastev = lastev || ev
if ($settings.dh) return
if (!contracted) return
recompute(); // yeah I gave up
const [sw, sh] = [visualViewport.width, visualViewport.height]
// shamelessly stolen from 4chanX
if (dims[0] == 0 && dims[1] == 0)
recompute();
if (dims[0] == 0 && dims[1] == 0) recompute()
let width = dims[0]
let height = dims[1] + 25
let { clientX, clientY } = (ev || lastev!)
let { clientX, clientY } = ev || lastev!
let top = Math.max(0, (clientY * (sh - height)) / sh)
let threshold = sw / 2
let marginX: number | string =
@ -238,13 +269,12 @@
if (!$settings.ca) return
if (!isVideo) return
if ($settings.dh && contracted) return
if (!hasAudio(videoElem))
return;
let vol = videoElem.volume * (ev.deltaY > 0 ? 0.9 : 1.1);
vol = Math.max(0, Math.min(1, vol));
videoElem.volume = vol;
hoverVideo.volume = videoElem.volume;
hoverVideo.muted = vol < 0;
if (!hasAudio(videoElem)) return
let vol = videoElem.volume * (ev.deltaY > 0 ? 0.9 : 1.1)
vol = Math.max(0, Math.min(1, vol))
videoElem.volume = vol
hoverVideo.volume = videoElem.volume
hoverVideo.muted = vol < 0
ev.preventDefault()
}
</script>
@ -257,8 +287,8 @@
class="place"
class:hasembed={file?.embed_type === EMBED_TYPES.MEDIA_EMBED}
class:hasext={file?.embed_type === EMBED_TYPES.THIRD_EYE}
on:click={e => e.preventDefault()}
on:auxclick={e => e.preventDefault()}
on:click={(e) => e.preventDefault()}
on:auxclick={(e) => e.preventDefault()}
on:mousedown={bepis}
on:mouseover={hoverStart}
on:mouseout={hoverStop}
@ -268,10 +298,16 @@
>
{#if isImage}
<!-- svelte-ignore a11y-missing-attribute -->
<img bind:this={imgElem} alt={file.filename} src={furl || url} />
<img
referrerpolicy="no-referrer"
bind:this={imgElem}
alt={file.filename}
src={furl || url}
/>
{/if}
{#if isAudio}
<audio
referrerpolicy="no-referrer"
controls
src={furl || url}
loop={$settings.loop}
@ -283,7 +319,12 @@
{#if isVideo}
<!-- svelte-ignore a11y-media-has-caption -->
<!-- svelte-ignore a11y-missing-attribute -->
<video loop={$settings.loop} bind:this={videoElem} src={furl || url} />
<video
referrerpolicy="no-referrer"
loop={$settings.loop}
bind:this={videoElem}
src={furl || url}
/>
<!-- assoom videos will never be loaded from thumbnails -->
{/if}
</div>
@ -298,11 +339,16 @@
>{/if}
{#if isImage}
<img alt={file.filename} src={furl || url} />
<img referrerpolicy="no-referrer" alt={file.filename} src={furl || url} />
{/if}
{#if isVideo}
<!-- svelte-ignore a11y-media-has-caption -->
<video loop={$settings.loop} bind:this={hoverVideo} src={furl || url} />
<video
referrerpolicy="no-referrer"
loop={$settings.loop}
bind:this={hoverVideo}
src={furl || url}
/>
<!-- assoom videos will never be loaded from thumbnails -->
{/if}
</div>

3
src/gif.ts

@ -85,7 +85,7 @@ const write_embedding = async (writer: WritableStreamDefaultWriter<Buffer>, inj:
}
};
const inject = async (container: File, inj: File) => {
const inject = async (container: File, [inj]: File[]) => {
const [writestream, extract] = BufferWriteStream();
const writer = writestream.getWriter();
@ -136,6 +136,5 @@ const has_embed = (gif: Buffer) : EMBED_STATUS => {
export default {
extract,
has_embed,
inject,
match: fn => !!fn.match(/\.gif$/)
} as ImageProcessor;

15
src/main.ts

@ -21,8 +21,8 @@ export interface ImageProcessor {
skip?: true;
match(fn: string): boolean;
has_embed(b: Buffer, fn?: string): EMBED_STATUS | Promise<EMBED_STATUS>;
extract(b: Buffer, fn?: string): EmbeddedFile[] | Promise<EmbeddedFile[] | undefined>;
inject?(b: File, c: File): Buffer | Promise<Buffer>;
extract(b: Buffer, fn?: string): EmbeddedFile[] | Promise<EmbeddedFile[]>;
inject?(b: File, c: File[]): Buffer | Promise<Buffer>;
}
export let csettings: Parameters<typeof settings['set']>[0];
@ -85,8 +85,8 @@ type EmbeddedFileWithPreview = {
page?: { title: string, url: string }; // can be a booru page
source?: string; // can be like a twitter post this was posted in originally
filename: string;
thumbnail: () => Promise<Buffer>;
data: (lisn?: EventTarget) => Promise<Buffer>;
thumbnail: undefined | Buffer | (() => Promise<Buffer>);
data: string | ((lisn?: EventTarget) => Promise<Buffer>);
};
@ -98,7 +98,7 @@ type EmbeddedFileWithoutPreview = {
source: undefined;
thumbnail: undefined;
filename: string;
data: Buffer;
data: string | Buffer;
};
export type EmbeddedFile = EmbeddedFileWithPreview | EmbeddedFileWithoutPreview;
@ -299,13 +299,14 @@ document.addEventListener('QRDialogCreation', <any>((e: CustomEvent<HTMLElement>
const input = document.createElement('input') as HTMLInputElement;
input.setAttribute("type", "file");
const type = file.type;
input.multiple = true;
input.onchange = (async ev => {
if (input.files) {
try {
const proc = processors.filter(e => e.inject).find(e => e.match(file.name));
if (!proc)
throw new Error("Container filetype not supported");
const buff = await proc.inject!(file, input.files[0]);
const buff = await proc.inject!(file, [...input.files]);
document.dispatchEvent(new CustomEvent('QRSetFile', {
//detail: { file: new Blob([buff]), name: file.name, type: file.type }
detail: { file: new Blob([buff], { type }), name: file.name }
@ -313,7 +314,7 @@ document.addEventListener('QRDialogCreation', <any>((e: CustomEvent<HTMLElement>
document.dispatchEvent(new CustomEvent("CreateNotification", {
detail: {
type: 'success',
content: 'File successfully embedded!',
content: `File${input.files.length > 1 ? 's' : ''} successfully embedded!`,
lifetime: 3
}
}));

3
src/png.ts

@ -144,7 +144,7 @@ export const BufferWriteStream = () => {
return [ret, () => b] as [WritableStream<Buffer>, () => Buffer];
};
const inject = async (container: File, inj: File) => {
const inject = async (container: File, [inj]: File[]) => {
const [writestream, extract] = BufferWriteStream();
const encoder = new PNGEncoder(writestream);
const decoder = new PNGDecoder(container.stream().getReader());
@ -202,6 +202,5 @@ const has_embed = async (png: Buffer) : Promise<EMBED_STATUS> => {
export default {
extract,
has_embed,
inject,
match: fn => !!fn.match(/\.png$/)
} as ImageProcessor;

33
src/pngv3.ts

@ -4,7 +4,6 @@ import { EMBED_TYPES, ImageProcessor, EmbeddedFile, EMBED_STATUS } from './main'
import { PNGDecoder, PNGEncoder } from "./png";
import { decodeCoom3Payload } from "./utils";
const CUM0 = Buffer.from("CUM\0" + "0");
const CUM3 = Buffer.from("CUM\0" + "3");
const BufferReadStream = (b: Buffer) => {
@ -19,7 +18,6 @@ const BufferReadStream = (b: Buffer) => {
const extract = async (png: Buffer): Promise<EmbeddedFile[] | undefined> => {
let magic = false;
let coom3 = false;
const reader = BufferReadStream(png).getReader();
const sneed = new PNGDecoder(reader);
try {
@ -30,10 +28,7 @@ const extract = async (png: Buffer): Promise<EmbeddedFile[] | undefined> => {
// should exist at the beginning of file to signal decoders if the file indeed has an embedded chunk
case 'tEXt':
buff = chunk;
if (buff.slice(4, 4 + CUM0.length).equals(CUM0))
magic = true;
if (buff.slice(4, 4 + CUM0.length).equals(CUM3)) {
coom3 = true;
if (buff.slice(4, 4 + CUM3.length).equals(CUM3)) {
magic = true;
}
break;
@ -52,16 +47,8 @@ const extract = async (png: Buffer): Promise<EmbeddedFile[] | undefined> => {
}
}
if (lastIDAT) {
let data = (lastIDAT as Buffer).slice(4);
if (coom3) {
let file = decodeCoom3Payload(data)
return;
}
const fnsize = data.readUInt32LE(0);
const fn = data.slice(4, 4 + fnsize).toString();
// Todo: xor the buffer to prevent scanning for file signatures (4chan embedded file detection)?
data = data.slice(4 + fnsize);
return [{ embed_type: EMBED_TYPES.MEDIA_EMBED, filename: fn, data } as EmbeddedFile];
const data = (lastIDAT as Buffer).slice(4);
return await decodeCoom3Payload(data);
}
} catch (e) {
console.error(e);
@ -87,7 +74,7 @@ export const BufferWriteStream = () => {
return [ret, () => b] as [WritableStream<Buffer>, () => Buffer];
};
const inject = async (container: File, inj: File) => {
const inject = async (container: File, injs: File[]) => {
const [writestream, extract] = BufferWriteStream();
const encoder = new PNGEncoder(writestream);
const decoder = new PNGDecoder(container.stream().getReader());
@ -97,15 +84,13 @@ const inject = async (container: File, inj: File) => {
if (magic && name != "IDAT")
break;
if (!magic && name == "IDAT") {
await encoder.insertchunk(["tEXt", buildChunk("tEXt", CUM0), 0, 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt", CUM3), 0, 0]);
magic = true;
}
await encoder.insertchunk([name, chunk, crc, offset]);
}
const injb = Buffer.alloc(4 + inj.name.length + inj.size);
injb.writeInt32LE(inj.name.length, 0);
injb.write(inj.name, 4);
Buffer.from(await inj.arrayBuffer()).copy(injb, 4 + inj.name.length);
const injb = Buffer.alloc(4);
// TODO
await encoder.insertchunk(["IDAT", buildChunk("IDAT", injb), 0, 0]);
await encoder.insertchunk(["IEND", buildChunk("IEND", Buffer.from([])), 0, 0]);
return extract();
@ -121,9 +106,7 @@ const has_embed = async (png: Buffer): Promise<EMBED_STATUS> => {
// should exist at the beginning of file to signal decoders if the file indeed has an embedded chunk
case 'tEXt':
buff = chunk;
if (buff.slice(4, 4 + CUM0.length).equals(CUM0))
return EMBED_STATUS.SUCCESS;
if (buff.slice(4, 4 + CUM0.length).equals(CUM3))
if (buff.slice(4, 4 + CUM3.length).equals(CUM3))
return EMBED_STATUS.SUCCESS;
break;
case 'IDAT':

8
src/pomf.ts

@ -2,6 +2,7 @@ import { EmbeddedFile, EMBED_STATUS, EMBED_TYPES, ImageProcessor } from "./main"
import { GM_fetch, GM_head } from "./requests";
import type { Buffer } from "buffer";
import thumbnail from "./assets/hasembed.png";
import { settings } from "./stores";
const sources = [
{ host: 'Catbox', prefix: 'https://files.catbox.moe/' },
@ -9,6 +10,11 @@ const sources = [
{ host: 'Pomf', prefix: 'https://a.pomf.cat/' },
];
export let csettings: Parameters<typeof settings['set']>[0];
settings.subscribe(b => {
csettings = b;
});
const getExt = (fn: string) => {
const isDum = fn!.match(/^([a-z0-9]{6}\.(?:jpe?g|png|webm|gif))/gi);
const isB64 = fn!.match(/^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))?\.(gif|jpe?g|png|webm)/);
@ -41,7 +47,7 @@ const extract = async (b: Buffer, fn?: string) : Promise<EmbeddedFile[]> => {
return [{
embed_type: EMBED_TYPES.MEDIA_EMBED,
filename: ext,
data: async (lsn) => {
data: csettings.hotlink ? rsource! : async (lsn) => {
try {
return (await GM_fetch(rsource, undefined, lsn)).arrayBuffer();
} catch (e) {

1
src/stores.ts

@ -23,6 +23,7 @@ export const settings = writable(localLoad('settingsv2', {
sh: false,
ep: false,
expte: false,
hotlink: false,
conc: 8,
ho: false,
blacklist: ['guro', 'scat', 'ryona', 'gore'],

14
src/thirdeye.ts

@ -3,6 +3,11 @@ import { GM_fetch } from "./requests";
import { localLoad, settings } from "./stores";
import { Buffer } from "buffer";
export let csettings: Parameters<typeof settings['set']>[0];
settings.subscribe(b => {
csettings = b;
});
export type Booru = {
disabled?: boolean;
name: string;
@ -163,10 +168,17 @@ const extract = async (b: Buffer, fn?: string) : Promise<EmbeddedFile[]> => {
cachedPrev = (await (await GM_fetch(prev || full)).arrayBuffer()); // prefer preview
return cachedPrev;
},
data: async (lsn) => {
data: csettings.hotlink ? (full || prev) : async (lsn) => {
if (!cachedFull)
cachedFull = (await (await GM_fetch(full || prev, undefined, lsn)).arrayBuffer()); // prefer full
return cachedFull;
// thumbnail: (await (await GM_fetch(prev || full)).arrayBuffer()), // prefer preview
// data: csettings.hotlink ? (full || prev) : async (lsn) => {
// if (!cachedFile)
// cachedFile = (await (await GM_fetch(full || prev, undefined, lsn)).arrayBuffer()); // prefer full
// return cachedFile;
}
} as EmbeddedFile];
};

53
src/utils.ts

@ -1,5 +1,52 @@
import type { Buffer } from "buffer";
import { Buffer } from "buffer";
import { GM_fetch, headerStringToObject } from "./requests";
import thumbnail from "./assets/hasembed.png";
import { EmbeddedFile, EMBED_TYPES } from './main';
export const decodeCoom3Payload = (buff: Buffer) => {
//
/*
header (must be < 2k): [1 byte bitfield](if hasfilename: null terminated string)(if has tags: [X null terminated string, tags are whitespace-separated])
(if has thumbnail: [thumbnail size X]
rest: [X bytes of thumbnail data])[file bytes]
&1 => has filename
&2 => has tags
&4 => has thumbnail
*/
export const decodeCoom3Payload = async (buff: Buffer) : Promise<EmbeddedFile[]> => {
const pees = buff.toString().split('\0');
return Promise.all(pees.map(async pee => {
const res = await GM_fetch(pee, { headers: { ranges: 'bytes=0-2048' } });
const size = +(res.headers.get('content-size') || 0);
const header = Buffer.from(await res.arrayBuffer());
const flags = header[0];
const hasFn = flags & 1;
const hasTags = flags & 2;
const hasThumbnail = flags & 4;
let [ptr, ptr2] = [1, 1];
let fn = 'embedded';
let tags = [];
let thumb: EmbeddedFile['thumbnail'] = Buffer.from(thumbnail);
if (hasFn) {
while (buff[ptr2] != 0)
ptr2++;
fn = header.slice(ptr, ptr2).toString();
ptr = ++ptr2;
}
if (hasTags) {
while (buff[ptr2] != 0)
ptr2++;
tags = header.slice(ptr, ptr2).toString().split(/\s+/);
}
let thumbsize = 0;
if (hasThumbnail) {
thumbsize = header.readInt32LE(ptr);
thumb = Buffer.from(await (await GM_fetch(pee, { headers: { range: `bytes: ${ptr + 4}-${ptr + 4 + thumbsize}` } })).arrayBuffer());
}
return {
embed_type: EMBED_TYPES.MEDIA_EMBED,
filename: fn,
data: async (lsn) =>
Buffer.from(await (await GM_fetch(pee, { headers: { range: `bytes: ${ptr + 4 + thumbsize}-${size-1}` } }, lsn)).arrayBuffer()),
thumbnail: thumb,
} as EmbeddedFile;
}));
};

2
src/webm.ts

@ -123,7 +123,7 @@ const extract = (webm: Buffer) : EmbeddedFile[] | undefined => {
return [{ embed_type: EMBED_TYPES.MEDIA_EMBED, filename: 'string', data: chk.data } as EmbeddedFile];
};
const inject = async (container: File, inj: File): Promise<Buffer> =>
const inject = async (container: File, [inj]: File[]): Promise<Buffer> =>
embed(Buffer.from(await container.arrayBuffer()), Buffer.from(await inj.arrayBuffer()));
const has_embed = (webm: Buffer) : EMBED_STATUS => {

Loading…
Cancel
Save