Browse Source

Add a way to add custom boorus

pull/46/head 0.104
coomdev 2 years ago
parent
commit
671fd5b8a2
  1. 2
      main.meta.js
  2. 1168
      main.user.js
  3. 170
      src/App.svelte
  4. 26
      src/Dialog.svelte
  5. 54
      src/Tag.svelte
  6. 55
      src/stores.ts
  7. 115
      src/thirdeye.ts

2
main.meta.js

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

1168
main.user.js

File diff suppressed because it is too large

170
src/App.svelte

@ -1,7 +1,19 @@
<script lang="ts">
import { hasContext, onDestroy } from 'svelte'
import Dialog from './Dialog.svelte';
import { settings } from './stores'
import Tag from './Tag.svelte'
import type { Booru } from './thirdeye';
let newbooru: Partial<Omit<Booru, 'quirks'> & {view: string}> = {};
let dial: Dialog;
function appendBooru() {
$settings.rsources = [...$settings.rsources, newbooru as any];
dial.toggle();
newbooru = {}
}
let visible = false
let penisEvent = () => {
@ -11,39 +23,23 @@
document.addEventListener('penis', penisEvent)
console.log('app loaded')
let sources = [
'gelbooru.com',
'yande.re',
'capi-v2.sankakucomplex.com',
'api.rule34.xxx',
'danbooru.donmai.us',
'lolibooru.moe',
'booru.allthefallen.moe'
]
let selectobj: HTMLSelectElement
let selectobj2: HTMLSelectElement
function toggleSelection() {
for (let i = 0; i < selectobj.selectedOptions.length; ++i) {
let item = selectobj.selectedOptions.item(i)
if (!item) continue
if ($settings.sources.includes(item.value))
$settings.sources = $settings.sources.filter(
(e: string) => e != item!.value,
)
else $settings.sources = [...$settings.sources, item.value]
}
function removeTag(t: string) {
$settings.blacklist = $settings.blacklist.filter((e: any) => e != t)
}
function removeSelection() {
let s = new Set<string>();
for (let i = 0; i < selectobj2.selectedOptions.length; ++i) {
let obj = selectobj2.selectedOptions.item(i)
if (!obj) continue
s.add(obj.value)
$settings.blacklist = $settings.blacklist.filter((e: any) => !s.has(e))
}
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
}
function toggleBooru(t: string) {
const elem = $settings.rsources.find(e => e.domain == t)
if (elem)
elem.disabled = !elem.disabled;
$settings.rsources = $settings.rsources
}
onDestroy(() => {
@ -94,7 +90,9 @@
<label>
<input type="checkbox" bind:checked={$settings.ep} />
<!-- svelte-ignore a11y-missing-attribute -->
Turn off embedded file preloading<a title="You might still want to enable 'preload external files'">?</a>
Turn off embedded file preloading<a
title="You might still want to enable 'preload external files'">?</a
>
</label>
<label>
<input type="checkbox" bind:checked={$settings.te} />
@ -102,26 +100,71 @@
</label>
{#if !$settings.te}
<h3>Booru sources</h3>
<select multiple bind:this={selectobj} size={sources.length}>
{#each sources as source, i}
<option
class="sourcedi"
class:sourceen={$settings.sources.includes(source)}
value={source}>{source}</option
>
<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}
</select>
<button on:click={toggleSelection}>Toggle sources</button>
</div>
<button
on:click={ev => {
dial.setPos([ev.clientX, ev.clientY])
dial.toggle()
}}>Add a source</button
>
<Dialog bind:this={dial}>
<div class="form">
<label>
Name
<input
type="text"
placeholder="Gelbooru"
bind:value={newbooru.name}
/>
</label>
<label>
Domain
<input
type="text"
placeholder="gelbooru.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://yande.re/post/show/"
bind:value={newbooru.view}
/>
</label>
<button on:click={appendBooru}>Add</button>
</div>
</Dialog>
<hr />
<h3>Blacklisted tags</h3>
<select multiple bind:this={selectobj2} size={sources.length}>
{#each $settings.blacklist as source, i}
<option value={source}>{source}</option>
<div class="tagcont">
{#each $settings.blacklist as tag, i}
<Tag {tag} on:toggle={() => removeTag(tag)} />
{/each}
</select>
<button on:click={removeSelection}>Remove</button>
</div>
<input
placeholder="Press enter after typing your tag"
placeholder="Press enter after typing your tag"
on:keydown={ev => {
if (ev.key == 'Enter') {
$settings.blacklist = [
@ -137,6 +180,13 @@
</div>
<style scoped>
.tagcont {
display: flex;
gap: 5px;
margin-bottom: 10px;
flex-wrap: wrap;
}
select {
font-size: 1.2em;
}
@ -145,14 +195,6 @@
display: block;
}
.sourcedi {
border-right: 10px solid lightcoral;
}
.sourceen {
border-right: 10px solid lightgreen;
}
.disabled {
display: none;
}
@ -169,6 +211,22 @@
h1 {
text-align: center;
}
.form {
display: flex;
flex-direction: column;
gap: 20px;
position: absolute;
padding: 15px;
border: 1px solid white;
background-color: inherit;
border-radius: 10px;
}
.form > label {
display: flex;
flex-direction: column;
gap: 10px;
}
.backpanel {
position: absolute;
@ -181,5 +239,7 @@
background-color: rgba(0, 0, 0, 0.2);
pointer-events: all;
backdrop-filter: blur(9px);
max-height: 80vh;
min-width: 321px;
}
</style>

26
src/Dialog.svelte

@ -0,0 +1,26 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let pos = [0, 0]
let visible = false
export function toggle() {
visible = !visible
}
export function setPos(p: [number, number]) {
//pos = p;
}
</script>
{#if visible}
<div style="top: {pos[1]}px; left: {pos[0]}px" class="dialog">
<slot />
</div>
{/if}
<style scoped>
.dialog {
position: relative;
}
</style>

54
src/Tag.svelte

@ -0,0 +1,54 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let tag: string
export let toggleable = false
export let toggled = false
const dispatch = createEventDispatcher()
</script>
<span
class:toggle={toggleable}
class:toggled={toggleable && toggled}
on:click={() => dispatch('toggle')}
class="tag"
>
{tag}
{#if toggleable}
<span on:click={e => (e.preventDefault(), dispatch('remove'))}>x</span>
{/if}
</span>
<style scoped>
.tag {
padding: 5px;
border: 1px solid;
border-radius: 55px;
cursor: pointer;
display: inline-flex;
}
.tag.toggled {
background-color: rgb(213, 255, 212);
}
span.tag > span {
margin-left: 5px;
border-left: 1px solid;
padding-left: 5px;
}
.tag.toggled:hover {
color: white;
background-color: rgb(255 156 156 / 80%);
color: white;
}
.tag:not(.toggled):hover {
color: white;
background-color: rgb(213, 255, 212);
color: white;
}
</style>

55
src/stores.ts

@ -1,4 +1,5 @@
import { writable } from "svelte/store";
import type { Booru } from "./thirdeye";
const localLoad = <T>(key: string, def: T) =>
('__pee__' + key) in localStorage
@ -8,7 +9,8 @@ const localLoad = <T>(key: string, def: T) =>
const localSet = (key: string, value: any) =>
localStorage.setItem('__pee__' + key, JSON.stringify(value));
export const settings = writable(localLoad('settings', {
export const settings = writable(localLoad('settingsv2', {
...localLoad('settings', {}),
loop: true,
dh: false,
xpv: false,
@ -21,12 +23,49 @@ export const settings = writable(localLoad('settings', {
sh: false,
ep: false,
blacklist: ['guro', 'scat', 'ryona', 'gore'],
sources: ['gelbooru.com',
'yande.re',
'capi-v2.sankakucomplex.com',
'api.rule34.xxx',
'danbooru.donmai.us',
'lolibooru.moe']
rsources: [{
name: 'Gelbooru',
domain: 'gelbooru.com',
endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
view: "https://gelbooru.com/index.php?page=post&s=view&id="
},
{
name: 'Yandere',
domain: 'yande.re',
endpoint: '/post.json?tags=md5:',
view: `https://yande.re/post/show/`
},
{
name: 'Sankaku',
domain: 'capi-v2.sankakucomplex.com',
endpoint: '/posts/keyset?tags=md5:',
view: `https://chan.sankakucomplex.com/post/show/`
},
{
name: 'Rule34',
domain: 'api.rule34.xxx',
endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
// note: rule34 do not seem to give source in their API
view: "https://rule34.xxx/index.php?page=post&s=view&id="
},
{
name: 'Danbooru',
domain: 'danbooru.donmai.us',
endpoint: '/posts.json?tags=md5:',
view: 'https://danbooru.donmai.us/posts/'
},
{
name: 'Lolibooru',
domain: 'lolibooru.moe',
endpoint: '/post.json?tags=md5:',
view: 'https://lolibooru.moe/post/show/'
},
{
name: "ATFbooru",
domain: "booru.allthefallen.moe",
endpoint: "/posts.json?tags=md5:",
view: 'https://booru.allthefallen.moe/posts/'
}] as (Omit<Booru, 'quirks'> & {view: string, disabled?: boolean})[]
}));
export const appState = writable({
@ -36,5 +75,5 @@ export const appState = writable({
});
settings.subscribe(newVal => {
localSet('settings', newVal);
localSet('settingsv2', newVal);
});

115
src/thirdeye.ts

@ -31,120 +31,29 @@ function firstThatFor<T>(promises: Promise<T>[], pred: (v: T) => boolean) {
const gelquirk: (s: string) => tran = prefix => (a =>
(a.post || a).map((e: any) => ({
ext: e.image.substr(e.image.indexOf('.') + 1),
full_url: e.file_url,
preview_url: e.preview_url,
source: e.source,
ext: e.file_ext || e.file_url.substr(e.file_url.lastIndexOf('.') + 1),
page: `${prefix}${e.id}`,
preview_url: e.preview_url,
tags: e.tags.split(' ')
tags: (e.tag_string || e.tags || '').split(' ')
} as BooruMatch)) || []);
export const boorus: Booru[] = [
{
name: 'Gelbooru',
domain: 'gelbooru.com',
endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
quirks: gelquirk("https://gelbooru.com/index.php?page=post&s=view&id=")
},
{
name: 'Yandere',
domain: 'yande.re',
endpoint: '/post.json?tags=md5:',
quirks: a =>
a.map((e: any) => ({
source: e.source,
page: `https://yande.re/post/show/${e.id}`,
ext: e.file_ext,
full_url: e.file_url,
preview_url: e.preview_url,
tags: e.tags.split(' ')
} as BooruMatch))
},
{
name: 'Sankaku',
domain: 'capi-v2.sankakucomplex.com',
endpoint: '/posts/keyset?tags=md5:',
quirks: a => a.data ?
a.data.map((e: any) => ({
source: e.source,
// api cannot differenciate between idol and chan?
page: `https://chan.sankakucomplex.com/post/show/${e.id}`,
ext: e.file_type.substr(e.file_type.indexOf('/') + 1),
full_url: e.file_url,
preview_url: e.preview_url,
tags: e.tags.map((e: any) => e.name_en)
} as BooruMatch)) : []
},
{
name: 'Rule34',
domain: 'api.rule34.xxx',
endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
// note: rule34 do not seem to give source in their API
quirks: gelquirk("https://rule34.xxx/index.php?page=post&s=view&id=")
},
{
name: 'Danbooru',
domain: 'danbooru.donmai.us',
endpoint: '/posts.json?tags=md5:',
quirks: a =>
a.map((e: any) => ({
source: e.source,
page: `https://danbooru.donmai.us/posts/${e.id}`,
ext: e.file_ext,
full_url: e.file_url,
preview_url: e.preview_url,
tags: e.tag_string.split(' ')
} as BooruMatch))
},
{
name: 'Lolibooru',
domain: 'lolibooru.moe',
endpoint: '/post.json?tags=md5:',
quirks: a =>
a.map((e: any) => ({
source: e.source,
page: `https://lolibooru.moe/post/show/${e.id}`,
ext: e.file_url.substr(e.file_url.lastIndexOf('.') + 1),
full_url: e.file_url,
preview_url: e.preview_url,
tags: e.tags.split(' ')
} as BooruMatch))
},
{
name: "ATFbooru",
domain: "booru.allthefallen.moe",
endpoint: "/posts.json?tags=md5:",
quirks: a =>
a.map((e: any) => ({
source: e.source,
page: `https://booru.allthefallen.moe/posts/${e.id}`,
ext: e.file_url.substr(e.file_url.lastIndexOf('.') + 1),
full_url: e.file_url,
preview_url: e.preview_url,
tags: e.tag_string.split(' ')
} as BooruMatch))
},
// {
// name: "Rule34Paheal", domain: "rule34.paheal.net",
// endpoint: "/posts.json?tags=md5:",
// quirks: a =>
// a.map((e: any) => ({
// source: e.source,
// page: `https://rule34.paheal.net/post/view/${e.id}`,
// ext: e.file_url.substr(e.file_url.lastIndexOf('.') + 1),
// full_url: e.file_url,
// preview_url: e.preview_url,
// tags: e.tag_string.split(' ')
// } as BooruMatch))
// }
];
settings.subscribe(s => {
boorus = s.rsources.map(e => ({
...e,
quirks: gelquirk(e.view)
}));
});
export let boorus: Booru[] = [];
let black = new Set<string>();
let sources = new Set<string>();
settings.subscribe(s => {
black = new Set(s.blacklist);
sources = new Set(s.sources);
sources = new Set(s.rsources.map(e => e.domain));
});
const cache: any = {};

Loading…
Cancel
Save