Browse Source

Add opt-in telemetry

pull/46/head
coomdev 2 years ago
parent
commit
216e2e1ce6
  1. 16
      LICENSE
  2. 2
      main.meta.js
  3. 6322
      main.user.js
  4. 24
      package-lock.json
  5. 2
      package.json
  6. 128
      src/Components/App.svelte
  7. 130
      src/main.ts
  8. 1
      src/stores.ts

16
LICENSE

@ -0,0 +1,16 @@
MIT No Attribution
Copyright 2022 You
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2
main.meta.js

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

6322
main.user.js

File diff suppressed because it is too large

24
package-lock.json

@ -16,12 +16,14 @@
"file-type": "^17.0.2",
"image-hash": "^5.0.1",
"jpeg-js": "^0.4.3",
"lodash": "^4.17.21",
"png-js": "^1.0.0",
"readable-stream": "^3.6.0",
"ts-ebml": "^2.0.2"
},
"devDependencies": {
"@tsconfig/svelte": "^3.0.0",
"@types/lodash": "^4.14.181",
"@types/tampermonkey": "^4.0.5",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"esbuild": "^0.14.7",
@ -631,6 +633,12 @@
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.181",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz",
"integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==",
"dev": true
},
"node_modules/@types/minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
@ -3113,6 +3121,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@ -5191,6 +5204,12 @@
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.181",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz",
"integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==",
"dev": true
},
"@types/minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
@ -6956,6 +6975,11 @@
"p-locate": "^5.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",

2
package.json

