Browse Source

Sources, FF fixes, preloading...

pull/46/head 0.88
coomdev 2 years ago
parent
commit
cf4317010a
  1. 4
      main.meta.js
  2. 863
      main.user.js
  3. 12
      src/App.svelte
  4. 110
      src/Embedding.svelte
  5. 24
      src/EyeButton.svelte
  6. 55
      src/ScrollHighlighter.svelte
  7. 2
      src/global.css
  8. 15
      src/main.ts
  9. 11
      src/stores.ts
  10. 24
      src/thirdeye.ts

4
main.meta.js

@ -1,7 +1,7 @@
// ==UserScript==
// @name PNGExtraEmbed
// @namespace https://coom.tech/
// @version 0.86
// @version 0.87
// @description uhh
// @author You
// @match https://boards.4channel.org/*
@ -10,6 +10,8 @@
// @require https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @grant GM_openInTab
// @grant GM.openInTab
// @run-at document-start
// @connect 4chan.org
// @connect 4channel.org

863
main.user.js

File diff suppressed because it is too large

12
src/App.svelte

@ -74,6 +74,18 @@
<input type="checkbox" bind:checked={$settings.eye} />
Hide embedded content behind an eye.
</label>
<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.ca} />
Control audio on videos with mouse wheel.
</label>
<label>
<input type="checkbox" bind:checked={$settings.te} />
Turn off third-eye.

110
src/Embedding.svelte

