Adds a small 3D Ripper button on supported 3D model source sites.
// ==UserScript==
// @name 3DRipper Helper
// @namespace https://3dripper.online/
// @version 1.1.29
// @author 3DRipper Online
// @description Adds a small 3D Ripper button on supported 3D model source sites.
// @license MIT
// @icon https://3dripper.online/icons/favicon-32x32.png
// @match https://sketchfab.com/3d-models/*
// @match https://www.sketchfab.com/3d-models/*
// @match https://sketchfab.com/models/*/embed
// @match https://www.sketchfab.com/models/*/embed
// @match https://sketchfab.com/models/*/embed*
// @match https://www.sketchfab.com/models/*/embed*
// @match https://fab.com/listings/*
// @match https://www.fab.com/listings/*
// @match https://fab.com/dope/*
// @match https://www.fab.com/dope/*
// @match http://localhost:3001/*
// @match https://3dripper.online/*
// @match https://www.3dripper.online/*
// @match https://hub.vroid.com/*
// @match https://www.artstation.com/artwork/*
// @match https://www.artstation.com/embed/*
// @match https://marmoset.co/*
// @match https://*.marmoset.co/*
// @match https://aplaybox.com/*
// @match https://www.aplaybox.com/*
// @grant GM_deleteValue
// @grant GM_getValue
// @grant GM_info
// @grant GM_listValues
// @grant GM_setValue
// @run-at document-idle
// ==/UserScript==
(function() {
"use strict";
var MESSAGE_NAMESPACE = "threedripper-bookmarklet";
var GM_REQUEST_EVENT = "threedripper-bookmarklet-gm-request";
var GM_RESPONSE_EVENT = "threedripper-bookmarklet-gm-response";
var USERSCRIPT_INSTALLED_EVENT = "threedripper:userscript-installed";
var GM_PAYLOAD_KEY_PREFIX = "3dripper:";
var GM_PAYLOAD_TTL_MS = 7200 * 1e3;
var bookmarkletWindow = window;
bookmarkletWindow.__THREEDRIPPER_BOOKMARKLET_MODE__ = "userscript";
bookmarkletWindow.__THREEDRIPPER_BOOKMARKLET_TARGET_BASE_URL__ = "https://3dripper.online";
bookmarkletWindow.__THREEDRIPPER_BOOKMARKLET_VERSION__ = "1.1.29";
function markUserscriptInstalled() {
document.documentElement.dataset.threedripperUserscriptInstalled = "1";
document.dispatchEvent(new CustomEvent(USERSCRIPT_INSTALLED_EVENT));
}
function isRecord(value) {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function dispatchGmResponse(id, ok, value) {
document.dispatchEvent(new CustomEvent(GM_RESPONSE_EVENT, { detail: {
id,
ok,
value
} }));
}
function getPayloadKeyTimestamp(key) {
const match = /^3dripper:[^:]+:(\d+):/.exec(key);
if (!match) return null;
const timestamp = Number(match[1]);
return Number.isFinite(timestamp) ? timestamp : null;
}
async function cleanupExpiredPayloads() {
try {
const now = Date.now();
const expiredKeys = (await GM_listValues()).filter((key) => {
const timestamp = getPayloadKeyTimestamp(key);
return timestamp !== null && now - timestamp > GM_PAYLOAD_TTL_MS;
});
await Promise.all(expiredKeys.map((key) => GM_deleteValue(key)));
return expiredKeys.length;
} catch {
return 0;
}
}
function installSourceStorageBridge() {
document.addEventListener(GM_REQUEST_EVENT, (event) => {
const detail = event.detail;
if (!isRecord(detail) || typeof detail.id !== "string" || typeof detail.key !== "string") return;
(async () => {
try {
if (detail.action === "set") {
if (detail.key.startsWith(GM_PAYLOAD_KEY_PREFIX)) await cleanupExpiredPayloads();
await GM_setValue(detail.key, detail.value);
dispatchGmResponse(detail.id, true, true);
return;
}
if (detail.action === "get") {
dispatchGmResponse(detail.id, true, await GM_getValue(detail.key, ""));
return;
}
if (detail.action === "delete") {
await GM_deleteValue(detail.key);
dispatchGmResponse(detail.id, true, true);
return;
}
if (detail.action === "clear") {
const matchedKeys = (await GM_listValues()).filter((key) => key.startsWith(detail.key));
await Promise.all(matchedKeys.map((key) => GM_deleteValue(key)));
dispatchGmResponse(detail.id, true, matchedKeys.length);
}
} catch {
dispatchGmResponse(detail.id, false);
}
})();
});
}
function isDownloaderAppPage() {
try {
const targetOrigin = new URL("https://3dripper.online").origin;
return window.location.origin === targetOrigin || window.location.hostname === "3dripper.online" || window.location.hostname === "www.3dripper.online" || window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
} catch {
return false;
}
}
function installTargetPayloadBridge() {
window.addEventListener("message", (event) => {
if (event.origin !== window.location.origin || !isRecord(event.data)) return;
if (event.data.namespace !== MESSAGE_NAMESPACE) return;
if (event.data.type !== "request-fab-models" && event.data.type !== "request-artstation-models" && event.data.type !== "request-sketchfab-models" && event.data.type !== "request-vroidhub-models") return;
const payloadKey = typeof event.data.payloadKey === "string" ? event.data.payloadKey : "";
if (!payloadKey.startsWith(GM_PAYLOAD_KEY_PREFIX)) return;
const responseType = event.data.type === "request-artstation-models" ? "artstation-models" : event.data.type === "request-sketchfab-models" ? "sketchfab-models" : event.data.type === "request-vroidhub-models" ? "vroidhub-models" : "fab-models";
(async () => {
const raw = await GM_getValue(payloadKey, "");
let models = [];
try {
models = raw ? JSON.parse(String(raw)) : [];
} catch {
models = [];
}
window.postMessage({
namespace: MESSAGE_NAMESPACE,
type: responseType,
payloadKey,
models
}, window.location.origin);
if (raw) await GM_deleteValue(payloadKey);
})();
});
}
markUserscriptInstalled();
installSourceStorageBridge();
cleanupExpiredPayloads();
if (isDownloaderAppPage()) installTargetPayloadBridge();
else {
const script = document.createElement("script");
script.src = `https://3dripper.online/bookmarklet.js?mode=userscript&t=${Date.now()}`;
script.async = true;
(document.head || document.documentElement).appendChild(script);
}
})();