Browse Source

Experimental native JPG support

pull/46/head
coomdev 2 years ago
parent
commit
cde3a75566
  1. 2
      README.md
  2. 2304
      chrome/dist/main.js
  3. 2
      chrome/manifest.json
  4. 2617
      dist/main.js
  5. 23
      firefox/dist/background.js
  6. 2672
      firefox/dist/main.js
  7. 2
      firefox/manifest.json
  8. 2
      firefox_update.json
  9. 10
      main.d.ts
  10. 2
      main.meta.js
  11. 2619
      main.user.js
  12. 28
      package-lock.json
  13. 1
      package.json
  14. 8
      src/Components/App.svelte
  15. 57
      src/jpg.ts
  16. 16
      src/main.ts
  17. 1
      src/stores.ts

2
README.md

@ -1,7 +1,7 @@
PNGExtraEmbedder (PEE)
========================
Can embed any file in a PNG/WebM/GIF and upload it to a third-party host through 4chan.
Can embed any file in a PNG/WebM/GIF/JPEG and upload it to a third-party host through 4chan.
Requires a userscript manager, such as ViolentMonkey.
It should work with 4chan's native extension but 4ChanX is highly recommended as it is much more tested.
Also supports desuarchive.

2304
chrome/dist/main.js

File diff suppressed because it is too large

2
chrome/manifest.json

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "PngExtraEmbedder",
"description": "Discover embedded files on 4chan and archives!",
"version": "0.260",
"version": "0.261",
"icons": {
"64": "1449696017588.png"
},

2617
dist/main.js

File diff suppressed because it is too large

23
firefox/dist/background.js

