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== // ==UserScript==
// @name PNGExtraEmbed // @name PNGExtraEmbed
// @namespace https://coom.tech/ // @namespace https://coom.tech/
// @version 0.102 // @version 0.103
// @description uhh // @description uhh
// @author You // @author You
// @match https://boards.4channel.org/* // @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"> <script lang="ts">
import { hasContext, onDestroy } from 'svelte' import { hasContext, onDestroy } from 'svelte'
import Dialog from './Dialog.svelte';
import { settings } from './stores' 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 visible = false
let penisEvent = () => { let penisEvent = () => {
@ -11,39 +23,23 @@
document.addEventListener('penis', penisEvent) document.addEventListener('penis', penisEvent)
console.log('app loaded') console.log('app loaded')
let sources = [ function removeTag(t: string) {
'gelbooru.com', $settings.blacklist = $settings.blacklist.filter((e: any) => e != t)
'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 removeSelection() { function removeBooru(t: string) {
let s = new Set<string>(); const idx = $settings.rsources.findIndex(e => e.domain == t)
for (let i = 0; i < selectobj2.selectedOptions.length; ++i) { const rep = prompt("You DO know what you're doing, right? (type 'y')")
let obj = selectobj2.selectedOptions.item(i) if (!rep || rep != 'y') return
if (!obj) continue if (idx >= 0) $settings.rsources.splice(idx, 1)
s.add(obj.value) $settings.rsources = $settings.rsources
$settings.blacklist = $settings.blacklist.filter((e: any) => !s.has(e)) }
}
function toggleBooru(t: string) {
const elem = $settings.rsources.find(e => e.domain == t)
if (elem)
elem.disabled = !elem.disabled;
$settings.rsources = $settings.rsources
} }
onDestroy(() => { onDestroy(() => {
@ -94,7 +90,9 @@
<label> <label>
<input type="checkbox" bind:checked={$settings.ep} /> <input type="checkbox" bind:checked={$settings.ep} />
<!-- svelte-ignore a11y-missing-attribute --> <!-- 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>
<label> <label>
<input type="checkbox" bind:checked={$settings.te} /> <input type="checkbox" bind:checked={$settings.te} />
@ -102,26 +100,71 @@
</label> </label>
{#if !$settings.te} {#if !$settings.te}
<h3>Booru sources</h3> <h3>Booru sources</h3>
<select multiple bind:this={selectobj} size={sources.length}> <div class="tagcont">
{#each sources as source, i} {#each $settings.rsources as source, i}
<option <Tag
class="sourcedi" tag={source.name}
class:sourceen={$settings.sources.includes(source)} on:remove={() => removeBooru(source.domain)}
value={source}>{source}</option on:toggle={() => toggleBooru(source.domain)}
> toggleable={true}
toggled={!$settings.rsources.find(e => e.domain == source.domain)
?.disabled}
/>
{/each} {/each}
</select> </div>
<button on:click={toggleSelection}>Toggle sources</button> <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 /> <hr />
<h3>Blacklisted tags</h3> <h3>Blacklisted tags</h3>
<select multiple bind:this={selectobj2} size={sources.length}> <div class="tagcont">
{#each $settings.blacklist as source, i} {#each $settings.blacklist as tag, i}
<option value={source}>{source}</option> <Tag {tag} on:toggle={() => removeTag(tag)} />
{/each} {/each}
</select> </div>
<button on:click={removeSelection}>Remove</button>
<input <input
placeholder="Press enter after typing your tag" placeholder="Press enter after typing your tag"
on:keydown={ev => { on:keydown={ev => {
if (ev.key == 'Enter') { if (ev.key == 'Enter') {
$settings.blacklist = [ $settings.blacklist = [
@ -137,6 +180,13 @@
</div> </div>
<style scoped> <style scoped>
.tagcont {
display: flex;
gap: 5px;
margin-bottom: 10px;
flex-wrap: wrap;
}
select { select {
font-size: 1.2em; font-size: 1.2em;
} }
@ -145,14 +195,6 @@
display: block; display: block;
} }
.sourcedi {
border-right: 10px solid lightcoral;
}
.sourceen {
border-right: 10px solid lightgreen;
}
.disabled { .disabled {
display: none; display: none;
} }
@ -169,6 +211,22 @@
h1 { h1 {
text-align: center; 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 { .backpanel {
position: absolute; position: absolute;
@ -181,5 +239,7 @@
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
pointer-events: all; pointer-events: all;
backdrop-filter: blur(9px); backdrop-filter: blur(9px);
max-height: 80vh;
min-width: 321px;
} }
</style> </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 { writable } from "svelte/store";
import type { Booru } from "./thirdeye";
const localLoad = <T>(key: string, def: T) => const localLoad = <T>(key: string, def: T) =>
('__pee__' + key) in localStorage ('__pee__' + key) in localStorage
@ -8,7 +9,8 @@ const localLoad = <T>(key: string, def: T) =>
const localSet = (key: string, value: any) => const localSet = (key: string, value: any) =>
localStorage.setItem('__pee__' + key, JSON.stringify(value)); localStorage.setItem('__pee__' + key, JSON.stringify(value));
export const settings = writable(localLoad('settings', { export const settings = writable(localLoad('settingsv2', {
...localLoad('settings', {}),
loop: true, loop: true,
dh: false, dh: false,
xpv: false, xpv: false,
@ -21,12 +23,49 @@ export const settings = writable(localLoad('settings', {
sh: false, sh: false,
ep: false, ep: false,
blacklist: ['guro', 'scat', 'ryona', 'gore'], blacklist: ['guro', 'scat', 'ryona', 'gore'],
sources: ['gelbooru.com', rsources: [{
'yande.re', name: 'Gelbooru',
'capi-v2.sankakucomplex.com', domain: 'gelbooru.com',
'api.rule34.xxx', endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
'danbooru.donmai.us', view: "https://gelbooru.com/index.php?page=post&s=view&id="
'lolibooru.moe'] },
{
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({ export const appState = writable({
@ -36,5 +75,5 @@ export const appState = writable({
}); });
settings.subscribe(newVal => { 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 => const gelquirk: (s: string) => tran = prefix => (a =>
(a.post || a).map((e: any) => ({ (a.post || a).map((e: any) => ({
ext: e.image.substr(e.image.indexOf('.') + 1),
full_url: e.file_url, full_url: e.file_url,
preview_url: e.preview_url,
source: e.source, source: e.source,
ext: e.file_ext || e.file_url.substr(e.file_url.lastIndexOf('.') + 1),
page: `${prefix}${e.id}`, page: `${prefix}${e.id}`,
preview_url: e.preview_url, tags: (e.tag_string || e.tags || '').split(' ')
tags: e.tags.split(' ')
} as BooruMatch)) || []); } as BooruMatch)) || []);
export const boorus: Booru[] = [ settings.subscribe(s => {
{ boorus = s.rsources.map(e => ({
name: 'Gelbooru', ...e,
domain: 'gelbooru.com', quirks: gelquirk(e.view)
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=") });
}, export let boorus: Booru[] = [];
{
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))
// }
];
let black = new Set<string>(); let black = new Set<string>();
let sources = new Set<string>(); let sources = new Set<string>();
settings.subscribe(s => { settings.subscribe(s => {
black = new Set(s.blacklist); black = new Set(s.blacklist);
sources = new Set(s.sources); sources = new Set(s.rsources.map(e => e.domain));
}); });
const cache: any = {}; const cache: any = {};

Loading…
Cancel
Save