diff --git a/src/App.svelte b/src/App.svelte
index c48973c..c08a0d8 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -113,13 +113,6 @@
Disable third-eye.
{#if !$settings.te}
-
-
Booru sources
{#each $settings.rsources as source, i}
diff --git a/src/Embedding.svelte b/src/Embedding.svelte
index c51fadd..8ffaee7 100644
--- a/src/Embedding.svelte
+++ b/src/Embedding.svelte
@@ -5,6 +5,9 @@
import type { EmbeddedFile } from './main'
import { createEventDispatcher } from 'svelte'
import { GM_head, headerStringToObject } from '../dist/requests'
+ import { text } from 'svelte/internal'
+ import App from './App.svelte'
+ import { Buffer } from 'buffer'
export const dispatch = createEventDispatcher()
@@ -12,6 +15,7 @@
let isVideo = false
let isImage = false
let isAudio = false
+ let isText = false
let url = ''
let settled = false
let contracted = true
@@ -38,6 +42,7 @@
return contracted
}
+ let content: Blob
beforeUpdate(async () => {
if (settled) return
settled = true
@@ -46,7 +51,15 @@
let type: FileTypeResult | undefined
if (typeof thumb != 'string') {
type = await fileTypeFromBuffer(thumb)
- url = URL.createObjectURL(new Blob([thumb], { type: type?.mime }))
+ if (
+ !type &&
+ file.filename.endsWith('.txt') &&
+ file.filename.startsWith('message')
+ ) {
+ type = { ext: 'txt', mime: 'text/plain' } as any
+ }
+ content = new Blob([thumb], { type: type?.mime })
+ url = URL.createObjectURL(content)
if (!type) return
} else {
let head = headerStringToObject(await GM_head(thumb, undefined))
@@ -56,6 +69,7 @@
isVideo = type.mime.startsWith('video/')
isAudio = type.mime.startsWith('audio/')
isImage = type.mime.startsWith('image/')
+ isText = type.mime.startsWith('text/plain')
dispatch('fileinfo', { type })
if (isImage) {
@@ -97,9 +111,17 @@
lisn.addEventListener('progress', (e: any) => {
progress = e.detail
})
- let full = await file.data(lisn)
+ let full = Buffer.isBuffer(file.data) ? file.data : await file.data(lisn)
type = await fileTypeFromBuffer(full)
- furl = URL.createObjectURL(new Blob([full], { type: type?.mime }))
+ if (
+ !type &&
+ file.filename.endsWith('.txt') &&
+ file.filename.startsWith('message')
+ ) {
+ type = { ext: 'txt', mime: 'text/plain' } as any
+ }
+ content = new Blob([full], { type: type?.mime })
+ furl = URL.createObjectURL(content)
} else {
url = file.data
furl = file.data
@@ -110,6 +132,7 @@
isVideo = type.mime.startsWith('video/')
isAudio = type.mime.startsWith('audio/')
isImage = type.mime.startsWith('image/')
+ isText = type.mime.startsWith('text/plain')
unzipping = false
dispatch('fileinfo', { type })
@@ -186,7 +209,7 @@
let [iw, ih] = [0, 0]
if (isImage) {
;[iw, ih] = [imgElem.naturalWidth, imgElem.naturalHeight]
- } else {
+ } else if (isVideo) {
;[iw, ih] = [videoElem.videoWidth, videoElem.videoHeight]
}
let scale = Math.min(1, sw / iw, sh / ih)
@@ -197,6 +220,7 @@
}
async function hoverStart(ev?: MouseEvent) {
+ if (!(isVideo || isImage)) return
if ($settings.dh) return
if (file.thumbnail && !furl) {
unzip()
@@ -231,6 +255,7 @@
lastev = lastev || ev
if ($settings.dh) return
if (!contracted) return
+ if (!(isVideo || isImage)) return
recompute() // yeah I gave up
const [sw, sh] = [visualViewport.width, visualViewport.height]
// shamelessly stolen from 4chanX
@@ -310,6 +335,16 @@
/>
{/if}
+ {#if isText}
+
+
+ {#await content.text()}
+
Loading...
+ {:then con}
+
{con}
+ {/await}
+
+ {/if}
+ import { appState } from './stores'
+ import type { ImageProcessor } from './main'
+
+ import { fireNotification, getSelectedFile } from './utils'
+
+ export let processors: ImageProcessor[] = []
+ export let textinput: HTMLTextAreaElement
+
+ let files: File[] = []
+
+ const addContent = (...newfiles: File[]) => {
+ files = [...files, ...newfiles]
+ if (files.length > 5) {
+ fireNotification(
+ 'warning',
+ 'Can only add up to 5 attachments, further attachments will be dropped',
+ )
+ files = files.slice(0, 5)
+ }
+ }
+
+ const embedText = async (e: Event) => {
+ if (textinput.value == '') return
+ if (textinput.value.length > 2000) {
+ fireNotification("error", "Message attachments are limited to 2000 characters")
+ return;
+ }
+ addContent(
+ new File(
+ [new Blob([textinput.value], { type: 'text/plain' })],
+ `message${files.length}.txt`,
+ ),
+ )
+ textinput.value = ''
+ }
+
+ const embedContent = async (e: Event) => {
+ const file = await getSelectedFile()
+ if (!file) return
+ const type = file.type
+ 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, [...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 },
+ }),
+ )
+ fireNotification(
+ 'success',
+ `File${files.length > 1 ? 's' : ''} successfully embedded!`,
+ )
+ } catch (err) {
+ const e = err as Error
+ fireNotification('error', "Couldn't embed file: " + e.message)
+ }
+ }
+
+ const embedFile = async (e: Event) => {
+ const input = document.createElement('input') as HTMLInputElement
+ input.setAttribute('type', 'file')
+ input.multiple = true
+ input.onchange = async (ev) => {
+ if (input.files) {
+ addContent(...input.files)
+ }
+ }
+ input.click()
+ }
+
+
+
+
+
diff --git a/src/global.css b/src/global.css
index fac8fe6..e1a61e3 100644
--- a/src/global.css
+++ b/src/global.css
@@ -73,3 +73,7 @@ div.hasmultiple .catalog-host img {
display: flex;
gap: 20px;
}
+
+#qr > form {
+ overflow: visible !important;
+}
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index daf5260..f978b55 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -13,6 +13,7 @@ import { GM_fetch, GM_head, headerStringToObject } from "./requests";
import App from "./App.svelte";
import ScrollHighlighter from "./ScrollHighlighter.svelte";
+import PostOptions from "./PostOptions.svelte";
import SettingsButton from './SettingsButton.svelte';
//import Embedding from './Embedding.svelte';
import Embeddings from './Embeddings.svelte';
@@ -77,7 +78,7 @@ type EmbeddedFileWithPreview = {
source?: string; // can be like a twitter post this was posted in originally
thumbnail: Buffer;
filename: string;
- data: string | ((lisn?: EventTarget) => Promise
);
+ data: EmbeddedFileWithoutPreview['data'] | ((lisn?: EventTarget) => Promise);
};
type EmbeddedFileWithoutPreview = {
@@ -244,9 +245,8 @@ const scrapeBoard = async (self: HTMLButtonElement) => {
fireNotification("success", "Processing finished!");
};
-const startup = async () => {
- if (typeof (window as any)['FCX'] != "undefined")
- appState.set({ ...cappState, is4chanX: true });
+const startup = async (is4chanX = true) => {
+ appState.set({ ...cappState, is4chanX });
if (csettings.vercheck)
versionCheck();
@@ -296,11 +296,12 @@ const startup = async () => {
if (cappState.isCatalog) {
const opts = document.getElementById('index-options') as HTMLDivElement;
- const button = document.createElement('button');
- button.textContent = "おもらし";
- button.onclick = () => scrapeBoard(button);
- opts.insertAdjacentElement("beforebegin", button);
-
+ if (opts) {
+ const button = document.createElement('button');
+ button.textContent = "おもらし";
+ button.onclick = () => scrapeBoard(button);
+ opts.insertAdjacentElement("beforebegin", button);
+ }
}
const n = 7;
@@ -315,15 +316,8 @@ const startup = async () => {
//await Promise.all(posts.map(e => processPost(e as any)));
};
-const getSelectedFile = () => {
- return new Promise(res => {
- document.addEventListener('QRFile', e => res((e as any).detail), { once: true });
- document.dispatchEvent(new CustomEvent('QRGetFile'));
- });
-};
-
//if (cappState!.is4chanX)
-document.addEventListener('4chanXInitFinished', startup);
+document.addEventListener('4chanXInitFinished', () => startup(true));
/*else {
document.addEventListener("QRGetFile", (e) => {
const qr = document.getElementById('qrFile') as HTMLInputElement | null;
@@ -353,51 +347,22 @@ if (cappState!.is4chanX) {
}
document.addEventListener('QRDialogCreation', ((e: CustomEvent) => {
- const a = document.createElement('a');
- const i = document.createElement('i');
- i.className = "fa fa-magnet";
- a.appendChild(i);
- a.title = "Embed File (Select a file before...)";
+ const a = document.createElement('span');
let target;
- if (cappState.is4chanX) {
- i.innerText = "🧲";
+ if (!cappState.is4chanX) {
target = e.detail;
target.querySelector("input[type=submit]")?.insertAdjacentElement("beforebegin", a);
}
else {
target = e.target as HTMLDivElement;
+ new PostOptions({
+ target: a,
+ props: { processors, textinput: target.querySelector('textarea')! }
+ });
target.querySelector('#qr-filename-container')?.appendChild(a);
}
- a.onclick = async (e) => {
- const file = await getSelectedFile();
- if (!file)
- return;
- 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].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 }
- }));
- fireNotification('success', `File${input.files.length > 1 ? 's' : ''} successfully embedded!`);
- } catch (err) {
- const e = err as Error;
- fireNotification('error', "Couldn't embed file: " + e.message);
- }
- }
- });
- input.click();
- };
}), { once: !cappState!.is4chanX }); // 4chan's normal extension destroys the QR form everytime
const customStyles = document.createElement('style');
diff --git a/src/thirdeye.ts b/src/thirdeye.ts
index 37b1d76..90e6bf1 100644
--- a/src/thirdeye.ts
+++ b/src/thirdeye.ts
@@ -116,12 +116,12 @@ const shoujoFind = async (hex: string): Promise => {
const findFileFrom = async (b: Booru, hex: string, abort?: EventTarget) => {
try {
- if (experimentalApi) {
+/* if (experimentalApi) {
const res = await shoujoFind(hex);
if (!res)
debugger;
return hex in res ? (res[hex][b.domain] || []) : [];
- }
+ }*/
if (b.domain in cache && hex in cache[b.domain])
return cache[b.domain][hex] as BooruMatch[];
const res = await GM_fetch(`https://${b.domain}${b.endpoint}${hex}`);
diff --git a/src/utils.ts b/src/utils.ts
index 99a87b1..5c94d1d 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -60,7 +60,7 @@ export const buildPeeFile = async (f: File) => {
const namebuf = Buffer.from(f.name);
const ret = Buffer.alloc(4 /* Magic */ +
1 /* Flags */ + namebuf.byteLength + 1 +
- (4 + thumbnail.byteLength) /* TSize + Thumbnail */ +
+ (thumbnail.byteLength != 0 ? (4 + thumbnail.byteLength) : 0) /* TSize + Thumbnail */ +
f.size /*Teh file*/);
let ptr = 0;
ret.write('PEE\0', 0);
@@ -126,12 +126,16 @@ export const decodeCoom3Payload = async (buff: Buffer) => {
let thumbsize = 0;
if (hasThumbnail) {
thumbsize = header.readInt32LE(ptr);
- thumb = Buffer.from(await (await GM_fetch(pee, { headers: { 'user-agent': '', range: `bytes=${ptr + 4}-${ptr + 4 + thumbsize}` } })).arrayBuffer());
+ ptr += 4;
+ thumb = Buffer.from(await (await GM_fetch(pee, { headers: { 'user-agent': '', range: `bytes=${ptr}-${ptr + thumbsize}` } })).arrayBuffer());
+ ptr += thumbsize;
}
+ const unzip = async (lsn?: EventTarget) =>
+ Buffer.from(await (await GM_fetch(pee, { headers: { 'user-agent': '', range: `bytes=${ptr}-${size - 1}` } }, lsn)).arrayBuffer());
return {
filename: fn,
- data: async (lsn) =>
- Buffer.from(await (await GM_fetch(pee, { headers: { 'user-agent': '', range: `bytes=${ptr + 4 + thumbsize}-${size - 1}` } }, lsn)).arrayBuffer()),
+ // if file is small, then just get it fully
+ data: size < 3072 ? await unzip() : unzip,
thumbnail: thumb,
} as EmbeddedFile;
} catch (e) {
@@ -175,4 +179,11 @@ export const uploadFiles = async (injs: File[]) => {
fireNotification('info', `Uploaded files [${++total}/${injs.length}] ${ret}`);
return ret;
}));
-};
\ No newline at end of file
+};
+
+export const getSelectedFile = () => {
+ return new Promise(res => {
+ document.addEventListener('QRFile', e => res((e as any).detail), { once: true });
+ document.dispatchEvent(new CustomEvent('QRGetFile'));
+ });
+};
diff --git a/src/websites/index.ts b/src/websites/index.ts
new file mode 100644
index 0000000..508e7a6
--- /dev/null
+++ b/src/websites/index.ts
@@ -0,0 +1,9 @@
+export type QueryProcessor = {
+ thumbnailSelector: string;
+ md5Selector: string;
+ filenameSelector: string;
+ linkSelector: string;
+ postWithFileSelector: string;
+ postContainerSelector: string;
+ controlHostSelector: string;
+};