dA_DragnFav

drag thumbs and deviations to yur favourites/collections

当前为 2023-12-31 提交的版本,查看 最新版本

// ==UserScript==
// @name         dA_DragnFav
// @namespace    phi.pf-control.de/userscripts/dA_DragnFav/dA_DragnFav.user.js
// @version      2.1
// @description  drag thumbs and deviations to yur favourites/collections
// @author       Dediggefedde
// @match        https://www.deviantart.com/*
// @require    	 http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
// @require      http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @grant        GM_addStyle
// @grant        GM.xmlHttpRequest
// @grant        GM.setValue
// @grant        GM.getValue
// ==/UserScript==

/* globals $*/
/* jshint esnext:true */

// faving informations:
//	POST https://www.deviantart.com/_napi/shared_api/collections/collect
//	{"itemid":864977862,"folderids":[1468881,48232885],"csrf_token":"Jt3u9HHt4hGtMxG7.qluh26.7-qAIhS-bmbIhMbgks-rKcuE_j1y6EsqMJyonDrrjsE"}

(function() {
    'use strict';

    //# temporary variables
    let grIDs = [];
    let settingmode = false;
    let groupOrder = [];
    let hiddengroups = [];
    let headerfilled = false;
    //# resources
    //hook copied from deviantart svg
    let imgHook = '<svg width="50" height="50" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg"><path d="M1.237 6.187L0 4.95l1.237-1.238L2.475 4.95l3.712-3.713 1.238 1.238-4.95 4.95-1.238-1.238z" fill-rule="evenodd"></path></svg>';
    //imgGear copied from inkscape "render gear", slightly adjusted
    let imgGear = '<svg  xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 20.444057 20.232336" > <g transform="translate(-15.480352,-5.6695418)">  <g transform="matrix(0.26458333,0,0,0.26458333,25.702381,15.78571)"  style="fill:#000000">  <path  style="fill:#000000;stroke:#000000;stroke-width:1"  d="m 28.46196,-3.25861 4.23919,-0.48535 0.51123,0.00182 4.92206,1.5536 v 4.37708 l -4.92206,1.5536 -0.51123,0.00182 -4.23919,-0.48535 -1.40476,6.15466 4.02996,1.40204 0.45982,0.22345 3.76053,3.53535 -1.89914,3.94361 -5.1087,-0.73586 -0.4614,-0.22017 -3.60879,-2.2766 -3.93605,4.93565 3.02255,3.01173 0.31732,0.40083 1.8542,4.81687 -3.42214,2.72907 -4.2835,-2.87957 -0.32017,-0.39856 -2.26364,-3.61694 -5.68776,2.73908 1.41649,4.0249 0.11198,0.49883 -0.41938,5.14435 -4.26734,0.97399 -2.6099,-4.45294 -0.11554,-0.49801 -0.47013,-4.2409 h -6.31294 l -0.47013,4.2409 -0.11554,0.49801 -2.6099,4.45294 -4.26734,-0.97399 -0.41938,-5.14435 0.11198,-0.49883 1.41649,-4.0249 -5.68776,-2.73908 -2.26364,3.61694 -0.32017,0.39856 -4.2835,2.87957 -3.42214,-2.72907 1.8542,-4.81687 0.31732,-0.40083 3.02255,-3.01173 -3.93605,-4.93565 -3.60879,2.2766 -0.4614,0.22017 -5.1087,0.73586 -1.89914,-3.94361 3.76053,-3.53535 0.45982,-0.22345 4.02996,-1.40204 -1.40476,-6.15466 -4.23919,0.48535 -0.51123,-0.00182 -4.92206,-1.5536 v -4.37708 l 4.92206,-1.5536 0.51123,-0.00182 4.23919,0.48535 1.40476,-6.15466 -4.02996,-1.40204 -0.45982,-0.22345 -3.76053,-3.53535 1.89914,-3.94361 5.1087,0.73586 0.4614,0.22017 3.60879,2.2766 3.93605,-4.93565 -3.02255,-3.01173 -0.31732,-0.40083 -1.8542,-4.81687 3.42214,-2.72907 4.2835,2.87957 0.32017,0.39856 2.26364,3.61694 5.68776,-2.73908 -1.41649,-4.0249 -0.11198,-0.49883 0.41938,-5.14435 4.26734,-0.97399 2.6099,4.45294 0.11554,0.49801 0.47013,4.2409 h 6.31294 l 0.47013,-4.2409 0.11554,-0.49801 2.6099,-4.45294 4.26734,0.97399 0.41938,5.14435 -0.11198,0.49883 -1.41649,4.0249 5.68776,2.73908 2.26364,-3.61694 0.32017,-0.39856 4.2835,-2.87957 3.42214,2.72907 -1.8542,4.81687 -0.31732,0.40083 -3.02255,3.01173 3.93605,4.93565 3.60879,-2.2766 0.4614,-0.22017 5.1087,-0.73586 1.89914,3.94361 -3.76053,3.53535 -0.45982,0.22345 -4.02996,1.40204 z"  />  <circle  style="fill:#ffffff;stroke:#000000;stroke-width:1"  cx="0"  cy="0"  r="15" />  </g>  </g> </svg>';

    //CSS styling
    //"#dA_DragnFav_header div[folderId] * {pointer-events: none;}
    GM_addStyle(`
#dA_DragnFav_header {color:var(--L8);background-color: #8fac85cc;z-index: 77;position: fixed;width: 100%;border: 2px ridge white;
	display: none;padding: 20px;justify-content: center;align-items: center;flex-wrap: wrap;box-sizing: border-box;}
#dA_DragnFav_header div[folderId] {padding: 15px;border-radius: 5px;background-color: #ffffdd;border: 1px solid black;margin: 10px;
	display:flex;align-items: center;flex-direction: column;position: relative;overflow:clip;}
#dA_DragnFav_header div[folderId] img {height:50px;max-width: 100px; overflow: hidden;text-overflow: ellipsis;font-size: smaller;}
#dA_DragnFav_header div.dA_DragnFav_sets{position:absolute;top:10px;right:10px;cursor:pointer;}
#dA_DragnFav_header div.dA_DragnFav_markhide{background-color:#a22;}
div.dA_DragnFav_right{right:0}
div.dA_DragnFav_groupTitle{display:inline-block;}
div.dA_DragnFav_left{left:0}
div.dA_DragnFav_over{z-index:3;position: absolute;top: 0;width: 50%;height: 100%;
	background-color: #b9d9b1;display: flex;align-items: center;justify-content: center;opacity:0;text-shadow: 3px 3px 3px #0006;font-weight:bold;}
div.dA_DragnFav_over p{pointer-events: none;}
div.dA_DragnFav_inside {position:absolute;z-index:2;left:0;width:100%;top:0;height:100%;display:flex;align-items: center;justify-content: center;visibility:hidden}
div.dA_DragnFav_inside svg {fill:green;}
#dA_DragnFav_drowDown{color:var(--L8);max-height:350px;position:absolute;z-index:99;top:0;left:0;border:1px solid green;
	overflow-y:scroll;border-radius:5px;background: linear-gradient(90deg,var(--L19),var(--L3),var(--L19));}
#dA_DragnFav_drowDown li{height:50px;background-color:var(--L19);position: relative;overflow:hidden;margin:2px 0;}
#dA_DragnFav_drowDown li:hover{background-color:var(--L2)}
#dA_DragnFav_drowDown img{overflow: hidden;text-overflow: ellipsis;font-size: smaller;max-width: 100px;height: 100%;}
#dA_DragnFav_drowDown div.dA_DragnFav_wrapImg{width: 100px; overflow: hidden;text-overflow: ellipsis;font-size: smaller;
	display:inline-block;vertical-align:middle;position: relative;height: 100%;text-align: center;}
#dA_DragnFav_drowDown div.dA_DragnFav_groupTitle{margin:0 15px}
`.replace(/\s\s+/g, ''));

    //# https requests
    //gets your currently available collections. returns promise with json response on success. response or parse error on error
    //assumes <21 collections
    function getCollections(offset = 0) {
        let token = $("input[name=validate_token]").val();
        return new Promise(function(resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/_napi/shared_api/gallection/folders?type=collection&offset=" + offset + "&limit=20&csrf_token=" + token,
                headers: {
                    "accept": 'application/json, text/plain, */*',
                    "content-type": 'application/json;charset=UTF-8'
                },
                onerror: function(response) {
                    console.log("error:", response);
                    reject(response);
                },
                onload: async function(response) {
                    let dat;
                    try {
                        dat = JSON.parse(response.responseText);
                        if (dat.hasMore) {
                            getCollections(dat.nextOffset).then(nret => {
                                dat.results = dat.results.concat(nret.results);
                                resolve(dat);
                                return;
                            });
                        } else {
                            resolve(dat);
                        }
                    } catch (e) {
                        reject(e);
                    }
                }
            });
        });
    }

    //get list of collection ids, this deviation id is inside already
    //returns promise with json response on success. response or parse error on error
    function inCollections(id) {
        let token = $("input[name=validate_token]").val();
        return new Promise(function(resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/_napi/shared_api/collections/collections_for_deviation?deviationid=" + id + "&csrf_token=" + token,
                headers: {
                    "accept": 'application/json, text/plain, */*',
                    "content-type": 'application/json;charset=UTF-8'
                },
                onerror: function(response) {
                    console.log("error:", response);
                    reject(response);
                },
                onload: async function(response) {
                    let dat;
                    try {
                        dat = JSON.parse(response.responseText);
                    } catch (e) {
                        reject(e);
                    }
                    resolve(dat);
                }
            });
        });
    }

    //sets the collection of the deviation with this id to the array cols (folder ids).
    //searches for validate_token on current page
    function setCollection(id, cols) {
        let token = $("input[name=validate_token]").val();
        let dat = {
            "itemid": id,
            "folderids": cols,
            "csrf_token": token
        };
        return new Promise(function(resolve, reject) {
            GM.xmlHttpRequest({
                method: "POST",
                url: "https://www.deviantart.com/_napi/shared_api/collections/collect",
                headers: {
                    "accept": 'application/json, text/plain, */*',
                    "content-type": 'application/json;charset=UTF-8'
                },
                dataType: 'json',
                data: JSON.stringify(dat),
                onerror: function(response) {
                    console.log("error:", response);
                    reject(response);
                },
                onload: async function(response) {
                    let dat;
                    try {
                        dat = JSON.parse(response.responseText); //returns {success:true}
                    } catch (e) {
                        reject(e);
                    }
                    resolve(dat);
                }
            });
        });
    }

    //# event listener
    //when items are dragged onto the bar
    function itemDroppedOnCol(event, add = true, mid = 0) {

        let id = mid;
        if (mid == 0) id = event.originalEvent.dataTransfer.getData('id');

        let fEl = $(event.target).closest("[folderId]");
        let fId = parseInt(fEl.attr("folderId"));

        if (!add) grIDs = [];
        if (!grIDs.includes(fId)) {
            grIDs.push(fId);
            setCollection(id, grIDs).then(ret => {
                fEl.find(".dA_DragnFav_inside").css("visibility", "visible");
            }).then(() => {
                markCollections(id);
            });
        }
    }

    function leaveSettingMode() {
        settingmode = false;
        $("#dA_DragnFav_header div.dA_DragnFav_markhide").css("display", "none");
        $("#dA_DragnFav_header div.dA_DragnFav_setDescr").remove();
        $("#dA_DragnFav_header").fadeOut();
    }

    function enterSettingMode() {
        settingmode = true;

        //show hidden
        $("#dA_DragnFav_header div.dA_DragnFav_markhide").css("display", "block");

        //make sortable
        $("#dA_DragnFav_header").sortable({
            helper: 'clone',
            forceHelperSize: true,
            forcePlaceholderSize: true,
            placeholder: "ui-state-highlight",
            items: 'div.dA_DragnFav_group',
            cursor: 'move',
            update: function(event, ui) {
                let colOrder = $(this).sortable('toArray', { attribute: 'folderId' });
                GM.setValue("groupOrder", JSON.stringify(colOrder));
            }
        }).prepend("<div class='dA_DragnFav_setDescr' style='width:100%'>Drag items to rearange them. Click an item to hide/show it. Click the gear to leave the settings mode.</div>");
    }

    //# initialization, reruns periodically for dynamic site building
    //insert fav-header
    function fillHeader(id) {
        return (new Promise(function(resolve, reject) {
            if (headerfilled) {
                resolve(id);
                return;
            }
            getCollections(0).then((ret) => {
                headerfilled = true;
                let tex = "";
                let contextTex = "";
                ret.results.forEach((el) => {
									let thumb = "";
									try{
                    if (el.thumb && el.thumb.coverImage && el.thumb.coverImage.media && el.thumb.coverImage.media.types && el.thumb.coverImage.media.prettyName && el.thumb.coverImage.media.token && el.thumb.coverImage.media.types[0] && el.thumb.coverImage.media.types[0].c) { //optional chaining breaks tampermonkey syntax highlight...
                        thumb += `${el.thumb.coverImage.media.baseUri}${el.thumb.coverImage.media.types[0].c.replace("<prettyName>", el.thumb.coverImage.media.prettyName)}?token=${el.thumb.coverImage.media.token[0]}`;
                    } else if (el.thumb && el.thumb.media && el.thumb.media.baseUri && el.thumb.media.types && el.thumb.media.prettyName && el.thumb.media.token && el.thumb.media.types[0] && el.thumb.media.types[0].c) {
                        thumb += `${el.thumb.media.baseUri}${el.thumb.media.types[0].c.replace("<prettyName>", el.thumb.media.prettyName)}?token=${el.thumb.media.token[0]}`;
                    }
									}catch(ex){
										console.log("error: thumbnail not parsed",ex);
									}
                    let thTitl = "no thumb";
                    if (el.thumb) {
                        thTitl = el.thumb.title;
                    }
                    tex += `
				<div class='dA_DragnFav_group ${hiddengroups.includes(el.folderId)?"dA_DragnFav_markhide":""}' folderId='${el.folderId}' ${hiddengroups.includes(el.folderId)?"style='display:none;'":""}>
					<img src='${thumb}' alt='${thTitl}' title='${thTitl}'/>
					<div class='dA_DragnFav_groupTitle'>${el.name}</div>
					<div class='dA_DragnFav_inside'>${imgHook}</div>
					<div class='dA_DragnFav_over dA_DragnFav_left'><p>Add</p></div>
					<div class='dA_DragnFav_over dA_DragnFav_right'><p>Move</p></div>
				</div>
				`.replace(/\s\s+/g, '');
                    contextTex += `<li class='dA_DragnFav_group ${hiddengroups.includes(el.folderId)?"dA_DragnFav_markhide":""}' folderId='${el.folderId}' ${hiddengroups.includes(el.folderId)?"style='display:none;'":""}>
					<div class='dA_DragnFav_wrapImg'><img src='${thumb}' alt='${thTitl}' title='${thTitl}'/></div>
					<div class='dA_DragnFav_groupTitle'>${el.name}</div>
					<div class='dA_DragnFav_inside'>${imgHook}</div>
					<div class='dA_DragnFav_over dA_DragnFav_left'><p>Add</p></div>
					<div class='dA_DragnFav_over dA_DragnFav_right'><p>Move</p></div>
				</li>`.replace(/\s\s+/g, '');
                });
                let header = $("#dA_DragnFav_header");
                header.html(tex).append(`<div class='dA_DragnFav_sets'>${imgGear}</div>`);
                let context = $("#dA_DragnFav_drowDown").html(contextTex);

                $.each(groupOrder, function(i, folderid) {
                    let $target = header.find(`[folderid='${folderid}']`);
                    $target.appendTo(header); // or prependTo for reverse
                });

                $("#dA_DragnFav_header div.dA_DragnFav_group").click(function(ev) {
                    if (settingmode && !$(this).hasClass("noClick")) { //hide toggle click in setting-mode
                        let fid = parseInt($(this).attr("folderId"));
                        let fhidInd = hiddengroups.indexOf(fid);
                        if (fhidInd > -1) {
                            hiddengroups.splice(fhidInd, 1);
                            $(this).removeClass("dA_DragnFav_markhide");
                        } else {
                            hiddengroups.push(fid);
                            $(this).addClass("dA_DragnFav_markhide");
                        }
                        GM.setValue("hiddengroups", JSON.stringify(hiddengroups));
                    }
                });

                //fav-header drag receivable
                $("#dA_DragnFav_header div.dA_DragnFav_over").on('dragover', false).on("dragenter", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0.7");
                }).on("dragleave", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0");
                }).on("drop", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0");
                    itemDroppedOnCol(ev, $(ev.target).hasClass("dA_DragnFav_left"));
                });

                $("#dA_DragnFav_drowDown div.dA_DragnFav_over").on('mouseover', false).on("mouseenter", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0.7");
                }).on("mouseleave", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0");
                }).on("click", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0");
                    let id = document.getElementById("dA_DragnFav_drowDown").getAttribute("ImgId");
                    itemDroppedOnCol(ev, $(ev.target).hasClass("dA_DragnFav_left"), id);
                });

                //settings open on drag, close on click
                $("#dA_DragnFav_header div.dA_DragnFav_sets").on('dragover', false).on("dragenter", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                }).on("dragleave", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                }).on("drop", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    enterSettingMode();
                }).click(function(ev) {
                    leaveSettingMode();
                });

                resolve(id);
                return;
            });
        })).then(markCollections);
    }

    function markCollections(id) {
        if (id == null) return;
        $(".dA_DragnFav_inside").css("visibility", "");
        $(".dA_DragnFav_group").css("background-color", "");
        // grIDs = [];
        inCollections(id).then(ret => {
            if (!ret.collectionIds) {
                console.log("error checking collections:", ret, id);
                return;
            }
            ret.collectionIds.forEach(el => {
                $("[folderId=" + el + "]").find(".dA_DragnFav_inside").css("visibility", "visible");
                $("[folderId=" + el + "]").closest(".dA_DragnFav_group").css("background-color", "rgb(180, 255, 200)");
            });
            grIDs = ret.collectionIds;
        });
    }

    function dragEnd() {
        if (!settingmode) {
            $("#dA_DragnFav_header").fadeOut();
        }
    }

    function dragStart(ev) {
        $("#dA_DragnFav_header").css("display", "flex");
        $("div.dA_DragnFav_over").css("opacity", "0");

        let drEl = ev.target.closest("[devId]");
        //let mode = $(this).attr("dA_DragnFav");
        let id;

        id = drEl.getAttribute("devId");
        if (!id) {
            console.log("error: ID can not be fetched", drEl, $(this), $(ev.target), $(ev.target).attr("href"), document.querySelector("meta[property='og:url']").getAttribute("content"));
        }
        ev.originalEvent.dataTransfer.setData('id', id);

        fillHeader(id);
    }


    //make deviations draggable, passing link with ID
    function makeDraggable() {
        //thumbs in links
        let els = $("a[data-hook='deviation_link']").not("[dA_DragnFav]").attr("dA_DragnFav", 1).attr("draggable", "true");
        if (els.length > 0) {
            els.each((ind, el) => { el.setAttribute("devId", el.href.match(/\d+$/)[0]) });
            els.on("dragstart", dragStart).on("dragend", dragEnd);
        }

        //fullview
        els = $("[data-hook='art_stage'] img").not("[dA_DragnFav]").attr("dA_DragnFav", 2).attr("draggable", "true");
        if (els.length > 0) {
            els.each((ind, el) => {
                el.setAttribute("devId", location.href.match(/deviantart.com\/[^\/]*\/art\/[^\/]*?(\d+)$/)[1]
                    // document.querySelector("meta[property='og:url']").getAttribute("content").match(/\d+$/)[0]
                )
            });
            els.on("dragstart", dragStart).on("dragend", dragEnd);
        }

        //fav button
        els = $("button[data-hook=\"fave_button\"]").not("[dA_DragnFav]").attr("dA_DragnFav", 1);
        if (els.length > 0) {
            els.each((ind, el) => {
                let it = el.closest("section").querySelector("a[data-hook='deviation_link']");
                if (it == null) it = location.href.match(/-(\d\d+)([\?#].*)?$/)[1];
                else it = it.href.match(/-(\d\d+)(\?.*)?$/)[1];
                el.setAttribute("devId", it);
            });
            els.contextmenu(function(e) {
                e.preventDefault();
                let el = e.target.closest("button");
                let id = el.getAttribute("devId");
                fillHeader(id);
                $('#dA_DragnFav_drowDown').attr("ImgId", id).css({ 'top': e.pageY, 'left': e.pageX }).show();
            });

            let plusImg = document.createElementNS('http://www.w3.org/2000/svg', "path");
            plusImg.setAttribute("stroke", "#0A0");
            plusImg.setAttribute("stroke-width", "4");
            plusImg.setAttribute("stroke-opacity", "0.8");
            plusImg.setAttribute("d", "M12 18H24M18 12V24");
            els.find("svg").append(plusImg);
        }
        els = $("div[data-hook=\"deviation_std_thumb\"] button").filter((i, el) => {
            return null != el.querySelector("svg path[d*='M13.71 3.408A1 1 0 0012.904 3h-1.85l-.119.007a1 1 0 00']");
        }).not("[dA_DragnFav]").attr("dA_DragnFav", 1);
        // console.log(els);
        if (els.length > 0) {
            els.each((ind, el) => {
                let it = el.closest("div[data-hook=\"deviation_std_thumb\"]").querySelector("a[data-hook='deviation_link']");
                if (it == null) it = location.href.match(/-(\d\d+)([\?#].*)?$/)[1];
                else it = it.href.match(/-(\d\d+)(\?.*)?$/)[1];
                el.setAttribute("devId", it);
            });
            els.contextmenu(function(e) {
                e.preventDefault();
                let el = e.target.closest("button");
                let id = el.getAttribute("devId");
                fillHeader(id);
                $('#dA_DragnFav_drowDown').attr("ImgId", id).css({ 'top': e.pageY, 'left': e.pageX }).show();
            });

            let plusImg = document.createElementNS('http://www.w3.org/2000/svg', "path");
            plusImg.setAttribute("stroke", "#0A0");
            plusImg.setAttribute("stroke-width", "4");
            plusImg.setAttribute("stroke-opacity", "0.8");
            plusImg.setAttribute("d", "M12 18H24M18 12V24");
            els.find("svg").append(plusImg);
        }

    }

    //runs every interval
    function init() {
        if (document.getElementById("dA_DragnFav_header") == null) {
            $("header[role='banner']").after("<div id='dA_DragnFav_header'>");
            headerfilled = false;
        }
        makeDraggable();
        if (document.getElementById("dA_DragnFav_styles") == null) {
            $("head").append(
                '<link id="dA_DragnFav_styles"' +
                'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css" ' +
                'rel="stylesheet" type="text/css">'
            );
        }
        if (document.getElementById("dA_DragnFav_drowDown") == null) {
            $("body").append("<ul id='dA_DragnFav_drowDown'></ul>");
            $("#dA_DragnFav_drowDown").hide();
        }
        $(document).click(function(ev) { $("#dA_DragnFav_drowDown").hide(); })
    }

    if (window.top === window.self) {
        GM.getValue("hiddengroups", "").then(ret => {
            if (ret != "") {
                hiddengroups = JSON.parse(ret);
            }
            return GM.getValue("groupOrder", "");
        }).then(ret => {
            if (ret != "") {
                groupOrder = JSON.parse(ret);
            }
            setInterval(init, 1000); // check every second
        });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址