Browse Source

Fix Regex matching and hover thing?

pull/46/head
coomdev 2 years ago
parent
commit
0c8dd166fa
  1. 2
      main.meta.js
  2. 29
      main.user.js
  3. 2
      src/Embedding.svelte
  4. 2
      src/gif.ts
  5. 10
      src/main.ts
  6. 10
      src/png.ts
  7. 149
      src/pngv3.ts
  8. 18
      src/pomf.ts
  9. 1
      src/thirdeye.ts
  10. 5
      src/utils.ts
  11. 2
      src/webm.ts

2
main.meta.js

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

29
main.user.js

@ -1,7 +1,7 @@
// ==UserScript==
// @name PNGExtraEmbed
// @namespace https://coom.tech/
// @version 0.115
// @version 0.116
// @description uhh
// @author You
// @match https://boards.4channel.org/*
@ -11181,7 +11181,7 @@
const fnsize = data.readUInt32LE(0);
const fn = data.slice(4, 4 + fnsize).toString();
data = data.slice(4 + fnsize);
return { filename: fn, data };
return [{ filename: fn, data }];
}
} catch (e) {
console.error(e);
@ -11191,7 +11191,7 @@
};
var buildChunk = (tag, data) => {
const ret = import_buffer.Buffer.alloc(data.byteLength + 4);
ret.write(tag.substr(0, 4), 0);
ret.write(tag.slice(0, 4), 0);
data.copy(ret, 4);
return ret;
};
@ -11345,7 +11345,7 @@
return;
const chk = chunks[embed2 + 1];
if (chk.type == "b" && chk.name == "TagBinary")
return { filename: "string", data: chk.data };
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 has_embed2 = (webm) => {
@ -11407,7 +11407,7 @@
ptr += sec.data.byteLength;
end = sec.end;
} while (sec.appname == "COOMTECH" && gif[end] == "!".charCodeAt(0));
return { data: ret, filename: "embedded" };
return [{ data: ret, filename: "embedded" }];
}
end = sec.end;
}
@ -11629,7 +11629,6 @@
var unlockQueue = Promise.resolve();
var queryCache = {};
var processQueries = async () => {
console.log("======== FIRIN =======");
let unlock;
unlockQueue = new Promise((_) => unlock = _);
const md5 = reqQueue.map((e) => e[0]).filter((e) => !(e in queryCache));
@ -11747,7 +11746,7 @@
];
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}=)?$/);
const isB64 = fn.match(/^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))?\.(gif|jpe?g|png|webm)/);
const isExt = fn.match(/\[.*=(.*)\]/);
let ext;
if (isDum) {
@ -11770,7 +11769,7 @@
} catch {
}
}
return {
return [{
filename: ext,
data: async (lsn) => {
try {
@ -11779,7 +11778,7 @@
}
},
thumbnail: hasembed_default
};
}];
};
var has_embed5 = async (b, fn) => {
const ext = getExt(fn);
@ -11798,13 +11797,7 @@
skip: true,
extract: extract5,
has_embed: has_embed5,
match: (fn) => {
const base = fn.split(".").slice(0, -1).join(".");
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}=)?$/);
const isExt = !!fn.match(/\[.*=.*\]/);
return isB64 || isExt || isDum;
}
match: (fn) => !!getExt(fn)
};
// src/App.svelte
@ -15847,6 +15840,8 @@
if (!contracted)
return;
const [sw, sh] = [visualViewport.width, visualViewport.height];
if (dims[0] == 0 && dims[1] == 0)
recompute();
let width = dims[0];
let height = dims[1] + 25;
let { clientX, clientY } = ev || lastev;
@ -16647,7 +16642,7 @@
res2 = res2?.filter((e) => e);
if (!res2 || res2.length == 0)
return;
processAttachments(post, res2?.filter((e) => e));
processAttachments(post, res2?.filter((e) => e).flatMap((e) => e[0].map((k) => [k, e[1]])));
};
var startup = async () => {
if (typeof window["FCX"] != "undefined")

2
src/Embedding.svelte

@ -211,6 +211,8 @@
if (!contracted) return
const [sw, sh] = [visualViewport.width, visualViewport.height]
// shamelessly stolen from 4chanX
if (dims[0] == 0 && dims[1] == 0)
recompute();
let width = dims[0]
let height = dims[1] + 25
let { clientX, clientY } = (ev || lastev!)

2
src/gif.ts

@ -43,7 +43,7 @@ const extractBuff = (gif: Buffer) => {
ptr += sec.data.byteLength;
end = sec.end;
} while (sec.appname == "COOMTECH" && gif[end] == '!'.charCodeAt(0));
return { data: ret, filename: 'embedded' } as EmbeddedFile;
return [{ data: ret, filename: 'embedded' }] as EmbeddedFile[];
}
end = sec.end;
}