@ -1,8 +1,11 @@
<script lang="ts">
import { fileTypeFromBuffer } from 'file-type'
import { settings } from './stores'
import { settings, appState } from './stores'
import { beforeUpdate, tick } from 'svelte'
import type {EmbeddedFile} from './main';
import { createEventDispatcher } from 'svelte';
export const dispatch = createEventDispatcher();
export let file: EmbeddedFile
let isVideo = false
@ -10,7 +13,7 @@
let isAudio = false
let url = ''
let settled = false
let contracted = true
let contracted = true;
let hovering = false
let ftype = '';
@ -23,6 +26,7 @@
let furl: string | undefined = undefined;
let visible = false;
export const isNotChrome = !navigator.userAgent.includes("Chrome/");
export let id = '';
document.addEventListener("reveal", (e: any) => {
@ -30,6 +34,10 @@
visible = !visible;
});
export function isContracted() {
return contracted;
}
beforeUpdate(async () => {
if (settled) return
settled = true
@ -43,18 +51,37 @@
isVideo = type.mime.startsWith('video/')
isAudio = type.mime.startsWith('audio/')
isImage = type.mime.startsWith('image/')
dispatch("fileinfo", {type})
if (isImage) contracted = !$settings.xpi
if (isImage) {
contracted = !$settings.xpi && !$appState.isCatalog;
}
if (isVideo) {
contracted = !$settings.xpv
contracted = !$settings.xpv && !$appState.isCatalog
}
if ($settings.pre) {
unzip(); // not awaiting on purpose
}
})
if ($settings.prev) {
let obs = new IntersectionObserver((entries, obs) => {
for(const item of entries) {
if(!item.isIntersecting) continue
unzip();
obs.unobserve(place);
}
}, {root:null, rootMargin: '0px', threshold: 0.01});
obs.observe(place);
}
});
let unzipping = false;
let progress = [0, 0]
async function unzip() {
if (!file.thumbnail)
return;
if (unzipping)
return;
unzipping = true;
let lisn = new EventTarget();
lisn.addEventListener("progress", (e: any) => {
@ -69,6 +96,8 @@
isVideo = type.mime.startsWith('video/')
isAudio = type.mime.startsWith('audio/')
isImage = type.mime.startsWith('image/')
dispatch("fileinfo", {type})
if (hovering) {
// reset hovering to recompute proper image coordinates
setTimeout(() => {
@ -86,24 +115,38 @@
)
}
async function bepis() {
contracted = !contracted
if (hovering) hoverStop()
if (contracted && isVideo) {
videoElem.controls = false
videoElem.pause()
}
if (!contracted && isVideo) {
videoElem.controls = true
// has to be delayed
setTimeout(async () => {
videoElem.currentTime = hoverVideo.currentTime || 0;
await videoElem.play()
}, 10)
}
if (file.thumbnail && !furl) {
// don't know how you managed to click before hovering but oh well
unzip()
export async function bepis(ev: MouseEvent) {
if (ev.button == 0) {
contracted = !contracted
if (hovering) hoverStop()
if (contracted && isVideo) {
videoElem.controls = false
videoElem.pause()
}
if (!contracted && isVideo) {
videoElem.controls = true
// has to be delayed
setTimeout(async () => {
videoElem.currentTime = hoverVideo.currentTime || 0;
await videoElem.play()
}, 10)
}
if (file.thumbnail && !furl) {
// don't know how you managed to click before hovering but oh well
unzip()
}
} else if (ev.button == 1) { // middle click
let src = furl || url;
if (ev.altKey && file.source) {
src = file.source;
}
if (ev.shiftKey && file.page) {
src = file.page;
}
if (isNotChrome) {
window.open(src, '_blank');
} else
await GM.openInTab(src, {active: false, insert: true});
}
}
@ -179,15 +222,17 @@
}
function adjustAudio(ev: WheelEvent) {
if (!$settings.ca) return
if (!isVideo) return
if (hasAudio(videoElem)) {
let vol = videoElem.volume * (ev.deltaY > 0 ? 0.9 : 1.1);
vol = Math.max(0, Math.min(1, vol));
videoElem.volume = vol;
hoverVideo.volume = videoElem.volume;
hoverVideo.muted = vol < 0;
ev.preventDefault()
}
if ($settings.dh && contracted) return
if (!hasAudio(videoElem))
return;
let vol = videoElem.volume * (ev.deltaY > 0 ? 0.9 : 1.1);
vol = Math.max(0, Math.min(1, vol));
videoElem.volume = vol;
hoverVideo.volume = videoElem.volume;
hoverVideo.muted = vol < 0;
ev.preventDefault()
}
</script>
@ -196,7 +241,8 @@
<div
class:contract={contracted}
class="place"
on:click={() => bepis()}
on:click={bepis}
on:auxclick={bepis}
on:mouseover={hoverStart}
on:mouseout={hoverStop}
on:mousemove={hoverUpdate}

24
src/EyeButton.svelte

@ -1,5 +1,6 @@
<script lang="ts">
import { fileTypeFromBuffer } from 'file-type';
import type Embedding from './Embedding.svelte';
import type { EmbeddedFile } from './main';
@ -7,12 +8,20 @@ import type { EmbeddedFile } from './main';
export let id = ''
export let file: EmbeddedFile;
export let inst: Embedding;
let isVideo = false
inst.$on("fileinfo", (info) => {
isVideo = info.detail.type.mime.startsWith('video/');
})
let visible = false
function reveal() {
visible = !visible
document.dispatchEvent(new CustomEvent('reveal', { detail: { id } }))
}
const isNotChrome = !navigator.userAgent.includes("Chrome/");
async function downloadFile() {
const a = document.createElement("a") as HTMLAnchorElement;
@ -36,7 +45,20 @@ import type { EmbeddedFile } from './main';
class="fa clickable"
/>
{/if}
<span title={file.filename} on:click={downloadFile} class="fa fa-download clickable" />
<span
title={file.filename}
on:click={downloadFile}
class="fa fa-download clickable"
/>
{#if isNotChrome && isVideo}
<!-- svelte-ignore a11y-missing-attribute -->
<a on:click={(ev) => {
inst.bepis(ev);
}} alt="By clicking this you agree to stay hydrated"
class="clickable"
>[PEE contract]</a
>
{/if}
<style scoped>
.clickable {

55
src/ScrollHighlighter.svelte

@ -0,0 +1,55 @@
<script lang="ts">
import { fileTypeFromBuffer } from 'file-type';
import type { EmbeddedFile } from './main';
import { settings } from './stores'
function getOffset(el: HTMLElement | null) {
var _x = 0;
var _y = 0;
while(el && el instanceof HTMLElement) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent as HTMLElement;
}
return { top: _y, left: _x };
}
</script>
{#if $settings.sh}
<div class="scroll-container" />
{/if}
<style scoped>
.scroll-container {
position: fixed;
height: 100%;
width: 12px;
/* pointer-events: none; */
top: 0;
right: 0;
z-index: 1000;
}
.scroll-container span {
/* markers */
position: absolute;
right: 0;
width: 33%;
cursor: pointer;
transition: width 200ms;
}
.scroll-container:hover span {
width: 100%;
}
.scroll-container span.position {
pointer-events: none;
}
.marker-hovered {
opacity: 0.8;
}
</style>

2
src/global.css

@ -61,4 +61,4 @@ div.hasext .catalog-host img {
.fileThumb.filehost {
margin-left: 0 !important;
}
}

15
src/main.ts

@ -1,5 +1,5 @@
import { Buffer } from "buffer";
import { settings } from "./stores";
import { appState, settings } from "./stores";
import globalCss from './global.css';
import png from "./png";
@ -10,6 +10,7 @@ import thirdeye from "./thirdeye";
import { GM_fetch, GM_head, headerStringToObject } from "./requests";
import App from "./App.svelte";
import ScrollHighlighter from "./ScrollHighlighter.svelte";
import SettingsButton from './SettingsButton.svelte';
import Embedding from './Embedding.svelte';
import EyeButton from './EyeButton.svelte';
@ -59,12 +60,16 @@ async function* streamRemote(url: string, chunkSize = 16 * 1024, fetchRestOnNonC
}
type EmbeddedFileWithPreview = {
page?: string; // can be a booru page
source?: string; // can be like a twitter post this was posted in originally
thumbnail: Buffer;
filename: string;
data: (lisn?: EventTarget) => Promise<Buffer>;
};
type EmbeddedFileWithoutPreview = {
page: undefined;
source: undefined;
thumbnail: undefined;
filename: string;
data: Buffer;
@ -152,7 +157,7 @@ const processPost = async (post: HTMLDivElement) => {
eyecont.innerHTML = '';
}
const id = ~~(Math.random() * 20000000);
new Embedding({
const emb = new Embedding({
target: imgcont,
props: {
file: res,
@ -163,6 +168,7 @@ const processPost = async (post: HTMLDivElement) => {
target: eyecont,
props: {
file: res,
inst: emb,
id: '' + id
}
});
@ -232,6 +238,11 @@ const startup = async () => {
const appInstance = new App({ target: appHost });
document.body.append(appHost);
const scrollHost = textToElement(`<div class="pee-scroll"></div>`);
new ScrollHighlighter({ target: scrollHost });
document.body.append(scrollHost);
appState.set({isCatalog: !!document.querySelector('.catalog-small')});
await Promise.all(posts.map(e => processPost(e as any)));
};

11
src/stores.ts

@ -15,6 +15,9 @@ export const settings = writable(localLoad('settings', {
xpi: false,
te: false,
eye: false,
ca: false,
pre: false,
prev: false,
blacklist: ['guro', 'scat', 'ryona', 'gore'],
sources: ['gelbooru.com',
'yande.re',
@ -24,6 +27,14 @@ export const settings = writable(localLoad('settings', {
'lolibooru.moe']
}));
export const appState = writable({
isCatalog: false
});
appState.subscribe(v => {
console.log(v);
});
settings.subscribe(newVal => {
localSet('settings', newVal);
});

24
src/thirdeye.ts

@ -9,6 +9,8 @@ export type Booru = {
};
export type BooruMatch = {
source?: string;
page?: string;
tags: string[];
full_url: string;
preview_url: string;
@ -17,25 +19,29 @@ export type BooruMatch = {
type tran = (a: any) => BooruMatch[];
const gelquirk: tran = a =>
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,
source: e.source,
page: `${prefix}${e.id}`,
preview_url: e.preview_url,
tags: e.tags.split(' ')
} as BooruMatch)) || [];
} as BooruMatch)) || []);
export const boorus: Booru[] = [
{
domain: 'gelbooru.com',
endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
quirks: gelquirk
quirks: gelquirk("https://gelbooru.com/index.php?page=post&s=view&id=")
},
{
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,
@ -47,6 +53,9 @@ export const boorus: Booru[] = [
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,
@ -56,13 +65,16 @@ export const boorus: Booru[] = [
{
domain: 'api.rule34.xxx',
endpoint: '/index.php?page=dapi&s=post&q=index&json=1&tags=md5:',
quirks: gelquirk
// note: rule34 do not seem to give source in their API
quirks: gelquirk("https://rule34.xxx/index.php?page=post&s=view&id=")
},
{
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,
@ -74,6 +86,8 @@ export const boorus: Booru[] = [
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,
@ -120,6 +134,8 @@ const extract = async (b: Buffer, fn?: string) => {
}
let cachedFile: ArrayBuffer;
return {
source: result[0].source,
page: result[0].page,
filename: fn!.substring(0, 33) + result[0].ext,
thumbnail: (await (await GM_fetch(result[0].preview_url)).arrayBuffer()),
data: async (lsn) => {

Loading…
Cancel
Save