< script lang = "ts" >
import { hasContext , onDestroy } from "svelte";
import { slide } from "svelte/transition";
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 { settings , appState } from "../stores";
import { filehosts } from "../filehosts";
import HydrusSearch from "./HydrusSearch.svelte";
import { request , ifetch } from "../platform";
import { writable } from "svelte/store";
let newbooru: Partial< Omit < Booru , " quirks " > & { view : string } > = {} ;
let dial: Dialog;
export let rev: number;
let news: {
title: string;
content: string;
}[] = [];
const updateNews = async () => {
const res = await fetch("https://shoujo.coom.tech/news");
news = await res.json();
};
function appendBooru() {
if (execution_mode != "userscript") {
request(newbooru.domain!);
}
$settings.rsources = [...$settings.rsources, newbooru as any];
dial.toggle();
newbooru = {} ;
}
let visible = false;
let penisEvent = () => {
visible = !visible;
};
document.addEventListener("penis", penisEvent);
console.log("app loaded");
function removeTag(t: string) {
$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 setFromClip = async () => {
try {
const content = await navigator.clipboard.readText();
$settings.rsources = JSON.parse(content);
} catch {
alert("How can you fail so badly? pathetic...");
}
}
const boardname = location.pathname.match(/\/([^/]*)\//)![1];
let updating = false;
let threads: {
id: number;
cnt: number;
pees: number;
eyes: number;
other: number;
}[] = [];
async function updateThreads() {
updating = true;
let params = "";
if ($settings.phash) {
params = "?mdist=" + $settings.mdist;
}
let res = await ifetch(
"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;
}
onDestroy(() => {
document.removeEventListener("penis", penisEvent);
});
let cached = writable< boolean > (false);
settings.subscribe((val) => {
cached.set(
typeof val.cache == "boolean" ? val.cache : location.host.includes("b4k")
);
});
cached.subscribe((v) => {
$settings.cache = v;
});
< / script >
{ #if $appState . processing != $appState . processed }
< div class = "loading" >
PEE Loading... ({ $appState . processed } / { $appState . processing } )
< / div >
{ /if }
{ #if visible }
< div class = "backpanel" transition:slide >
< div class = "content" >
< h1 > PEE Settings 0.{ rev } </ h1 >
< h4 > < a href = "https://2chen.moe/tech/" > Join us on 2chen!< / a > • < a href = "https://git.coom.tech/araragi/JKCS/src/branch/master/README.md" > Install JKCS!< / a > < / h4 >
< hr / >
< Tabs >
< TabList >
< Tab > General< / Tab >
< Tab > External< / Tab >
< Tab > File Host< / Tab >
< Tab on:select = {() => updateThreads ()} > Thread Watcher </ Tab >
< Tab on:select = {() => updateNews ()} > Reminder</Tab >
< Tab > Advanced< / Tab >
{ #if $appState . akValid }
< Tab > Hydrus< / Tab >
{ /if }
< / TabList >
< TabPanel >
< label >
< input type = "checkbox" bind:checked = { $settings . notcata } / >
Disable loading on catalog
< / label >
< label >
< input type = "checkbox" bind:checked = { $cached } / >
Try to load embeds from server cache
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . dvc } / >
Display view counts
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . vercheck } / >
Check for new versions at startup.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . xpi } / >
Autoexpand Images on opening.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . xpv } / >
Autoexpand Videos on opening.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . loop } / >
Loop media content.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . dh } / >
Disable hover preview.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . eye } / >
Hide embedded content behind an eye.
< / label >
{ #if $settings . eye }
< label >
< input type = "checkbox" bind:checked = { $settings . ho } / >
Hide original content when hidden content is visible.
< / label >
{ /if }
< label >
< input type = "checkbox" bind:checked = { $settings . pre } / >
Preload external files.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . prev } / >
Preload external files when they are in view.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . hotlink } / >
Hotlink content.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . ca } / >
Control audio on videos with mouse wheel.
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . sh } / >
Show Minimap
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . ep } / >
<!-- svelte - ignore a11y - missing - attribute -->
Disable embedded file preloading< a
title="You might still want to enable 'preload external files'"
>?< /a
>
< / label >
< label >
< input type = "checkbox" bind:checked = { $settings . hyd } / >
<!-- svelte - ignore a11y - missing - attribute -->
Enable Hydrus Integration
< / label >
{ #if $settings . hyd }
{ #if $appState . herror }
< span class = "error" > { $appState . herror } </ span >
{ /if }
< label >
Hydrus Access Key
<!-- svelte - ignore a11y - missing - attribute -->
< a
title="Only requires Search Files permission. See Hydrus docs on where to set this up."
>?< /a
>
< input type = "text" bind:value = { $settings . ak } / >
< / label >
{ #if $appState . akValid }
< label >
Auto-embed < input
style="width: 5ch;"
type="number"
bind:value={ $settings . auto_embed }
/>
random files
<!-- svelte - ignore a11y - missing - attribute -->
< / label >
< label >
<!-- svelte - ignore a11y - missing - attribute -->
< input
placeholder="Restrict to these tags (space to separate tags, _ to separate words)"
type="text"
bind:value={ $settings . auto_tags }
/>
< / label >
{ /if }
{ /if }
< / TabPanel >
< TabPanel >
< label >
< input type = "checkbox" bind:checked = { $settings . te } / >
Disable third-eye.
< / label >
{ #if ! $settings . te }
< label >
< input type = "checkbox" bind:checked = { $settings . phash } / >
Enable perceptual hash-based filtering
< / label >
{ #if $settings . phash }
< label >
< input type = "number" bind:value = { $settings . mdist } / >
Minimum distance required (5 recommended)
<!-- svelte - ignore a11y - missing - attribute -->
< a
title="Higher will filter more potentially different images, lower will let more identical images through"
>?< /a
>
< / label >
{ /if }
< h3 > Booru sources< / h3 >
{ #if $settings . rsources . length == 0 }
< p > Don't know what to put here? Ask some anons ;)< / p >
{ /if }
< div class = "tagcont" >
{ #each $settings . rsources as source , i }
< Tag
tag={ source . name }
on:remove={() => removeBooru ( source . domain )}
on:toggle={() => toggleBooru ( source . domain )}
toggleable={ true }
toggled={ ! $settings . rsources . find (
(e) => e.domain == source.domain
)?.disabled}
/>
{ /each }
< / div >
< button
on:click={( ev ) => {
dial.setPos([ev.clientX, ev.clientY]);
dial.toggle();
}}>Add a source< /button
>
{ #if $settings . rsources . length == 0 }
< button
on:click={ setFromClip } >Import Bulk from clipboard< /button
>
{ /if }
< Dialog bind:this = { dial } >
< div class = "form" >
< label >
Name
< input
type="text"
placeholder="Safebooru"
bind:value={ newbooru . name }
/>
< / label >
< label >
Domain
< input
type="text"
placeholder="safebooru.com"
bind:value={ newbooru . domain }
/>
< / label >
< label >
API Endpoint
< input
type="text"
placeholder="/post.json?tags=md5:"
bind:value={ newbooru . endpoint }
/>
< / label >
< label >
Post page prefix (for sources)
< input
type="text"
placeholder="https://safebooru.com/post/show/"
bind:value={ newbooru . view }
/>
< / label >
< button on:click = { appendBooru } > Add</button >
< / div >
< / Dialog >
< hr / >
< h3 > Blacklisted tags< / h3 >
< div class = "tagcont" >
{ #each $settings . blacklist as tag , i }
< Tag { tag } on:toggle = {() => removeTag ( tag )} / >
{ /each }
< / div >
< input
placeholder="Press enter after typing your tag"
on:keydown={( ev ) => {
if (ev.key == "Enter") {
$settings.blacklist = [
...$settings.blacklist,
ev.currentTarget.value,
];
ev.currentTarget.value = "";
}
}}
/>
{ /if }
< / TabPanel >
< TabPanel >
< p > Host to use when uploading files (Only permanent hosts)< / p >
< select bind:value = { $settings . fhost } >
{ #each filehosts as fh , i }
< option value = { i } > { fh . domain } </option >
{ /each }
< / select >
< label >
Maximum number of embedded links to display
< 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 >
{ #if ! updating }
< div class = "bepis" >
< p > Format is (embedded links / link in filename)< / p >
{ #each threads as thread }
< div class = "mbepis" >
< a
href={ "https://boards.4chan.org/" +
boardname +
"/thread/" +
thread.id}>>>{ thread . id } < /a
>
({ thread . pees } / { thread . eyes + thread . other } )
< / div >
{ /each }
< / div >
{ : else }
< p > Loading...< / p >
{ /if }
< / TabPanel >
< TabPanel >
< p >
Reminder to report issues to < a
href="https://git.coom.tech/coomdev/PEE">the main repo< /a
>
< / p >
< div class = "newsbox" >
{ #if news . length == 0 }
< p > There are no news yet.< / p >
{ : else }
{ #each news as anew }
< h3 > { anew . title } </ h3 >
< p > { anew . content } </ p >
{ /each }
{ /if }
< / div >
< / TabPanel >
< TabPanel >
< label >
PNG Embedding method
< select bind:value = { $settings . pmeth } >
{ #each [ 0 , 1 , 2 , 3 , 4 , 5 ] as m }
< option value = { m } > Method { m } </ option >
{ /each }
< / select >
< / label >
< / TabPanel >
{ #if $appState . akValid }
< TabPanel >
< HydrusSearch / >
< / TabPanel >
{ /if }
< / Tabs >
< / div >
< / div >
{ /if }
< style scoped >
.bepis {
max-height: 260px;
overflow-y: auto;
}
.tagcont {
display: flex;
gap: 5px;
margin-bottom: 10px;
flex-wrap: wrap;
}
label > input[type="text"],
label > input[type="number"] {
width: 95%;
}
.content {
display: flex;
flex-direction: column;
}
.error {
color: red;
}
hr {
width: 100%;
}
h1 {
text-align: center;
margin-bottom: 0;
}
h4 {
text-align: center;
margin: 0;
}
.form {
display: flex;
flex-direction: column;
gap: 20px;
position: absolute;
padding: 15px;
border: 1px solid white;
background-color: black;
border-radius: 10px;
}
.form > label {
display: flex;
flex-direction: column;
gap: 10px;
}
.newsbox {
max-height: 300px;
overflow-y: scroll;
}
.backpanel {
position: absolute;
right: 32px;
padding: 10px;
width: 15%;
top: 32px;
border: 1px solid;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.8);
pointer-events: all;
backdrop-filter: blur(9px);
max-height: 80vh;
min-width: 321px;
}
.loading {
position: absolute;
right: 32px;
padding: 10px;
bottom: 32px;
border: 1px solid;
border-radius: 10px;
background-color: rgba(0, 0, 0, 0.8);
pointer-events: all;
backdrop-filter: blur(9px);
}
< / style >