@ -1874,6 +1874,8 @@
// src/platform.ts
var lqueue = {};
var localLoad = (key, def) => "__pee__" + key in localStorage ? JSON.parse(localStorage.getItem("__pee__" + key)) : def;
var localSet = (key, value) => localStorage.setItem("__pee__" + key, JSON.stringify(value));
var { port1, port2 } = new MessageChannel();
console.log("ff_api", true);
if (false) {
@ -1917,6 +1919,15 @@
for (const k of keys)
ctor[k] = bridge(k, ctor[k]);
};
if (false) {
popupport = chrome.runtime.connect({ name: "popup" });
popupport.onMessage.addListener((msg) => {
if (msg.id in pendingcmds) {
pendingcmds[msg.id](msg);
delete pendingcmds[msg.id];
}
});
}
var Platform = class {
static async openInTab(src, opts) {
if (false) {
@ -1928,7 +1939,14 @@
i = (await obj2.tabs.getCurrent()).index + 1;
return obj2.tabs.create({ active: opts.active, url: src, index: i });
}
static getValue(name, def) {
return localLoad(name, def);
}
static setValue(name, val) {
localSet(name, val);
}
};
Platform.cmdid = 0;
Platform = __decorateClass([
Bridged
], Platform);
@ -1953,8 +1971,9 @@
var bgCorsFetch = async (c, id, input, init) => {
if (input.startsWith("//"))
input = "https:" + input;
if (init?.body && false)
init.body = await deserialize(init.body);
if (init?.body && false) {
init.body = await bravedeserialize(init.body);
}
try {
const k = await fetch(input, init);
let headersStr = "";

2672
firefox/dist/main.js

File diff suppressed because it is too large

2
firefox/manifest.json

@ -7,7 +7,7 @@
},
"name": "PngExtraEmbedder",
"description": "Discover embedded files on 4chan and archives!",
"version": "0.254",
"version": "0.261",
"icons": {
"64": "1449696017588.png"
},

2
firefox_update.json

@ -1 +1 @@
{"addons":{"{34ac4994-07f2-44d2-8599-682516a6c6a6}":{"updates":[{"version":"0.254","update_link":"https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.254.xpi"}]}}}
{"addons":{"{34ac4994-07f2-44d2-8599-682516a6c6a6}":{"updates":[{"version":"0.261","update_link":"https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.261.xpi"}]}}}

10
main.d.ts

@ -6,6 +6,16 @@ declare module '*.png' {
export default new Uint8Array;
}
declare module 'f5stegojs' {
export default class f5 {
constructor(private seed: ArrayLike<number>);
embed(jpegdata: Uint8Array, data: Uint8Array): Uint8Array;
extract(jpegdata: Uint8Array): Uint8Array | null;
}
}
declare module 'mp4box' {
type BaseTrackInfo = {
id: number,

2
main.meta.js

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

2619
main.user.js

File diff suppressed because it is too large

28
package-lock.json

@ -13,6 +13,7 @@
"buffer": "^6.0.3",
"crc-32": "^1.2.0",
"events": "^3.3.0",
"f5stegojs": "^0.1.2",
"file-type": "^17.0.2",
"hls.js": "^1.1.5",
"image-hash": "^5.0.1",
@ -4144,6 +4145,19 @@
"node >=0.6.0"
]
},
"node_modules/f5stegojs": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/f5stegojs/-/f5stegojs-0.1.2.tgz",
"integrity": "sha512-aOLoD2U9yENaq7ripE0PTrAcyYml5IYr1Kuf5k5rW33xhVKAFbQiFx3kRID7Te05vZoWYNbnjPuP8YrG01Fhug==",
"dependencies": {
"minimist": "^1.2.0"
},
"bin": {
"f5stego": "bin/f5stego",
"stegodctdump": "bin/stegodctdump",
"stegodcthist": "bin/stegodcthist"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -6239,8 +6253,7 @@
"node_modules/minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"node_modules/minimist-options": {
"version": "4.1.0",
@ -12262,6 +12275,14 @@
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"f5stegojs": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/f5stegojs/-/f5stegojs-0.1.2.tgz",
"integrity": "sha512-aOLoD2U9yENaq7ripE0PTrAcyYml5IYr1Kuf5k5rW33xhVKAFbQiFx3kRID7Te05vZoWYNbnjPuP8YrG01Fhug==",
"requires": {
"minimist": "^1.2.0"
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -13879,8 +13900,7 @@
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"minimist-options": {
"version": "4.1.0",

1
package.json

@ -19,6 +19,7 @@
"buffer": "^6.0.3",
"crc-32": "^1.2.0",
"events": "^3.3.0",
"f5stegojs": "^0.1.2",
"file-type": "^17.0.2",
"hls.js": "^1.1.5",
"image-hash": "^5.0.1",

8
src/Components/App.svelte

@ -123,6 +123,14 @@
{/if}
</TabList>
<TabPanel>
<label>
<input type="checkbox" bind:checked={$settings.jpeg} />
Enable JPGs support (JPG embed and extract)
<a
title="JPG embed detection is relatively slow, heavy, so you might want to also enable server cache loading"
>?</a
>
</label>
<label>
<input type="checkbox" bind:checked={$cached} />
Try to load embeds from server cache

57
src/jpg.ts

@ -1,7 +1,15 @@
import { Buffer } from "buffer";
import type { ImageProcessor } from "./main";
import pngv3 from "./pngv3";
import { fireNotification } from "./utils";
import f5 from 'f5stegojs';
import { settings } from "./stores";
import { decodeCoom3Payload } from "./utils";
export let csettings: Parameters<typeof settings['set']>[0];
settings.subscribe(b => {
csettings = b;
});
export const convertToPng = async (f: File): Promise<Blob | undefined> => {
const can = document.createElement("canvas");
@ -32,7 +40,7 @@ export const convertToPng = async (f: File): Promise<Blob | undefined> => {
can.width = dims[0];
can.height = dims[1];
const ctx = can.getContext("2d");
if (!ctx)
return;
ctx.drawImage(source, 0, 0, dims[0], dims[1]);
@ -46,7 +54,21 @@ export const convertToPng = async (f: File): Promise<Blob | undefined> => {
}
};
const key = Buffer.from("CUNNYCUNNYCUNNY");
const f5inst = new f5(key);
const injectTrue = async (b: File, links: string[]) => {
// TODO: maybe do a lossless crop/embed/concat?
if (b.size / 20 < links.join(' ').length)
throw "Image too small to embed.";
const arr = new Uint8Array(await b.arrayBuffer());
const buff = f5inst.embed(arr, Buffer.from(links.join(' ')));
return Buffer.from(buff);
};
const inject = async (b: File, links: string[]) => {
if (csettings.jpeg)
return injectTrue(b, links);
const pngfile = await convertToPng(b);
if (!pngfile || pngfile.size > 3000 * 1024) {
throw new Error("Couldn't convert file to PNG: resulting filesize too big.");
@ -54,10 +76,35 @@ const inject = async (b: File, links: string[]) => {
return pngv3.inject!(new File([pngfile], b.name), links);
};
// unfortunately, because of the way f5 work, we can't determine
// if there's an embedded message until we have the complete file
// but the way PEE was designed forces us to just try to extract something until it works
const has_embed = (b: Buffer) => {
if (!csettings.jpeg)
return false;
try {
const res = f5inst.extract(b);
if (!res)
return; // unsure
if (res.length > 1024) // probably garbage, allows for ~20 links from take-me-to.space, should be enough
return; // unsure
const str = Buffer.from(res).toString();
if (!str.match(/^[a-zA-Z0-9:/.\-_ ]+$/))
return; // unsure
return str; // sure
} catch {
return; // unsure
}
};
const extract = (b: Buffer, ex: string) => {
// if we reached here then ex is heckin cute and valid
return decodeCoom3Payload(Buffer.from(ex));
};
export default {
skip: true,
match: fn => !!fn.match(/\.jpe?g$/),
has_embed: () => false,
extract: () => [],
has_embed,
extract,
inject
} as ImageProcessor;

16
src/main.ts

@ -32,7 +32,7 @@ if (!supportedMainDomain(location.host) && !supportedAltDomain(location.host))
export interface ImageProcessor {
skip?: true;
match(fn: string): boolean;
has_embed(b: Buffer, fn?: string, prevurl?: string): boolean | Promise<boolean>;
has_embed(b: Buffer, fn?: string, prevurl?: string): boolean | string | undefined | Promise<boolean | string | undefined>;
extract(b: Buffer, fn?: string): EmbeddedFile[] | Promise<EmbeddedFile[]>;
inject?(b: File, c: string[]): Buffer | Promise<Buffer>;
}
@ -121,12 +121,16 @@ const processImage = async (srcs: AsyncGenerator<string, void, void>, fn: string
} else {
chunk = { done: false, value } as ReadableStreamDefaultReadValueResult<Buffer>;
cumul = Buffer.concat([cumul, value!]);
found = await proc.has_embed(cumul);
const v = await proc.has_embed(cumul);
if (typeof v == "string") {
return [await proc.extract(cumul, v), false] as [EmbeddedFile[], boolean];
}
found = v;
}
} while (found !== false && !chunk.done /* Because we only embed links now, it's safe to assume we get everything we need in the first chunk */);
succ = true;
await iter.next(true);
if (found === false) {
if (found !== true) {
//console.log(`Gave up on ${src} after downloading ${cumul.byteLength} bytes...`);
return;
}
@ -312,7 +316,11 @@ const scrapeBoard = async (self: HTMLButtonElement) => {
} else {
chunk = { done: false, value } as ReadableStreamDefaultReadValueResult<Buffer>;
cumul = Buffer.concat([cumul, value!]);
found = await proc.has_embed(cumul);
const v = await proc.has_embed(cumul);
if (typeof v == "string") {
return true;
}
found = v;
}
} while (found !== false && !chunk.done);
await iter.next(true);

1
src/stores.ts

@ -33,6 +33,7 @@ export const initial_settings = localLoad('settingsv2', {
mdist: -1,
phash: false,
hotlink: false,
jpeg: false,
vercheck: false,
cache: undefined as (boolean | undefined), // meaning defaults to false, except on b4k
fhost: 0,

Loading…
Cancel
Save