10
src/main.ts

@ -21,7 +21,7 @@ export interface ImageProcessor {
skip?: true;
match(fn: string): boolean;
has_embed(b: Buffer, fn?: string): boolean | Promise<boolean>;
extract(b: Buffer, fn?: string): EmbeddedFile | Promise<EmbeddedFile>;
extract(b: Buffer, fn?: string): EmbeddedFile[] | Promise<EmbeddedFile[]>;
inject?(b: File, c: File): Buffer | Promise<Buffer>;
}
@ -86,14 +86,14 @@ type EmbeddedFileWithoutPreview = {
export type EmbeddedFile = EmbeddedFileWithPreview | EmbeddedFileWithoutPreview;
const processImage = async (src: string, fn: string, hex: string): Promise<([EmbeddedFile, boolean] | undefined)[]> => {
const processImage = async (src: string, fn: string, hex: string): Promise<([EmbeddedFile[], boolean] | undefined)[]> => {
return Promise.all(processors.filter(e => e.match(fn)).map(async proc => {
if (proc.skip) {
// skip file downloading, file is referenced from the filename
// basically does things like filtering out blacklisted tags
const md5 = Buffer.from(hex, 'base64');
if (await proc.has_embed(md5, fn) === true)
return [await proc.extract(md5, fn), true] as [EmbeddedFile, boolean];
return [await proc.extract(md5, fn), true] as [EmbeddedFile[], boolean];
return;
}
const iter = streamRemote(src);
@ -118,7 +118,7 @@ const processImage = async (src: string, fn: string, hex: string): Promise<([Emb
//console.log(`Gave up on ${src} after downloading ${cumul.byteLength} bytes...`);
return;
}
return [await proc.extract(cumul), false] as [EmbeddedFile, boolean];
return [await proc.extract(cumul), false] as [EmbeddedFile[], boolean];
}));
};
@ -136,7 +136,7 @@ const processPost = async (post: HTMLDivElement) => {
res2 = res2?.filter(e => e);
if (!res2 || res2.length == 0)
return;
processAttachments(post, res2?.filter(e => e) as [EmbeddedFile, boolean][]);
processAttachments(post, res2?.filter(e => e).flatMap(e => e![0].map(k => [k, e![1]] as [EmbeddedFile, boolean])));
};
const startup = async () => {

10
src/png.ts

@ -2,9 +2,9 @@ import { buf } from "crc-32";
import { Buffer } from "buffer";
import type { ImageProcessor } from "./main";
type PNGChunk = [string, Buffer, number, number];
export type PNGChunk = [string, Buffer, number, number];
class PNGDecoder {
export class PNGDecoder {
repr: Buffer;
req = 8;
@ -47,7 +47,7 @@ class PNGDecoder {
}
}
class PNGEncoder {
export class PNGEncoder {
writer: WritableStreamDefaultWriter<Buffer>;
constructor(bytes: WritableStream<Buffer>) {
@ -118,7 +118,7 @@ const extract = async (png: Buffer) => {
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 { filename: fn, data };
return [{ filename: fn, data }];
}
} catch (e) {
console.error(e);
@ -129,7 +129,7 @@ const extract = async (png: Buffer) => {
const buildChunk = (tag: string, data: Buffer) => {
const ret = Buffer.alloc(data.byteLength + 4);
ret.write(tag.substr(0, 4), 0);
ret.write(tag.slice(0, 4), 0);
data.copy(ret, 4);
return ret;
};

149
src/pngv3.ts

@ -0,0 +1,149 @@
import { buf } from "crc-32";
import { Buffer } from "buffer";
import type { ImageProcessor } 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) => {
const ret = new ReadableStream<Buffer>({
pull(cont) {
cont.enqueue(b);
cont.close();
}
});
return ret;
};
const extract = async (png: Buffer) => {
let magic = false;
let coom3 = 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) {
// 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;
magic = true;
}
break;
case 'IDAT':
if (magic) {
lastIDAT = chunk;
break;
}
// eslint-disable-next-line no-fallthrough
case 'IEND':
if (!magic)
return; // Didn't find tExt Chunk;
// eslint-disable-next-line no-fallthrough
default:
break;
}
}
if (lastIDAT) {
let data = (lastIDAT as Buffer).slice(4);
if (coom3)
return decodeCoom3Payload(data);
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 [{ filename: fn, data }];
}
} catch (e) {
console.error(e);
} finally {
reader.releaseLock();
}
};
const buildChunk = (tag: string, data: Buffer) => {
const ret = Buffer.alloc(data.byteLength + 4);
ret.write(tag.slice(0, 4), 0);
data.copy(ret, 4);
return ret;
};
export const BufferWriteStream = () => {
let b = Buffer.from([]);
const ret = new WritableStream<Buffer>({
write(chunk) {
b = Buffer.concat([b, chunk]);
}
});
return [ret, () => b] as [WritableStream<Buffer>, () => Buffer];
};
const inject = async (container: File, inj: File) => {
const [writestream, extract] = BufferWriteStream();
const encoder = new PNGEncoder(writestream);
const decoder = new PNGDecoder(container.stream().getReader());
let magic = false;
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", CUM0), 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);
await encoder.insertchunk(["IDAT", buildChunk("IDAT", injb), 0, 0]);
await encoder.insertchunk(["IEND", buildChunk("IEND", Buffer.from([])), 0, 0]);
return extract();
};
const has_embed = async (png: Buffer) => {
const reader = BufferReadStream(png).getReader();
const sneed = new PNGDecoder(reader);
try {
for await (const [name, chunk, crc, offset] of sneed.chunks()) {
let buff: Buffer;
switch (name) {
// 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 true;
if (buff.slice(4, 4 + CUM0.length).equals(CUM3))
return true;
break;
case 'IDAT':
// eslint-disable-next-line no-fallthrough
case 'IEND':
return false; // Didn't find tExt Chunk; Definite no
// eslint-disable-next-line no-fallthrough
default:
break;
}
}
// stream ended on chunk boundary, so no unexpected EOF was fired, need more data anyway
} catch (e) {
return; // possibly unexpected EOF, need more data to decide
} finally {
reader.releaseLock();
}
};
export default {
extract,
has_embed,
inject,
match: fn => !!fn.match(/\.png$/)
} as ImageProcessor;

18
src/pomf.ts

@ -11,7 +11,7 @@ const sources = [
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}=)?$/);
const isB64 = fn!.match(/^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))?\.(gif|jpe?g|png|webm)/);
const isExt = fn!.match(/\[.*=(.*)\]/);
let ext;
if (isDum) {
@ -37,8 +37,8 @@ const extract = async (b: Buffer, fn?: string) => {
// 404
}
}
return {
return [{
filename: ext,
data: async (lsn) => {
try {
@ -48,7 +48,7 @@ const extract = async (b: Buffer, fn?: string) => {
}
},
thumbnail
} as EmbeddedFile;
} as EmbeddedFile];
};
const has_embed = async (b: Buffer, fn?: string) => {
@ -63,7 +63,7 @@ const has_embed = async (b: Buffer, fn?: string) => {
// 404
}
}
return false;
};
@ -71,11 +71,5 @@ export default {
skip: true,
extract,
has_embed,
match: fn => {
const base = fn.split('.').slice(0, -1).join('.');
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}=)?$/);
const isExt = !!fn.match(/\[.*=.*\]/);
return isB64 || isExt || isDum;
}
match: fn => !!getExt(fn)
} as ImageProcessor;

1
src/thirdeye.ts

@ -71,7 +71,6 @@ let unlockQueue = Promise.resolve();
const queryCache: ApiResult = {};
const processQueries = async () => {
console.log("======== FIRIN =======");
let unlock!: () => void;
unlockQueue = new Promise<void>(_ => unlock = _);
const md5 = reqQueue.map(e => e[0]).filter(e => !(e in queryCache));

5
src/utils.ts

@ -0,0 +1,5 @@
import type { Buffer } from "buffer";
export const decodeCoom3Payload = (buff: Buffer) => {
//
};

2
src/webm.ts

@ -120,7 +120,7 @@ const extract = (webm: Buffer) => {
return;
const chk = chunks[embed + 1];
if (chk.type == "b" && chk.name == "TagBinary")
return { filename: 'string', data: chk.data };
return [{ filename: 'string', data: chk.data }];
};
const inject = async (container: File, inj: File): Promise<Buffer> =>

Loading…
Cancel
Save