diff --git a/main.meta.js b/main.meta.js index 692a995..0e9adc5 100644 --- a/main.meta.js +++ b/main.meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name PNGExtraEmbed // @namespace https://coom.tech/ -// @version 0.117 +// @version 0.120 // @description uhh // @author You // @match https://boards.4channel.org/* diff --git a/main.user.js b/main.user.js index 2c60f45..c575d80 100644 --- a/main.user.js +++ b/main.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name PNGExtraEmbed // @namespace https://coom.tech/ -// @version 0.117 +// @version 0.120 // @description uhh // @author You // @match https://boards.4channel.org/* @@ -11021,6 +11021,7 @@ sh: false, ep: false, expte: false, + hotlink: false, conc: 8, ho: false, blacklist: ["guro", "scat", "ryona", "gore"], @@ -11121,25 +11122,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({ @@ -11189,43 +11171,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); @@ -11255,7 +11200,6 @@ var png_default = { extract, has_embed, - inject, match: (fn) => !!fn.match(/\.png$/) }; @@ -11347,7 +11291,7 @@ if (chk.type == "b" && chk.name == "TagBinary") return [{ 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); @@ -11362,7 +11306,7 @@ var webm_default = { extract: extract2, has_embed: has_embed2, - inject: inject2, + inject, match: (fn) => !!fn.match(/\.webm$/) }; @@ -11414,53 +11358,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); @@ -11488,7 +11385,6 @@ var gif_default = { extract: extract3, has_embed: has_embed3, - inject: inject3, match: (fn) => !!fn.match(/\.gif$/) }; @@ -11599,6 +11495,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, @@ -11704,7 +11604,7 @@ page: { title: booru, url: result[0].page }, filename: fn.substring(0, 33) + result[0].ext, thumbnail: await (await GM_fetch(prev || full)).arrayBuffer(), - data: async (lsn) => { + data: csettings2.hotlink ? full || prev : async (lsn) => { if (!cachedFile) cachedFile = await (await GM_fetch(full || prev, void 0, lsn)).arrayBuffer(); return cachedFile; @@ -11744,6 +11644,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)/); @@ -11771,7 +11675,7 @@ } return [{ filename: ext, - data: async (lsn) => { + data: csettings3.hotlink ? rsource : async (lsn) => { try { return (await GM_fetch(rsource, void 0, lsn)).arrayBuffer(); } catch (e) { @@ -12102,14 +12006,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) { @@ -12185,7 +12089,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) { @@ -12264,9 +12168,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; } @@ -12297,7 +12201,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); @@ -12365,7 +12269,7 @@ detach(button); if (detaching) detach(t7); - ctx[29](null); + ctx[30](null); destroy_component(dialog, detaching); if (detaching) detach(t8); @@ -12393,17 +12297,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 } @@ -12422,7 +12326,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); @@ -12523,10 +12427,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; @@ -12558,9 +12462,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() { @@ -12574,7 +12478,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) { @@ -12639,12 +12543,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; @@ -12692,22 +12600,26 @@ t17 = space(); label7 = element("label"); input7 = element("input"); - t18 = text("\n Control audio on videos with mouse wheel."); + t18 = text("\n Hotlink content."); t19 = space(); label8 = element("label"); input8 = element("input"); - t20 = text("\n Show Minimap"); + t20 = text("\n Control audio on videos with mouse wheel."); t21 = space(); label9 = element("label"); input9 = element("input"); - t22 = text("\n \n Disable embedded file preloading"); - a = element("a"); - a.textContent = "?"; - t24 = space(); + t22 = text("\n Show Minimap"); + t23 = space(); label10 = element("label"); input10 = element("input"); - t25 = text("\n Disable third-eye."); + t24 = text("\n \n Disable embedded file preloading"); + a = element("a"); + a.textContent = "?"; t26 = space(); + label11 = element("label"); + input11 = element("input"); + t27 = text("\n Disable third-eye."); + t28 = space(); if (if_block1) if_block1.c(); attr(h1, "class", "svelte-14cwalg"); @@ -12722,8 +12634,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]); @@ -12776,25 +12689,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; @@ -12810,7 +12728,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; } @@ -12850,16 +12769,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].ca; } if (dirty[0] & 8) { - input8.checked = ctx2[3].sh; + input9.checked = ctx2[3].sh; } if (dirty[0] & 8) { - input9.checked = ctx2[3].ep; + input10.checked = ctx2[3].ep; } if (dirty[0] & 8) { - input10.checked = ctx2[3].te; + input11.checked = ctx2[3].te; } if (!ctx2[3].te) { if (if_block1) { @@ -12980,18 +12902,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); } @@ -13056,6 +12982,7 @@ input8_change_handler, input9_change_handler, input10_change_handler, + input11_change_handler, input0_change_handler_1, func, remove_handler, @@ -15207,6 +15134,32 @@ // src/Embedding.svelte init_esbuild_inject(); + + // dist/requests.js + init_esbuild_inject(); + var xmlhttprequest2 = typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : typeof GM != "undefined" ? GM.xmlHttpRequest : window["GM_xmlhttpRequest"]; + var headerStringToObject2 = (s) => Object.fromEntries(s.split("\n").map((e) => { + const [name, ...rest] = e.split(":"); + return [name.toLowerCase(), rest.join(":").trim()]; + })); + function GM_head2(...[url, opt]) { + return new Promise((resolve, reject) => { + const gmopt = { + url: url.toString(), + data: opt?.body?.toString(), + method: "HEAD", + onload: (resp) => { + resolve(resp.responseHeaders); + }, + ontimeout: () => reject("fetch timeout"), + onerror: () => reject("fetch error"), + onabort: () => reject("fetch abort") + }; + xmlhttprequest2(gmopt); + }); + } + + // src/Embedding.svelte function add_css6(target) { append_styles(target, "svelte-yvh28x", ".place.svelte-yvh28x.svelte-yvh28x{cursor:pointer;max-width:100vw;max-height:100vh}.unzipping.svelte-yvh28x>img.svelte-yvh28x{filter:brightness(0.5) blur(10px)}.progress.svelte-yvh28x.svelte-yvh28x{color:black;-webkit-text-stroke:0.7px white;font-weight:bold;left:50%;top:50%;font-size:larger;display:inline-block;position:absolute;z-index:10}.hoverer.svelte-yvh28x.svelte-yvh28x{display:none;position:fixed;pointer-events:none}.visible.svelte-yvh28x.svelte-yvh28x{display:block;z-index:9}.contract.svelte-yvh28x img.svelte-yvh28x,.contract.svelte-yvh28x video.svelte-yvh28x{max-width:125px !important;max-height:125px !important;width:auto;height:auto}.place.svelte-yvh28x:not(.contract) video.svelte-yvh28x,.place.svelte-yvh28x:not(.contract) img.svelte-yvh28x,.hoverer.svelte-yvh28x>video.svelte-yvh28x,.hoverer.svelte-yvh28x>img.svelte-yvh28x{max-width:100vw;max-height:100vh}"); } @@ -15404,6 +15357,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); @@ -15442,6 +15396,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); @@ -15482,6 +15437,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); @@ -15552,6 +15508,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); @@ -15581,6 +15538,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); @@ -15685,10 +15643,19 @@ return; settled = true; const thumb = file.thumbnail || file.data; - const type = await fileTypeFromBuffer(thumb); - $$invalidate(5, url = URL.createObjectURL(new Blob([thumb], { type: type?.mime }))); - if (!type) - return; + let type; + if (typeof thumb != "string") { + type = await fileTypeFromBuffer(thumb); + $$invalidate(5, url = URL.createObjectURL(new Blob([thumb], { type: type?.mime }))); + if (!type) + return; + } else { + let head = headerStringToObject2(await GM_head2(thumb, void 0)); + type = { + ext: "", + mime: head["content-type"].split(";")[0].trim() + }; + } $$invalidate(8, ftype = type.mime); $$invalidate(2, isVideo = type.mime.startsWith("video/")); $$invalidate(4, isAudio = type.mime.startsWith("audio/")); @@ -15728,25 +15695,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 != "string") { + $$invalidate(16, unzipping = true); + let lisn = new EventTarget(); + lisn.addEventListener("progress", (e) => { + $$invalidate(17, progress = e.detail); + }); + let full = await file.data(lisn); + type = await fileTypeFromBuffer(full); + $$invalidate(14, furl = URL.createObjectURL(new Blob([full], { type: type?.mime }))); + } else { + $$invalidate(5, url = file.data); + $$invalidate(14, furl = file.data); + let head = headerStringToObject2(await GM_head2(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); } } @@ -15839,6 +15819,7 @@ return; if (!contracted) return; + recompute(); const [sw, sh] = [visualViewport.width, visualViewport.height]; if (dims[0] == 0 && dims[1] == 0) recompute(); @@ -16560,13 +16541,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, @@ -16736,20 +16717,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 } })); diff --git a/src/Embedding.svelte b/src/Embedding.svelte index c012263..beb9a31 100644 --- a/src/Embedding.svelte +++ b/src/Embedding.svelte @@ -116,9 +116,11 @@ if (hovering) { // reset hovering to recompute proper image coordinates - setTimeout(() => { - recompute() - hoverUpdate() + setTimeout(async () => { + while (dims[0] == 0 && dims[1] == 0) { + hoverUpdate() + await new Promise(_ => setTimeout(_, 20)); + } }, 20) } } @@ -229,6 +231,7 @@ 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() diff --git a/src/main.ts b/src/main.ts index d7810cb..a9cea8c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,7 +22,7 @@ export interface ImageProcessor { match(fn: string): boolean; has_embed(b: Buffer, fn?: string): boolean | Promise; extract(b: Buffer, fn?: string): EmbeddedFile[] | Promise; - inject?(b: File, c: File): Buffer | Promise; + inject?(b: File, c: File[]): Buffer | Promise; } export let csettings: Parameters[0]; @@ -262,13 +262,14 @@ document.addEventListener('QRDialogCreation', ((e: CustomEvent 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 } @@ -276,7 +277,7 @@ document.addEventListener('QRDialogCreation', ((e: CustomEvent document.dispatchEvent(new CustomEvent("CreateNotification", { detail: { type: 'success', - content: 'File successfully embedded!', + content: `File${input.files.length > 1 ? 's' : ''} successfully embedded!`, lifetime: 3 } })); diff --git a/src/pngv3.ts b/src/pngv3.ts index 9cc0723..4f4c290 100644 --- a/src/pngv3.ts +++ b/src/pngv3.ts @@ -74,7 +74,7 @@ export const BufferWriteStream = () => { return [ret, () => b] as [WritableStream, () => 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()); @@ -89,10 +89,8 @@ const inject = async (container: File, inj: File) => { } 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(); diff --git a/src/utils.ts b/src/utils.ts index 602b521..632092f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,51 @@ -import type { Buffer } from "buffer"; +import { Buffer } from "buffer"; +import { GM_fetch, headerStringToObject } from "./requests"; +import thumbnail from "./assets/hasembed.png"; +import type { EmbeddedFile } 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) => { + 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 { + 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; + })); }; \ No newline at end of file diff --git a/src/webm.ts b/src/webm.ts index cd69f9f..c9c80f5 100644 --- a/src/webm.ts +++ b/src/webm.ts @@ -123,7 +123,7 @@ const extract = (webm: Buffer) => { return [{ filename: 'string', data: chk.data }]; }; -const inject = async (container: File, inj: File): Promise => +const inject = async (container: File, [inj]: File[]): Promise => embed(Buffer.from(await container.arrayBuffer()), Buffer.from(await inj.arrayBuffer())); const has_embed = (webm: Buffer) => {