@ -21,12 +21,14 @@
"file-type": "^17.0.2",
"image-hash": "^5.0.1",
"jpeg-js": "^0.4.3",
"lodash": "^4.17.21",
"png-js": "^1.0.0",
"readable-stream": "^3.6.0",
"ts-ebml": "^2.0.2"
},
"devDependencies": {
"@tsconfig/svelte": "^3.0.0",
"@types/lodash": "^4.14.181",
"@types/tampermonkey": "^4.0.5",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"esbuild": "^0.14.7",

128
src/Components/App.svelte

@ -1,55 +1,71 @@
<script lang="ts">
import { hasContext, onDestroy } from 'svelte'
import Dialog from './Dialog.svelte'
import { hasContext, onDestroy } from "svelte";
import Dialog from "./Dialog.svelte";
import Tag from './Tag.svelte'
import type { Booru } from '../thirdeye'
import Tabs from './Tabs.svelte'
import TabList from './TabList.svelte'
import Tab from './Tab.svelte'
import TabPanel from './TabPanel.svelte'
import Tag from "./Tag.svelte";
import type { Booru } from "../thirdeye";
import Tabs from "./Tabs.svelte";
import TabList from "./TabList.svelte";
import Tab from "./Tab.svelte";
import TabPanel from "./TabPanel.svelte";
import { settings } from '../stores'
import { filehosts } from '../filehosts'
import { settings } from "../stores";
import { filehosts } from "../filehosts";
import { appState } from "../../dist/stores";
let newbooru: Partial<Omit<Booru, 'quirks'> & { view: string }> = {}
let dial: Dialog
let newbooru: Partial<Omit<Booru, "quirks"> & { view: string }> = {};
let dial: Dialog;
function appendBooru() {
$settings.rsources = [...$settings.rsources, newbooru as any]
dial.toggle()
newbooru = {}
$settings.rsources = [...$settings.rsources, newbooru as any];
dial.toggle();
newbooru = {};
}
let visible = false
let visible = false;
let penisEvent = () => {
console.log('bepis')
visible = !visible
}
document.addEventListener('penis', penisEvent)
console.log('app loaded')
visible = !visible;
};
document.addEventListener("penis", penisEvent);
console.log("app loaded");
function removeTag(t: string) {
$settings.blacklist = $settings.blacklist.filter((e: any) => e != t)
$settings.blacklist = $settings.blacklist.filter((e: any) => e != t);
}
function removeBooru(t: string) {
const idx = $settings.rsources.findIndex((e) => e.domain == t)
const rep = prompt("You DO know what you're doing, right? (type 'y')")
if (!rep || rep != 'y') return
if (idx >= 0) $settings.rsources.splice(idx, 1)
$settings.rsources = $settings.rsources
const idx = $settings.rsources.findIndex((e) => e.domain == t);
const rep = prompt("You DO know what you're doing, right? (type 'y')");
if (!rep || rep != "y") return;
if (idx >= 0) $settings.rsources.splice(idx, 1);
$settings.rsources = $settings.rsources;
}
const boardname = location.pathname.match(/\/([^/]*)\//)![1];
let updating = false;
let threads: { id: number; cnt: number }[] = [];
async function updateThreads() {
updating = true;
let params = "";
if ($settings.phash) {
params = "?mdist" + $settings.mdist;
}
let res = await fetch(
"https://shoujo.coom.tech/listing/" + boardname + params
);
threads = await res.json();
updating = false;
}
function toggleBooru(t: string) {
const elem = $settings.rsources.find((e) => e.domain == t)
if (elem) elem.disabled = !elem.disabled
$settings.rsources = $settings.rsources
const elem = $settings.rsources.find((e) => e.domain == t);
if (elem) elem.disabled = !elem.disabled;
$settings.rsources = $settings.rsources;
}
onDestroy(() => {
document.removeEventListener('penis', penisEvent)
})
document.removeEventListener("penis", penisEvent);
});
</script>
<div class="backpanel" class:enabled={visible} class:disabled={!visible}>
@ -61,6 +77,7 @@
<Tab>General</Tab>
<Tab>External</Tab>
<Tab>File Host</Tab>
<Tab>Thread Watcher</Tab>
</TabList>
<TabPanel>
<label>
@ -152,15 +169,15 @@
on:toggle={() => toggleBooru(source.domain)}
toggleable={true}
toggled={!$settings.rsources.find(
(e) => e.domain == source.domain,
(e) => e.domain == source.domain
)?.disabled}
/>
{/each}
</div>
<button
on:click={(ev) => {
dial.setPos([ev.clientX, ev.clientY])
dial.toggle()
dial.setPos([ev.clientX, ev.clientY]);
dial.toggle();
}}>Add a source</button
>
<Dialog bind:this={dial}>
@ -211,12 +228,12 @@
<input
placeholder="Press enter after typing your tag"
on:keydown={(ev) => {
if (ev.key == 'Enter') {
if (ev.key == "Enter") {
$settings.blacklist = [
...$settings.blacklist,
ev.currentTarget.value,
]
ev.currentTarget.value = ''
];
ev.currentTarget.value = "";
}
}}
/>
@ -234,11 +251,46 @@
<input type="number" bind:value={$settings.maxe} />
</label>
</TabPanel>
<TabPanel>
<label>
<input type="checkbox" bind:checked={$settings.tm} />
<!-- svelte-ignore a11y-missing-attribute -->
Contribute to help keep this list up to date. [<a
title="This will make PEE automatically send the
post number of posts you find with embedded content">?</a
>]
</label>
<button on:click={updateThreads} disabled={updating}>Refresh</button>
{#if !updating}
<div class="bepis">
{#each threads as thread}
<div class="mbepis">
<a
href={"https://boards.4chan.org/" +
boardname +
"/thread/" +
thread.id}>>>{thread.id}</a
>
({thread.cnt} embeds)
</div>
{/each}
</div>
{:else}
<p>Loading...</p>
{/if}
</TabPanel>
</Tabs>
</div>
</div>
<style scoped>
.bepis {
max-height: 260px;
overflow-y: auto;
}
.tagcont {
display: flex;
gap: 5px;

130
src/main.ts

@ -1,5 +1,6 @@
import { Buffer } from "buffer";
import { appState, settings } from "./stores";
import _ from 'lodash';
import globalCss from './global.css';
import pngv3 from "./pngv3";
@ -106,6 +107,33 @@ const processImage = async (src: string, fn: string, hex: string, prevurl: strin
const textToElement = <T = HTMLElement>(s: string) =>
document.createRange().createContextualFragment(s).children[0] as any as T;
let pendingPosts: { id: number, op: number }[] = [];
const signalNewEmbeds = _.debounce(async () => {
// ensure user explicitely enabled telemetry
if (!csettings.tm)
return;
try {
const boardname = location.pathname.match(/\/([^/]*)\//)![1];
// restructure to minimize redundancy
const reshaped = Object.fromEntries([...new Set(pendingPosts.map(e => e.op))].map(e => [e, pendingPosts.filter(p => p.op == e).map(e => e.id)]));
console.log(reshaped);
const res = await fetch("https://shoujo.coom.tech/listing/" + boardname, {
method: "POST",
body: JSON.stringify(reshaped),
headers: {
'content-type': 'application/json'
}
});
await res.json();
pendingPosts = [];
} catch (e) {
// silently fail
console.error(e);
}
}, 5000, { trailing: true });
const processPost = async (post: HTMLDivElement) => {
const origlink = qp.getImageLink(post);
if (!origlink)
@ -115,6 +143,17 @@ const processPost = async (post: HTMLDivElement) => {
return;
let res2 = await processImage(origlink, qp.getFilename(post), qp.getMD5(post), thumbLink,
() => {
if (csettings.tm) {
// dont report results from archive, only live threads
if (['boards.4chan.org', 'boards.4channel.org'].includes(location.host)) {
if (!cappState.isCatalog) { // only save from within threads
// we must be in a thread, thus the following is valid
const op = +location.pathname.match(/\/thread\/(.*)/)![1];
pendingPosts.push({id: +(post.id.match(/([0-9]+)/)![1]), op});
signalNewEmbeds(); // let it run async
}
}
}
post.querySelector('.post')?.classList.add("embedfound");
});
res2 = res2?.filter(e => e);
@ -149,9 +188,12 @@ function copyTextToClipboard(text: string) {
}
const scrapeBoard = async (self: HTMLButtonElement) => {
if (csettings.tm) {
fireNotification("success", "Scrapping board with telemetry on! Thank you for your service, selfless stranger ;_;7");
}
self.disabled = true;
self.textContent = "Searching...";
const boardname = location.pathname.match(/\/(.*)\//)![1];
const boardname = location.pathname.match(/\/([^/]*)\//)![1];
const res = await ifetch(`https://a.4cdn.org/${boardname}/threads.json`);
const pages = await res.json() as Page[];
type Page = { threads: Thread[] }
@ -175,7 +217,7 @@ const scrapeBoard = async (self: HTMLButtonElement) => {
const filenames = threads
.reduce((a, b) => [...a, ...b.posts.filter(p => p.ext)
.map(p => p as PostWithFile)], [] as PostWithFile[]).filter(p => p.ext != '.webm' && p.ext != '.gif')
.map(p => [p.resto || p.no, `https://i.4cdn.org/${boardname}/${p.tim}${p.ext}`, p.md5, p.filename + p.ext,] as [number, string, string, string]);
.map(p => [p.resto || p.no, `https://i.4cdn.org/${boardname}/${p.tim}${p.ext}`, p.md5, p.filename + p.ext, p.no] as [number, string, string, string, number]);
console.log(filenames);
fireNotification("info", "Analyzing images...");
@ -226,6 +268,11 @@ const scrapeBoard = async (self: HTMLButtonElement) => {
processed++;
if (res.some(e => e)) {
hasEmbed.push(post);
// dont report results from archive, only live threads
if (['boards.4chan.org', 'boards.4channel.org'].includes(location.host)) {
pendingPosts.push({ id: post[4], op: post[0] });
signalNewEmbeds(); // let it run async
}
}
} catch (e) {
console.log(e);
@ -528,82 +575,3 @@ function processAttachments(post: HTMLDivElement, ress: [EmbeddedFile, boolean][
post.setAttribute('data-processed', "true");
}
//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;
// container.onchange = async () => {
// const api = lolisafe('zz.ht', 'z.zz.fo');
// const file = container.files![0];
// const blo = new Blob([file], {type: 'application/octet-stream'});
// const link = await api.uploadFile(blo);
// console.log(link);
// };
// };
//}
//
////if ((window as any)['pagemode']) {
// onload = () => {
// const extraction = document.getElementById("extraction") as HTMLInputElement;
// extraction.onchange = async () => {
// const pee = await convertToPng(extraction.files![0]);
// const result = document.getElementById("result") as HTMLImageElement;
// const data = await inject_data(new File([pee!], 'image.png', { type: "image/png" }), Buffer.from("coom"));
// result.src = URL.createObjectURL(new Blob([data]));
// };
// };
//}
// 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;
// }
// };
// };
// }
// if ((window as any)['pagemode']) {
// onload = () => {
// document.body.innerHTML = '';
// new App({
// target: document.body
// });
// setTimeout(() => {
// document.dispatchEvent(new CustomEvent("penis"));
// }, 30);
// };
// }

1
src/stores.ts

@ -22,6 +22,7 @@ export const settings = writable(localLoad('settingsv2', {
prev: false,
sh: false,
ep: false,
tm: false,
expte: false,
mdist: -1,
phash: false,

Loading…
Cancel
Save