// ==UserScript==
// @name 아카 뷰어
// @name:ko 아카 뷰어
// @name:en arca viewer
// @description i,j,k 키를 눌러보세요
// @description:ko i,j,k 키를 눌러보세요
// @description:en press i to open
// @version 250406112832
// @match https://arca.live/b/*/*
// @match https://*.arca.live/b/*/*
// @author nanikit
// @namespace https://greasyfork.org/ko/users/713014-nanikit
// @license MIT
// @connect namu.la
// @connect *
// @grant GM.addValueChangeListener
// @grant GM.getResourceText
// @grant GM.getValue
// @grant GM.removeValueChangeListener
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant unsafeWindow
// @require https://cdn.jsdelivr.net/npm/[email protected]/require.js
// @resource link:@headlessui/react https://cdn.jsdelivr.net/npm/@headlessui/[email protected]/dist/headlessui.prod.cjs
// @resource link:@stitches/react https://cdn.jsdelivr.net/npm/@stitches/[email protected]/dist/index.cjs
// @resource link:clsx https://cdn.jsdelivr.net/npm/[email protected]/dist/clsx.js
// @resource link:fflate https://cdn.jsdelivr.net/npm/[email protected]/lib/browser.cjs
// @resource link:jotai https://cdn.jsdelivr.net/npm/[email protected]/index.js
// @resource link:jotai-cache https://cdn.jsdelivr.net/npm/[email protected]/dist/cjs/atomWithCache.js
// @resource link:jotai/react https://cdn.jsdelivr.net/npm/[email protected]/react.js
// @resource link:jotai/react/utils https://cdn.jsdelivr.net/npm/[email protected]/react/utils.js
// @resource link:jotai/utils https://cdn.jsdelivr.net/npm/[email protected]/utils.js
// @resource link:jotai/vanilla https://cdn.jsdelivr.net/npm/[email protected]/vanilla.js
// @resource link:jotai/vanilla/utils https://cdn.jsdelivr.net/npm/[email protected]/vanilla/utils.js
// @resource link:overlayscrollbars https://cdn.jsdelivr.net/npm/[email protected]/overlayscrollbars.cjs
// @resource link:overlayscrollbars-react https://cdn.jsdelivr.net/npm/[email protected]/overlayscrollbars-react.cjs.js
// @resource link:react https://cdn.jsdelivr.net/npm/[email protected]/cjs/react.production.js
// @resource link:react-dom https://cdn.jsdelivr.net/npm/[email protected]/cjs/react-dom.production.js
// @resource link:react-dom/client https://cdn.jsdelivr.net/npm/[email protected]/cjs/react-dom-client.production.js
// @resource link:react-toastify https://cdn.jsdelivr.net/npm/[email protected]/dist/react-toastify.js
// @resource link:react/jsx-runtime https://cdn.jsdelivr.net/npm/[email protected]/cjs/react-jsx-runtime.production.js
// @resource link:scheduler https://cdn.jsdelivr.net/npm/[email protected]/cjs/scheduler.production.min.js
// @resource link:vcv-inject-node-env data:,unsafeWindow.process=%7Benv:%7BNODE_ENV:%22production%22%7D%7D
// @resource link:vim_comic_viewer https://update.greasyfork.org/scripts/417893/1566357/vim%20comic%20viewer.js
// @resource overlayscrollbars-css https://cdn.jsdelivr.net/npm/[email protected]/styles/overlayscrollbars.min.css
// @resource react-toastify-css https://cdn.jsdelivr.net/npm/[email protected]/dist/ReactToastify.css
// ==/UserScript==
"use strict";
define("main", (require, exports, module) => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
const vim_comic_viewer = __toESM(require("vim_comic_viewer"));
async function main() {
const viewer = await (0, vim_comic_viewer.initialize)({
source: comicSource,
mediaProps: { loading: "lazy" }
});
addEventListener("keydown", (event) => {
switch (event.key) {
case "m":
goToCommentIfEligible(event);
break;
default:
forwardEvent(event, viewer);
break;
}
}, { capture: true });
}
function forwardEvent(event, viewer) {
if (viewer.defaultGlobalKeyHandler(event)) {
event.stopPropagation();
return;
}
const ancestors = getAncestors(event.target);
if (ancestors.includes(viewer.container)) {
if (viewer.defaultElementKeyHandler(event)) event.stopPropagation();
}
}
function goToCommentIfEligible(event) {
if (isCaptureTargetEvent(event)) document.querySelector("#comment > *").scrollIntoView({ block: "center" });
}
function getAncestors(element) {
const ancestors = [];
let cursor = element;
while (cursor) {
ancestors.push(cursor);
cursor = cursor.parentElement;
}
return ancestors;
}
function isCaptureTargetEvent(event) {
const { ctrlKey, altKey, shiftKey } = event;
return !(ctrlKey || altKey || shiftKey || vim_comic_viewer.utils.isTyping(event));
}
async function comicSource({ cause, maxSize }) {
const isDownload = cause === "download";
const media = await searchMedia();
return media.map(isDownload ? getOriginalLink : getAdaptiveLink);
function getAdaptiveLink(imgOrVideo) {
const originalImageUrl = imgOrVideo.parentElement?.href;
const { width, height } = imgOrVideo;
const adaptive = {
src: imgOrVideo.src,
width,
height,
type: imgOrVideo.tagName === "IMG" ? "image" : "video"
};
if (!originalImageUrl) return adaptive;
const isGif = new URL(originalImageUrl).pathname.endsWith(".gif");
const original = {
type: "image",
src: originalImageUrl,
width,
height
};
if (isGif) return original;
const resizedWidth = 1e3;
const resizedHeight = height * resizedWidth / width;
const zoomRatio = Math.min(maxSize.width / resizedWidth, maxSize.height / resizedHeight);
const canBePoorVisual = zoomRatio >= 2;
if (canBePoorVisual && cause === "error") return adaptive;
return canBePoorVisual ? original : adaptive;
}
}
async function searchMedia() {
while (true) {
const media = [...document.querySelectorAll(".article-content img[src]:not([src='']), .article-content video[src]:not([src=''])")];
const isDehydrated = media.some((x) => x.tagName === "IMG" && !x.parentElement?.href);
if (isDehydrated) {
await vim_comic_viewer.utils.timeout(100);
continue;
}
return media;
}
}
function getOriginalLink(imgOrVideo) {
const originalImageUrl = imgOrVideo.parentElement?.href;
if (originalImageUrl) return {
src: originalImageUrl,
type: "image"
};
return {
src: imgOrVideo.src,
type: "video"
};
}
main();
});
define("tampermonkey_grants", function() { Object.assign(this.window, { GM, unsafeWindow }); });
requirejs.config({ deps: ["tampermonkey_grants"] });
load()
async function load() {
const links = GM.info.script.resources.filter(x => x.name.startsWith("link:"));
await Promise.all(links.map(async ({ name }) => {
const script = await GM.getResourceText(name)
define(name.replace("link:", ""), Function("require", "exports", "module", script))
}));
require(["main"], () => {}, console.error);
}