Browse Source

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

Anonymous 2 years ago
parent
commit
57f4fde976
  1. 2
      main.meta.js
  2. 1076
      main.user.js
  3. 140
      src/main.ts
  4. 53
      src/pngv3.ts
  5. 11
      src/requests.ts
  6. 109
      src/utils.ts

2
main.meta.js

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

1076
main.user.js

File diff suppressed because it is too large

140
src/main.ts

@ -3,6 +3,7 @@ import { appState, settings } from "./stores";
import globalCss from './global.css';
import png from "./png";
import pngv3 from "./pngv3";
import webm from "./webm";
import gif from "./gif";
import thirdeye from "./thirdeye";
@ -16,6 +17,8 @@ import SettingsButton from './SettingsButton.svelte';
//import Embedding from './Embedding.svelte';
import Embeddings from './Embeddings.svelte';
import EyeButton from './EyeButton.svelte';
import { buildPeeFile, fireNotification } from "./utils";
import { fileTypeFromBuffer } from "file-type";
export interface ImageProcessor {
skip?: true;
@ -27,12 +30,12 @@ export interface ImageProcessor {
export let csettings: Parameters<typeof settings['set']>[0];
let processors: ImageProcessor[] =
[thirdeye, pomf, png, webm, gif];
[thirdeye, pomf, png, pngv3, webm, gif];
let cappState: Parameters<typeof appState['set']>[0];
settings.subscribe(b => {
csettings = b;
processors = [...(!csettings.te ? [thirdeye] : []), png, pomf, webm, gif
processors = [...(!csettings.te ? [thirdeye] : []), png, pngv3, pomf, webm, gif
];
});
@ -238,7 +241,7 @@ const startup = async () => {
// await processPost(posts[i] as any);
const n = 7;
console.log(posts);
//console.log(posts);
const range = ~~(posts.length / n) + 1;
await Promise.all([...new Array(n + 1)].map(async (e, i) => {
const postsslice = posts.slice(i * range, (i + 1) * range);
@ -318,27 +321,15 @@ document.addEventListener('QRDialogCreation', <any>((e: CustomEvent<HTMLElement>
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]);
const buff = await proc.inject!(file, [...input.files].slice(0, 5));
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 }
}));
document.dispatchEvent(new CustomEvent("CreateNotification", {
detail: {
type: 'success',
content: `File${input.files.length > 1 ? 's' : ''} successfully embedded!`,
lifetime: 3
}
}));
fireNotification('success', `File${input.files.length > 1 ? 's' : ''} successfully embedded!`);
} catch (err) {
const e = err as Error;
document.dispatchEvent(new CustomEvent("CreateNotification", {
detail: {
type: 'error',
content: "Couldn't embed file: " + e.message,
lifetime: 3
}
}));
fireNotification('error', "Couldn't embed file: " + e.message);
}
}
});
@ -353,6 +344,8 @@ customStyles.appendChild(document.createTextNode(globalCss));
document.documentElement.insertBefore(customStyles, null);
function processAttachments(post: HTMLDivElement, ress: [EmbeddedFile, EMBED_TYPES][]) {
if (ress.length == 0)
return;
const replyBox = post.querySelector('.post');
const embed_status = ress[0][1];
if (embed_status === EMBED_TYPES.EXTERNAL_BOORU || embed_status === EMBED_TYPES.EXTERNAL_POMF)
@ -428,36 +421,81 @@ function processAttachments(post: HTMLDivElement, ress: [EmbeddedFile, EMBED_TYP
post.setAttribute('data-processed', "true");
}
//if ((window as any)['pagemode']) {
// onload = () => {
// console.log("loaded");
// const resbuf = async (s: EmbeddedFile['data']) => Buffer.isBuffer(s) ? s : await s();
// const container = document.getElementById("container") as HTMLInputElement;
// const injection = document.getElementById("injection") as HTMLInputElement;
// container.onchange = injection.onchange = async () => {
// console.log('eval changed');
// if (container.files?.length && injection.files?.length) {
// const dlr = document.getElementById("dlr") as HTMLAnchorElement;
// const dle = document.getElementById("dle") as HTMLAnchorElement;
// console.log(buf(new Uint8Array(await container.files[0].arrayBuffer())));
// console.log(buf(new Uint8Array(await injection.files[0].arrayBuffer())));
// const res = await gif.inject!(container.files[0], injection.files[0]);
// console.log('inj done', buf(res));
// const result = document.getElementById("result") as HTMLImageElement;
// const extracted = document.getElementById("extracted") as HTMLImageElement;
// const res2 = new Blob([res], { type: (await fileTypeFromBuffer(res))?.mime });
// result.src = URL.createObjectURL(res2);
// dlr.href = result.src;
// console.log('url created');
// const embedded = await gif.extract(res);
// console.log(buf(new Uint8Array(await resbuf(embedded.data))));
// if (!embedded) {
// debugger;
// return;
// }
// extracted.src = URL.createObjectURL(new Blob([await resbuf(embedded.data!)]));
// dle.href = extracted.src;
// }
// };
// };
//}
function parseForm(data: object) {
const form = new FormData();
Object.entries(data)
.filter(([key, value]) => value !== null)
.map(([key, value]) => form.append(key, value));
return form;
}
// if ((window as any)['pagemode']) {
// onload = () => {
// const resbuf = async (s: EmbeddedFile['data']) => typeof s != "string" && (Buffer.isBuffer(s) ? s : await s());
// const container = document.getElementById("container") as HTMLInputElement;
// const injection = document.getElementById("injection") as HTMLInputElement;
// container.onchange = async () => {
// const ret = await fetch("https://catbox.moe/user/api.php", {
// method: 'POST',
// body: parseForm({
// reqtype: 'fileupload',
// fileToUpload: container.files![0]
// })
// });
// console.log(ret);
// console.log(await ret.text());
// };
// };
// }
// if ((window as any)['pagemode']) {
// onload = () => {
// const extraction = document.getElementById("extraction") as HTMLInputElement;
// /* extraction.onchange = async () => {
// const pee = await buildPeeFile(extraction.files![0]);
// const dlr = document.getElementById("dlr") as HTMLAnchorElement;
// dlr.href = URL.createObjectURL(pee);
// };*/
// document.addEventListener("CreateNotification", (e: any) => console.log(e.detail));
// console.log("loaded");
// //const resbuf = async (s: any) => ((Buffer.isBuffer(s) ? s : await s()));
// const container = document.getElementById("container") as HTMLInputElement;
// const injection = document.getElementById("injection") as HTMLInputElement;
// injection.multiple = true;
// extraction.onchange = async () => {
// const embedded = await pngv3.extract(Buffer.from(await extraction.files![0].arrayBuffer()));
// const d = document.createElement('div');
// new Embeddings({
// target: d,
// props: { files: embedded }
// });
// document.body.append(d);
// };
// container.onchange = injection.onchange = async () => {
// console.log('eval changed');
// if (container.files?.length && injection.files?.length) {
// const dlr = document.getElementById("dlr") as HTMLAnchorElement;
// //const dle = document.getElementById("dle") as HTMLAnchorElement;
// const res = await pngv3.inject!(container.files[0], [...injection.files]);
// const result = document.getElementById("result") as HTMLImageElement;
// //const extracted = document.getElementById("extracted") as HTMLImageElement;
// const res2 = new Blob([res], { type: (await fileTypeFromBuffer(res))?.mime });
// result.src = URL.createObjectURL(res2);
// dlr.href = result.src;
// console.log('url created');
// //const embedded = await pngv3.extract(res);
// //if (!embedded) {
// // debugger;
// return;
// //}
// //extracted.src = URL.createObjectURL(new Blob([await resbuf(embedded.data!)]));
// //dle.href = extracted.src;
// }
// };
// };
// }

53
src/pngv3.ts

@ -1,10 +1,10 @@
import { buf } from "crc-32";
import { Buffer } from "buffer";
import { EMBED_TYPES, ImageProcessor, EmbeddedFile, EMBED_STATUS } from './main';
import { PNGDecoder, PNGEncoder } from "./png";
import { decodeCoom3Payload } from "./utils";
import { buildPeeFile, decodeCoom3Payload, fireNotification } from "./utils";
import { GM_fetch } from "./requests";
const CUM3 = Buffer.from("CUM\0" + "3");
const CUM3 = Buffer.from("doo\0" + "m");
const BufferReadStream = (b: Buffer) => {
const ret = new ReadableStream<Buffer>({
@ -17,11 +17,9 @@ const BufferReadStream = (b: Buffer) => {
};
const extract = async (png: Buffer): Promise<EmbeddedFile[] | undefined> => {
let magic = false;
const reader = BufferReadStream(png).getReader();
const sneed = new PNGDecoder(reader);
try {
let lastIDAT: Buffer | null = null;
for await (const [name, chunk, crc, offset] of sneed.chunks()) {
let buff: Buffer;
switch (name) {
@ -29,27 +27,18 @@ const extract = async (png: Buffer): Promise<EmbeddedFile[] | undefined> => {
case 'tEXt':
buff = chunk;
if (buff.slice(4, 4 + CUM3.length).equals(CUM3)) {
magic = true;
return await decodeCoom3Payload(buff.slice(4 + CUM3.length));
}
break;
case 'IDAT':
if (magic) {
lastIDAT = chunk;
break;
}
// eslint-disable-next-line no-fallthrough
case 'IEND':
if (!magic)
return; // Didn't find tExt Chunk;
return;
// eslint-disable-next-line no-fallthrough
default:
break;
}
}
if (lastIDAT) {
const data = (lastIDAT as Buffer).slice(4);
return await decodeCoom3Payload(data);
}
} catch (e) {
console.error(e);
} finally {
@ -74,24 +63,48 @@ export const BufferWriteStream = () => {
return [ret, () => b] as [WritableStream<Buffer>, () => Buffer];
};
function parseForm(data: object) {
const form = new FormData();
Object.entries(data)
.filter(([key, value]) => value !== null)
.map(([key, value]) => form.append(key, value));
return form;
}
const inject = async (container: File, injs: File[]) => {
const [writestream, extract] = BufferWriteStream();
const encoder = new PNGEncoder(writestream);
const decoder = new PNGDecoder(container.stream().getReader());
let total = 0;
fireNotification('info', `Uploading ${injs.length} files...`);
const links = await Promise.all(injs.map(async inj => {
const ret = await (await GM_fetch("https://catbox.moe/user/api.php", {
method: 'POST',
body: parseForm({
reqtype: 'fileupload',
fileToUpload: await buildPeeFile(inj)
})
})).text();
fireNotification('info', `Uploaded files [${++total}/${injs.length}] ${ret}`);
return ret;
}));
let magic = false;
const injb = Buffer.from(links.join(' '));
for await (const [name, chunk, crc, offset] of decoder.chunks()) {
if (magic && name != "IDAT")
break;
if (!magic && name == "IDAT") {
await encoder.insertchunk(["tEXt", buildChunk("tEXt", CUM3), 0, 0]);
await encoder.insertchunk(["tEXt", buildChunk("tEXt",
Buffer.concat([CUM3, injb])), 0, 0]);
magic = true;
}
await encoder.insertchunk([name, chunk, crc, offset]);
}
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();
};

11
src/requests.ts

@ -33,7 +33,7 @@ export function GM_head(...[url, opt]: Parameters<typeof fetch>) {
});
}
export function GM_fetch(...[url, opt, lisn]: [...Parameters<typeof fetch>, EventTarget?]) {
export let GM_fetch = (...[url, opt, lisn]: [...Parameters<typeof fetch>, EventTarget?]) => {
function blobTo(to: string, blob: Blob) {
if (to == "arrayBuffer" && blob.arrayBuffer)
return blob.arrayBuffer();
@ -56,10 +56,10 @@ export function GM_fetch(...[url, opt, lisn]: [...Parameters<typeof fetch>, Even
// https://www.tampermonkey.net/documentation.php?ext=dhdg#GM_xmlhttpRequest
const gmopt: Tampermonkey.Request<any> = {
url: url.toString(),
data: opt?.body?.toString(),
data: opt?.body as any,
responseType: "blob",
headers: opt?.headers as any,
method: "GET",
method: opt?.method as any || "GET",
...(lisn ? {
onprogress: (prog) => {
if (prog.loaded != prog.total && prog.total != 0)
@ -81,7 +81,10 @@ export function GM_fetch(...[url, opt, lisn]: [...Parameters<typeof fetch>, Even
};
xmlhttprequest(gmopt);
});
}
};
if ((window as any)['pagemode'])
GM_fetch = fetch as any;
const makePoolable = <T extends any[], U>(fun: (...args: T) => Promise<U>, getPoolSize: () => number) => {
const pool = [];

109
src/utils.ts

@ -1,8 +1,70 @@
import { Buffer } from "buffer";
import { GM_fetch, headerStringToObject } from "./requests";
import { GM_fetch, GM_head, headerStringToObject } from "./requests";
import thumbnail from "./assets/hasembed.png";
import { EmbeddedFile, EMBED_TYPES } from './main';
const generateThumbnail = async (f: File): Promise<Buffer> => {
const can = document.createElement("canvas");
can.width = 125;
can.height = 125;
const ctx = can.getContext("2d");
if (!ctx)
return Buffer.alloc(0);
const [sw, sh] = [125, 125];
const url = URL.createObjectURL(f);
if (f.type.startsWith("image")) {
const imgElem = document.createElement('img');
imgElem.src = url;
await new Promise(_ => imgElem.onload = _);
const [iw, ih] = [imgElem.naturalWidth, imgElem.naturalHeight];
const scale = Math.min(1, sw / iw, sh / ih);
const dims = [~~(iw * scale), ~~(ih * scale)] as [number, number];
ctx.drawImage(imgElem, 0, 0, dims[0], dims[1]);
} else if (f.type.startsWith("video")) {
const vidElem = document.createElement('video');
vidElem.src = url;
await new Promise(_ => vidElem.onloadedmetadata = _);
const [iw, ih] = [vidElem.videoWidth, vidElem.videoHeight];
const scale = Math.min(1, sw / iw, sh / ih);
const dims = [~~(iw * scale), ~~(ih * scale)] as [number, number];
ctx.drawImage(vidElem, 0, 0, dims[0], dims[1]);
} else
return Buffer.alloc(0);
const blob = await new Promise<Blob | null>(_ => can.toBlob(_, "image/jpg"));
if (!blob)
return Buffer.alloc(0);
return new Buffer(await blob.arrayBuffer());
};
export const buildPeeFile = async (f: File) => {
//const isMemeBrowser = navigator.userAgent.indexOf("Chrome") == -1;
let thumbnail = Buffer.alloc(0);
thumbnail = await generateThumbnail(f);
const namebuf = Buffer.from(f.name);
const ret = Buffer.alloc(4 /* Magic */ +
1 /* Flags */ + namebuf.byteLength + 1 +
(4 + thumbnail.byteLength) /* TSize + Thumbnail */ +
f.size /*Teh file*/);
let ptr = 0;
ret.write('PEE\0', 0);
ptr += 4;
ret[ptr++] = 1 | ((+(thumbnail.length != 0)) << 2);
namebuf.copy(ret, ptr);
ptr += namebuf.byteLength;
ret[ptr++] = 0;
if (thumbnail.length > 0) {
ret.writeInt32LE(thumbnail.byteLength, ptr);
ptr += 4;
thumbnail.copy(ret, ptr);
ptr += thumbnail.byteLength;
}
new Buffer(await f.arrayBuffer()).copy(ret, ptr);
return new Blob([ret]);
};
/*
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]
@ -11,42 +73,61 @@ rest: [X bytes of thumbnail data])[file bytes]
&2 => has tags
&4 => has thumbnail
*/
export const decodeCoom3Payload = async (buff: Buffer) : Promise<EmbeddedFile[]> => {
const pees = buff.toString().split('\0');
export const decodeCoom3Payload = async (buff: Buffer) => {
const pees = buff.toString().split(' ').slice(0, 5).filter(e => e.startsWith("http"));
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 headers = headerStringToObject(await GM_head(pee));
const res = await GM_fetch(pee, {
headers: { ranges: 'bytes=0-2048' },
mode: 'cors',
referrerPolicy: 'no-referrer',
});
const size = +headers['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 hptr = 0;
if (header.slice(0, 4).toString() == "PEE\0")
hptr += 4;
const flags = header[hptr];
const hasFn = !!(flags & 1);
const hasTags = !!(flags & 2);
const hasThumbnail = !!(flags & 4);
let [ptr, ptr2] = [hptr + 1, hptr + 1];
let fn = 'embedded';
let tags = [];
let thumb: EmbeddedFile['thumbnail'] = Buffer.from(thumbnail);
if (hasFn) {
while (buff[ptr2] != 0)
while (header[ptr2] != 0)
ptr2++;
fn = header.slice(ptr, ptr2).toString();
ptr = ++ptr2;
}
if (hasTags) {
while (buff[ptr2] != 0)
while (header[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());
console.log("Thumbnail size of ", thumbsize);
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()),
Buffer.from(await (await GM_fetch(pee, { headers: { range: `bytes=${ptr + 4 + thumbsize}-${size - 1}` } }, lsn)).arrayBuffer()),
thumbnail: thumb,
} as EmbeddedFile;
}));
};
export const fireNotification = (level: 'success' | 'error' | 'info' | 'warning', text: string, lifetime = 3) => {
document.dispatchEvent(new CustomEvent("CreateNotification", {
detail: {
type: level,
content: text,
lifetime
}
}));
};
Loading…
Cancel
Save