Browse Source

Preliminary kohlchan support

pull/54/head
coomdev 1 year ago
parent
commit
17f962e761
  1. 5
      README.md
  2. 1
      build-chrome.js
  3. 4
      build-ff.js
  4. BIN
      chrome/_metadata/generated_indexed_rulesets/_ruleset1
  5. 2
      chrome/b4k-csp.json
  6. 100
      chrome/dist/main.js
  7. 35
      chrome/kohlchan-hooks.js
  8. 5
      chrome/manifest.json
  9. 28
      dist/main.js
  10. 2
      main.d.ts
  11. 31
      src/Components/NotificationsHandler.svelte
  12. 72
      src/main.ts
  13. 1
      src/platform.ts
  14. 39
      src/websites/index.ts

5
README.md

@ -1,11 +1,16 @@
PNGExtraEmbedder (PEE)
========================
!NOTE!: the userscript version is now deprecated and broken.
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.
Kohlchan support is being worked on. Right now works barely on Chrome.
There's no server-side caching for kohlchan. Buttons to embed will only be added to the quick reply form.
How to Install
==============

1
build-chrome.js

@ -33,6 +33,7 @@ const domains = [
"https://fireden.net/*",
"https://thebarchive.com/*",
"https://archiveofsins.com/*",
"https://kohlchan.net/*"
];
const manif3 = {

4
build-ff.js

@ -51,7 +51,9 @@ const domains = [
"https://*.donmai.us/*",
"https://*.lolibooru.moe/*",
"https://*.allthefallen.moe/*",
"https://desu-usergeneratedcontent.xyz/*"
"https://desu-usergeneratedcontent.xyz/*",
"https://kohlchan.net/*"
];
const manif = {

BIN
chrome/_metadata/generated_indexed_rulesets/_ruleset1

Binary file not shown.

2
chrome/b4k-csp.json

@ -12,7 +12,7 @@
},
"condition": {
"requestDomains": [
"arch.b4k.co", "desuarchive.org", "boards.4chan.org", "boards.4channel.org"
"arch.b4k.co", "desuarchive.org", "boards.4chan.org", "boards.4channel.org", "kohlchan.net"
],
"resourceTypes": [
"main_frame"

100
chrome/dist/main.js

@ -73,7 +73,7 @@
var define_BUILD_VERSION_default;
var init_define_BUILD_VERSION = __esm({
"<define:BUILD_VERSION>"() {
define_BUILD_VERSION_default = [0, 324];
define_BUILD_VERSION_default = [0, 326];
}
});
@ -8574,7 +8574,8 @@
"b4k.co",
"fireden.net",
"thebarchive.com",
"archiveofsins.com"
"archiveofsins.com",
"kohlchan.net"
];
function supportedAltDomain(s) {
return altdomains.includes(s);
@ -20225,7 +20226,7 @@
init_define_BUILD_VERSION();
init_esbuild_inject();
function add_css13(target) {
append_styles(target, "svelte-120v8nn", ".clickable.svelte-120v8nn.svelte-120v8nn{cursor:pointer;float:right}.root.svelte-120v8nn>span.svelte-120v8nn{display:flex;gap:10px;border:1px solid;padding:10px;border-radius:5px;font-weight:bolder;color:white;min-width:45vw}.root.svelte-120v8nn.svelte-120v8nn{position:fixed;top:0;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;gap:10px}.error.svelte-120v8nn.svelte-120v8nn{background-color:crimson}.info.svelte-120v8nn.svelte-120v8nn{background-color:cornflowerblue}.warning.svelte-120v8nn.svelte-120v8nn{background-color:darkgoldenrod}.success.svelte-120v8nn.svelte-120v8nn{background-color:green}");
append_styles(target, "svelte-5t4r47", ".clickable.svelte-5t4r47.svelte-5t4r47{cursor:pointer;float:right}.root.svelte-5t4r47>span.svelte-5t4r47{display:flex;gap:10px;border:1px solid;padding:10px;border-radius:5px;font-weight:bolder;color:white;min-width:45vw}.root.svelte-5t4r47.svelte-5t4r47{position:fixed;top:0;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;gap:10px}.error.svelte-5t4r47.svelte-5t4r47{background-color:crimson}.info.svelte-5t4r47.svelte-5t4r47{background-color:cornflowerblue}.warning.svelte-5t4r47.svelte-5t4r47{background-color:darkgoldenrod}.success.svelte-5t4r47.svelte-5t4r47{background-color:green}");
}
function get_each_context6(ctx, list, i) {
const child_ctx = ctx.slice();
@ -20251,8 +20252,8 @@
t0 = text(t0_value);
span0 = element("span");
span0.textContent = "X";
attr(span0, "class", "clickable svelte-120v8nn");
attr(span1, "class", span1_class_value = null_to_empty(ctx[4].type) + " svelte-120v8nn");
attr(span0, "class", "clickable svelte-5t4r47");
attr(span1, "class", span1_class_value = null_to_empty(ctx[4].type) + " svelte-5t4r47");
this.first = span1;
},
m(target, anchor) {
@ -20268,7 +20269,7 @@
ctx = new_ctx;
if (dirty & 1 && t0_value !== (t0_value = ctx[4].content + ""))
set_data(t0, t0_value);
if (dirty & 1 && span1_class_value !== (span1_class_value = null_to_empty(ctx[4].type) + " svelte-120v8nn")) {
if (dirty & 1 && span1_class_value !== (span1_class_value = null_to_empty(ctx[4].type) + " svelte-5t4r47")) {
attr(span1, "class", span1_class_value);
}
},
@ -20297,7 +20298,7 @@
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].c();
}
attr(div, "class", "root svelte-120v8nn");
attr(div, "class", "root svelte-5t4r47");
},
m(target, anchor) {
insert(target, div, anchor);
@ -20431,9 +20432,35 @@
getCurrentBoard: V4chan.getCurrentBoard,
getCurrentThread: V4chan.getCurrentThread
};
var KChan = {
getFileThumbnail: (post) => post.querySelector("figure.uploadCell"),
getPost: (post) => post.querySelector(".innerPost"),
postsWithFiles: (h) => [...(h || document).querySelectorAll(".postCell:has(figure)")],
settingsHost: () => document.getElementById("navOptionsSpanThread"),
catalogControlHost: () => document.getElementById("divTools"),
getImageLink: async function* (post) {
yield post.querySelector('a.imgLink[target="_blank"]')?.href || "";
},
getFilename: (post) => {
const a = post.querySelector(".imgLink > img");
if (a && a.title)
return a.title;
return a?.textContent || "";
},
getMD5: (post) => "",
getThumbnailLink: (post) => post.querySelector(".imgLink > img")?.getAttribute("src") || "",
getInfoBox: (post) => post.querySelector("div.uploadDetails"),
getPostIdPrefix: () => "",
getTextBox: (post) => post.querySelector(".divMessage"),
getCurrentBoard: () => location.pathname.split("/")[1],
getCurrentThread: () => +location.pathname.split("/")[3]
};
var getQueryProcessor = (is4chanX) => {
if (["boards.4chan.org", "boards.4channel.org"].includes(location.host))
return is4chanX ? X4chan : V4chan;
if (location.host == "kohlchan.net") {
return KChan;
}
if (document.querySelector('meta[name="generator"]')?.getAttribute("content")?.startsWith("FoolFuuka"))
return FoolFuuka;
};
@ -21720,6 +21747,8 @@
}
}, 5e3, { trailing: true });
var shouldUseCache = () => {
if (location.host == "kohlchan.net")
return false;
if (cappState.isCatalog)
return false;
if (!csettings5)
@ -21872,7 +21901,7 @@
if (cappState.isCatalog)
op = +post.id.slice(2);
else
op = +location.pathname.match(/\/thread\/(.*)/)[1];
op = qp.getCurrentThread() || 0;
if (shouldUseCache()) {
res2 = await getEmbedsFromCache(qp.getCurrentBoard(), +qp.getCurrentThread(), post.id);
}
@ -22000,6 +22029,18 @@ Use the WebExtension version of PEE if you want to use b4k!`);
qr.files = dt.files;
});
}
if (!document.body) {
let bodyRes;
const bodyInit = new Promise((r) => bodyRes = r);
const mo2 = new MutationObserver((r) => {
if (document.body) {
mo2.disconnect();
bodyRes();
}
});
mo2.observe(document.documentElement, { childList: true, subtree: true });
await bodyInit;
}
if (!cappState.isCatalog) {
const mo = new MutationObserver((reco) => {
for (const rec of reco)
@ -22031,18 +22072,6 @@ Use the WebExtension version of PEE if you want to use b4k!`);
mo.observe(e, { childList: true, subtree: true });
});
}
if (!document.body) {
let bodyRes;
const bodyInit = new Promise((r) => bodyRes = r);
const mo2 = new MutationObserver((r) => {
if (document.body) {
mo2.disconnect();
bodyRes();
}
});
mo2.observe(document.documentElement, { childList: true, subtree: true });
await bodyInit;
}
document.addEventListener("QRDialogCreation", (e) => {
const a = document.createElement("span");
const po = new PostOptions_default({
@ -22059,7 +22088,12 @@ Use the WebExtension version of PEE if you want to use b4k!`);
}
};
const obs = new MutationObserver(somethingChanged);
if (!cappState.is4chanX) {
if (location.host == "kohlchan.net") {
target = e.detail;
a.style.display = "inline-block";
const filesinp = target.querySelector("#selectedDivQr");
filesinp.parentElement?.appendChild(a);
} else if (!cappState.is4chanX) {
target = e.detail;
a.style.display = "inline-block";
target.querySelector("input[type=submit]")?.insertAdjacentElement("beforebegin", a);
@ -22072,6 +22106,21 @@ Use the WebExtension version of PEE if you want to use b4k!`);
obs.observe(filesinp, { attributes: true });
}
}, { once: !cappState.is4chanX });
if (location.host == "kohlchan.net") {
setTimeout(() => {
const no = document.querySelector("#quick-reply");
console.log("emitting qrdialog");
document.dispatchEvent(new CustomEvent("QRDialogCreation", {
detail: no
}));
const s = document.createElement("script");
s.src = chrome.runtime.getURL("kohlchan-hooks.js");
s.onload = function() {
s.remove();
};
(document.head || document.documentElement).appendChild(s);
}, 1e3);
}
try {
cp = new CommandProcessor();
} catch {
@ -22080,7 +22129,7 @@ Use the WebExtension version of PEE if you want to use b4k!`);
else
fireNotification("error", "You may be using 4chanX\n\nGo to 4chanX's settings, Advanced > JS Whitelist and add 'blob:' without quotes to the list.");
}
if (!is4chanX && location.host.startsWith("boards.4chan")) {
if (!is4chanX && location.host.startsWith("boards.4chan") || location.host == "kohlchan.net") {
const notificationHost = document.createElement("span");
new NotificationsHandler_default({
target: notificationHost
@ -22182,9 +22231,12 @@ Use the WebExtension version of PEE if you want to use b4k!`);
});
document.addEventListener("ThreadUpdate", async (e) => {
const newPosts = e.detail.newPosts;
const qp2 = getQueryProcessor(cappState.is4chanX);
if (!qp2)
return;
for (const post of newPosts) {
const postContainer = document.getElementById("pc" + post.substring(post.indexOf(".") + 1));
const fn = qp.getFilename(postContainer);
const postContainer = document.getElementById(qp2.getPostIdPrefix() + post.substring(post.indexOf(".") + 1));
const fn = qp2.getFilename(postContainer);
if (fn) {
if (!processed.has(postContainer.id)) {
appState.update((v) => {

35
chrome/kohlchan-hooks.js

@ -0,0 +1,35 @@
const pc = postCommon;
const pcasf = pc.addSelectedFile.bind(pc);
let prevFile;
pc.addSelectedFile = (f, unk) => {
pcasf(f, unk);
// will only embed in the first file
const refresh = () => {
const currentFile = pc.selectedFiles[0];
if (prevFile != currentFile) {
prevFile = currentFile;
document.dispatchEvent(new CustomEvent("PEEFile", { detail: prevFile }));
}
};
refresh();
const rb = pc.selectedDiv.lastChild?.getElementsByClassName("removeButton")[0];
if (rb)
rb.addEventListener("click", refresh);
};
document.addEventListener("QRSetFile", (e) => {
if (pc.selectedFiles.length > 0)
pc.selectedFiles[0] = e.detail.file;
});
const taup = thread.addUnreadPost.bind(thread);
thread.addUnreadPost = (e) => {
taup(e);
document.dispatchEvent(new CustomEvent("ThreadUpdate", {
detail: {
newPosts: ['b.' + e.postId]
}
}));
}

5
chrome/manifest.json

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "PngExtraEmbedder",
"description": "Discover embedded files on 4chan and archives!",
"version": "0.324",
"version": "0.326",
"icons": {
"64": "1449696017588.png"
},
@ -44,7 +44,8 @@
"https://b4k.co/*",
"https://fireden.net/*",
"https://thebarchive.com/*",
"https://archiveofsins.com/*"
"https://archiveofsins.com/*",
"https://kohlchan.net/*"
],
"css": [],
"run_at": "document_start",

28
dist/main.js

@ -73,7 +73,7 @@
var define_BUILD_VERSION_default;
var init_define_BUILD_VERSION = __esm({
"<define:BUILD_VERSION>"() {
define_BUILD_VERSION_default = [0, 324];
define_BUILD_VERSION_default = [0, 326];
}
});
@ -20243,9 +20243,35 @@
getCurrentBoard: V4chan.getCurrentBoard,
getCurrentThread: V4chan.getCurrentThread
};
var KChan = {
getFileThumbnail: (post) => post.querySelector("figure.uploadCell"),
getPost: (post) => post.querySelector(".innerPost"),
postsWithFiles: (h) => [...(h || document).querySelectorAll(".postCell:has(figure)")],
settingsHost: () => document.getElementById("navOptionsSpanThread"),
catalogControlHost: () => document.getElementById("divTools"),
getImageLink: async function* (post) {
yield post.querySelector('a[target="_blank"]')?.getAttribute("href") || "";
},
getFilename: (post) => {
const a = post.querySelector(".imgLink > img");
if (a && a.title)
return a.title;
return a?.textContent || "";
},
getMD5: (post) => "",
getThumbnailLink: (post) => post.querySelector(".imgLink > img")?.getAttribute("src") || "",
getInfoBox: (post) => post.querySelector("div.uploadDetails"),
getPostIdPrefix: () => "",
getTextBox: (post) => post.querySelector(".divMessage"),
getCurrentBoard: () => location.pathname.split("/")[1],
getCurrentThread: () => +location.pathname.split("/")[3]
};
var getQueryProcessor = (is4chanX) => {
if (["boards.4chan.org", "boards.4channel.org"].includes(location.host))
return is4chanX ? X4chan : V4chan;
if (location.host == "kohlchan.net") {
return KChan;
}
if (document.querySelector('meta[name="generator"]')?.getAttribute("content")?.startsWith("FoolFuuka"))
return FoolFuuka;
};

2
main.d.ts

@ -142,7 +142,7 @@ declare const QR: any;
declare const BUILD_VERSION: [number, number];
declare const execution_mode: 'userscript' | 'chrome_api' | 'ff_api' | 'worker';
declare const isBackground: boolean;
declare const chrome: typeof browser;
declare const chrome: typeof browser & {declarativeNetRequest: any};
declare const _DOMParser: typeof DOMParser;
declare const manifest: 2 | 3;
declare function GM_addElement(parent: HTMLElement, tagname: string, attrs: Record<string, string>);

31
src/Components/NotificationsHandler.svelte

@ -1,25 +1,25 @@
<script lang="ts">
import type { fireNotification } from '../utils'
import type { fireNotification } from "../utils";
type t = Parameters<typeof fireNotification>
type t = Parameters<typeof fireNotification>;
type Notification = {
type: t[0]
content: t[1]
lifetime: t[2]
}
type: t[0];
content: t[1];
lifetime: t[2];
};
export let nots: (Notification & { id: number })[] = []
export let nots: (Notification & { id: number })[] = [];
const removeId = (id: number) => (nots = nots.filter((e) => e.id != id))
const removeId = (id: number) => (nots = nots.filter((e) => e.id != id));
let gid = 0
document.addEventListener('CreateNotification', <any>((
e: CustomEvent<Notification>,
let gid = 0;
document.addEventListener("CreateNotification", <any>((
e: CustomEvent<Notification>
) => {
const id = gid++
nots = [...nots, { ...e.detail, id }]
setTimeout(() => removeId(id), (e.detail.lifetime || 3) * 1000)
}))
const id = gid++;
nots = [...nots, { ...e.detail, id }];
setTimeout(() => removeId(id), (e.detail.lifetime || 3) * 1000);
}));
</script>
<div class="root">
@ -33,7 +33,6 @@
</div>
<style scoped>
.clickable {
cursor: pointer;
float: right;

72
src/main.ts

@ -201,6 +201,8 @@ const signalNewEmbeds = debounce(async () => {
}, 5000, { trailing: true });
const shouldUseCache = () => {
if (location.host == "kohlchan.net")
return false;
if (cappState.isCatalog)
return false;
if (!csettings)
@ -478,7 +480,7 @@ const processPost = async (post: HTMLDivElement) => {
if (cappState.isCatalog)
op = +post.id.slice(2);
else
op = +location.pathname.match(/\/thread\/(.*)/)![1];
op = qp.getCurrentThread() || 0;
if (shouldUseCache()) {
res2 = await getEmbedsFromCache(qp.getCurrentBoard(), +qp.getCurrentThread()!, post.id);
}
@ -753,6 +755,19 @@ const startup = async (is4chanX = true) => {
}
//await Promise.all([...document.querySelectorAll('.postContainer')].filter(e => e.textContent?.includes("191 KB")).map(e => processPost(e as any)));
if (!document.body) {
let bodyRes: any;
const bodyInit = new Promise(r => bodyRes = r);
const mo2 = new MutationObserver(r => {
if (document.body) {
mo2.disconnect();
bodyRes();
}
});
mo2.observe(document.documentElement, { childList: true, subtree: true });
await bodyInit;
}
// keep this to handle posts getting inlined
if (!cappState.isCatalog) {
const mo = new MutationObserver(reco => {
@ -788,20 +803,6 @@ const startup = async (is4chanX = true) => {
});
}
if (!document.body) {
let bodyRes: any;
const bodyInit = new Promise(r => bodyRes = r);
const mo2 = new MutationObserver(r => {
if (document.body) {
mo2.disconnect();
bodyRes();
}
});
mo2.observe(document.documentElement, { childList: true, subtree: true });
await bodyInit;
}
document.addEventListener('QRDialogCreation', <any>((e: CustomEvent<HTMLElement>) => {
const a = document.createElement('span');
@ -821,14 +822,22 @@ const startup = async (is4chanX = true) => {
}
};
const obs = new MutationObserver(somethingChanged);
if (!cappState.is4chanX) {
if (location.host == "kohlchan.net") {
// vanilla kohl, behaves a bit like a mix of the two
target = e.detail as HTMLDivElement;
a.style.display = "inline-block";
const filesinp = target.querySelector('#selectedDivQr') as HTMLInputElement;
filesinp.parentElement?.appendChild(a);
} else if (!cappState.is4chanX) {
// on vanilla 4chan, we need to hook the input element to know
// if the files to embed into were changed
target = e.detail;
a.style.display = "inline-block";
target.querySelector("input[type=submit]")?.insertAdjacentElement("beforebegin", a);
const filesinp = target.querySelector('#qrFile') as HTMLInputElement;
filesinp.addEventListener("change", somethingChanged);
}
else {
} else {
// with 4chan X, it's harder as they're more interactive
target = e.target as HTMLDivElement;
target.querySelector('#qr-filename-container')?.appendChild(a);
const filesinp = target.querySelector('#file-n-submit') as HTMLInputElement;
@ -837,6 +846,25 @@ const startup = async (is4chanX = true) => {
}), { once: !cappState!.is4chanX }); // 4chan's normal extension destroys the QR form everytime
if (location.host == "kohlchan.net") {
setTimeout(() => {
// do some kohlchan-specific hooks
const no = document.querySelector("#quick-reply");
console.log("emitting qrdialog");
document.dispatchEvent(new CustomEvent("QRDialogCreation", {
detail: no
}));
const s = document.createElement('script');
s.src = chrome.runtime.getURL('kohlchan-hooks.js');
s.onload = function () {
s.remove();
};
(document.head || document.documentElement).appendChild(s);
}, 1000);
}
try {
cp = new CommandProcessor();
} catch {
@ -847,7 +875,7 @@ const startup = async (is4chanX = true) => {
//return;
}
if (!is4chanX && location.host.startsWith('boards.4chan')) {
if ((!is4chanX && location.host.startsWith('boards.4chan')) || location.host == "kohlchan.net") {
const notificationHost = document.createElement('span');
new NotificationsHandler({
target: notificationHost
@ -968,8 +996,11 @@ document.addEventListener('4chanThreadUpdated', ((e: CustomEvent<{ count: number
document.addEventListener('ThreadUpdate', <any>(async (e: CustomEvent<any>) => {
const newPosts = e.detail.newPosts;
const qp = getQueryProcessor(cappState.is4chanX);
if (!qp)
return;
for (const post of newPosts) {
const postContainer = document.getElementById("pc" + post.substring(post.indexOf(".") + 1)) as HTMLDivElement;
const postContainer = document.getElementById(qp.getPostIdPrefix() + post.substring(post.indexOf(".") + 1)) as HTMLDivElement;
const fn = qp.getFilename(postContainer);
if (fn) {
if (!processed.has(postContainer.id)) {
@ -1095,3 +1126,4 @@ function processAttachments(post: HTMLDivElement, ress: [EmbeddedFile, boolean][
post.setAttribute('data-processed', "true");
}

1
src/platform.ts

@ -123,6 +123,7 @@ const altdomains = [
"fireden.net",
"thebarchive.com",
"archiveofsins.com",
"kohlchan.net"
];
export function supportedAltDomain(s: string) {

39
src/websites/index.ts

@ -6,7 +6,7 @@ export type QueryProcessor = {
postsWithFiles: (host?: HTMLElement) => HTMLElement[];
settingsHost: () => HTMLSpanElement;
catalogControlHost: () => HTMLDivElement;
getImageLink:(post: HTMLElement) => AsyncGenerator<string, void, void>;
getImageLink: (post: HTMLElement) => AsyncGenerator<string, void, void>;
getThumbnailLink: (post: HTMLElement) => string;
getFilename: (post: HTMLElement) => string;
getMD5: (post: HTMLElement) => string;
@ -23,10 +23,10 @@ export const V4chan: QueryProcessor = {
postsWithFiles: (h) => [...(h || document).querySelectorAll('.file')].map(e => e.closest('.postContainer')) as any,
settingsHost: () => document.getElementById("navtopright") as any,
catalogControlHost: () => document.getElementById("settings") as HTMLDivElement,
getImageLink: async function *(post: HTMLElement) {
getImageLink: async function* (post: HTMLElement) {
yield post.querySelector('a[target="_blank"]')?.getAttribute('href') || '';
},
//(post: HTMLElement) => post.querySelector('a[target="_blank"]')?.getAttribute('href') || '',
getFilename: (post: HTMLElement) => {
const a = post.querySelector('a[target="_blank"]') as (HTMLAnchorElement | null);
@ -49,7 +49,7 @@ export const X4chan: QueryProcessor = {
postsWithFiles: (h) => [...(h || document).querySelectorAll('.postContainer:not([class*="noFile"])')] as HTMLElement[],
settingsHost: () => document.getElementById("shortcuts") as any,
catalogControlHost: () => document.getElementById("index-options") as HTMLDivElement,
getImageLink: async function *(post: HTMLElement) {
getImageLink: async function* (post: HTMLElement) {
yield post.querySelector('a[target="_blank"]')?.getAttribute('href') || '';
},
getFilename: (post: HTMLElement) => {
@ -72,7 +72,7 @@ export const FoolFuuka: QueryProcessor = {
postsWithFiles: (h) => [...(h || document).querySelectorAll('article[class*="thread"], article[class*="has_image"]')] as HTMLElement[],
settingsHost: () => document.querySelector(".letters") as any,
catalogControlHost: () => document.getElementById("index-options") as HTMLDivElement,
getImageLink: async function *(post: HTMLElement) {
getImageLink: async function* (post: HTMLElement) {
if (location.host == "arch.b4k.co") { //get hecked
return;
}
@ -97,9 +97,38 @@ export const FoolFuuka: QueryProcessor = {
getCurrentThread: V4chan.getCurrentThread,
};
export const KChan: QueryProcessor = {
getFileThumbnail: post => post.querySelector('figure.uploadCell')!,
getPost: (post) => post.querySelector('.innerPost')!,
postsWithFiles: (h) => [...(h || document).querySelectorAll('.postCell:has(figure)')],
settingsHost: () => document.getElementById("navOptionsSpanThread") as any,
catalogControlHost: () => document.getElementById("divTools") as HTMLDivElement,
getImageLink: async function* (post: HTMLElement) {
yield (post.querySelector('a.imgLink[target="_blank"]') as HTMLAnchorElement)?.href || '';
},
//(post: HTMLElement) => post.querySelector('a[target="_blank"]')?.getAttribute('href') || '',
getFilename: (post: HTMLElement) => {
const a = post.querySelector('.imgLink > img') as (HTMLImageElement | null);
if (a && a.title)
return a.title;
return a?.textContent || '';
},
getMD5: (post: HTMLElement) => '', // kohlchan doesn't compute md5s, filename look like a longer hash
getThumbnailLink: (post: HTMLElement) => post.querySelector(".imgLink > img")?.getAttribute("src") || '',
getInfoBox: post => post.querySelector("div.uploadDetails")!,
getPostIdPrefix: () => '',
getTextBox: (post) => post.querySelector('.divMessage')!,
getCurrentBoard: () => location.pathname.split('/')[1],
getCurrentThread: () => +location.pathname.split('/')[3]
};
export const getQueryProcessor = (is4chanX: boolean) => {
if (['boards.4chan.org', 'boards.4channel.org'].includes(location.host))
return is4chanX ? X4chan : V4chan;
if (location.host == 'kohlchan.net') {
return KChan;
}
if (document.querySelector('meta[name="generator"]')?.getAttribute("content")?.startsWith("FoolFuuka"))
return FoolFuuka;
};

Loading…
Cancel
Save