Toyhouse Quickdial

Create a "quick dial" list of your most-used Toyhouse characters, and add them to image uploads without using the character select widget.

// ==UserScript==
// @name         Toyhouse Quickdial
// @namespace    http://circlejourney.net/
// @version      2024.2
// @description  Create a "quick dial" list of your most-used Toyhouse characters, and add them to image uploads without using the character select widget.
// @author       You
// @match        https://toyhou.se/~images/upload*
// @match        https://toyhou.se/~images/edit*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=toyhou.se
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require   	 https://unpkg.com/@popperjs/core@2
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';
    const frame = document.createElement('div');
    const style = document.createElement('style');
    style.innerHTML = "#quickdial-selector .character-name-badge {font-size: 10pt; cursor: pointer } .quickdial-add, .quickdial-delete { padding: 0.2rem 0.4rem; height: auto !important; } .char-nickname { margin-left: -5px }";
    document.body.appendChild(frame);
    document.head.appendChild(style);

    const formSettings = {
        'id': 'quickdial',
        'title': "Toyhouse Quickdial",
        'css': "#quickdial { border-radius: 0.5rem; border: none; color: black; z-index: 1000 !important; } #quickdial * { font-family: inherit; } #quickdial_wrapper { padding: 1rem; } #quickdial_char_display { gap: 0.5rem; }  .quickdial_char { white-space: nowrap }",
        'frame': frame,
        'fields': {
            "info": {
                'section': "Add characters",
                "type": "hidden"
            },
            "characters": {
                'label': 'Link to character or tab profile (e.g. <code>https://toyhou.se/12345.character-name/67890.tab-name</code>)',
                'type': 'text'
            },
            "add": {
                'label': 'Add character',
                'type': 'button',
                'click': function(){
                    addFromURL($("#quickdial_field_characters").val().trim());
                }
            },
            "manage": {
                'section': "Your quickdial characters",
                "type": "hidden"
            }
        },
        "events": {
            "close": function() {
                syncSelector();
            },
            "open": function() {
                $("#quickdial_section_header_0").before([
                    "<p>Toyhouse Quickdial is a utility that lets you add your most-used Toyhouse characters to image uploads without using the character select widget. Start adding characters/tabs to quickdial with the form below, then you can add them to any image upload with the shortcuts below the 'Add characters' panel. Still in beta, report bugs to <a href='https://toyhou.se/~messages/create/circlejourney' target='_blank'>circlejourney</a>.",
                    "<p>New in 16 June 2024 version: Wheel-click the character's name to open their profile in a new tab.</p>"
                ]);
                $("#quickdial_section_header_1").after("<div id='quickdial_char_display' class='flex-column p-1'></div>");
                $("#quickdial_saveBtn").remove();
                syncSelector();
            },
            "reset": async function() {
                const characters = await GM.getValue("characters");
                characters.forEach(function(character, i){
                    remove(character.id);
                });
                GM.deleteValue("characters");
            }
        }
    };

    const form = new GM_config(formSettings);
    const isCreate = location.pathname.indexOf("upload") > -1;
    let poppable, thisUser, charcard;

    window.addEventListener("load", function() {
        $(".display-user-username").eq(0).text();
        const formButton = $("<a class='quickdial-button btn btn-sm btn-primary w-100' href='#' onclick='event.preventDefault()'></a>").on("click", function(){ form.open() }).text("Add/remove quickdial characters");
        const quickdialBar = $("<div id='quickdial-wrapper' class='mt-3'><hr></div>")
        .append("<h4><i class='fa fa-phone'></i> Quickdial (click to add to image's character list)</h4>").append("<div id='quickdial-selector' class='p-1 d-flex flex-wrap'></div>").append(formButton);

        if(isCreate) charcard = $("#content .col-lg-5 .mb-3").first();
        else charcard = $("#content .col-xl-4 .mb-3").eq(1);
        $(".card-block", charcard).append(quickdialBar);

        syncSelector();

        poppable = $("<a data-toggle='tooltip' title='Placeholder' href='#'></a>");
        $(".btn[th-clone-trigger]", charcard).eq(0).on("click", function(){
            setTimeout(function() {
                const lastInput = $(".clone-dst .character-select-widget:last-child .character-select-selected-input", charcard);
                lastInput.on("change", function() {
                    if($(this).data("handler-attached")) return false;
                    appendButtons($(this).closest(".character-select-selected").eq(0));
                    $(this).data("handler-attached", true);
                });
            });
        });

        appendButtons($(".clone-dst .character-select-selected")[0])
    });

    async function add(id) {
        let addcharacters;
        let characters = await GM.getValue("characters") || [];
        let usechars = pluck(characters, "id");
        if(usechars.indexOf(id) > -1) return true;

        //if($("#quickdial_char_display .char-"+id).length) return true;
        let name;
        await getName(id).then((foundname)=>{ name=foundname }, function(){
            const note = $("<span class='badge badge-primary'> Character doesn't exist or unauthorised</span>");
            $("#quickdial_field_characters").after(note);
            setTimeout(function(){
                $(note).animate({ opacity: 0 }, { easing: "linear", duration: 500, complete: function(){$(this).remove()} })
            }, 1000);
        });
        if(!name) return true;

        const charselect = $(".character-select-widget .character-select-selected.char-"+id);
        $(charselect).find(".quickdial-add").addClass("hide");
        $(charselect).find(".quickdial-delete").removeClass("hide");
        form.set("characters", "");

        const thumb = await getThumbnail(id);
        characters.push({
            "id": id,
            "thumb": thumb || null,
            "name": name
        });

        GM.setValue("characters", characters).then(syncSelector);
    }

    async function remove(id) {
        const characters = await GM.getValue("characters");
        if(!characters || !characters.length) return false;
        characters.splice(findFirstIndex(characters, id), 1);

        GM.setValue("characters", characters).then(syncSelector);

        const classname = ".char-"+id;
        $("#quickdial_char_display "+classname).remove();
        $("#quickdial-selector "+classname).remove();
        const charselect = $(".character-select-widget .character-select-selected"+classname);
        $(charselect).find(".quickdial-add").removeClass("hide");
        $(charselect).find(".quickdial-delete").addClass("hide");
    }

    async function syncSelector() {
        let append = [];
        const characters = await GM.getValue("characters");
        if(!characters || !characters.length) return false;
        $("#quickdial-selector").empty()
        $("#quickdial_char_display").empty()
        for(let i=0; i<characters.length; i++) {
            const {id, thumb, name, nickname} = characters[i];
            const url = "https://toyhou.se/"+id+".";
            let tooltipTitle = nickname || "#"+id;
            const clickable = $(createBadge(id, thumb, name)).attr({ "data-toggle": "tooltip", "title": tooltipTitle });
            if(nickname) $(clickable).append("<small class='char-nickname'>"+(nickname || "")+"</small>");
            const badgeContent = $(".character-name-badge", clickable).clone();
            $(".character-name-badge", clickable).replaceWith(
                $("<a></a>").html(badgeContent).attr({ "href": url, "target": "_blank" })
            );
          	clickable.tooltip();
            $(clickable).find(".character-name-badge")
                .on("click", function(e){ e.preventDefault(); attachCharacter(this, id) });
            $("#quickdial-selector").append(clickable);

            const unclickable = clickable.clone().attr({ "data-toggle": "tooltip", "title": tooltipTitle });
            $("small", unclickable).remove();

          	unclickable.tooltip();
            const deletebutton = $('<a class="character-select-selected-remove btn btn-danger btn-square" href="#"><i class="fi-trash"></i></a>')
            .on("click", function(e){ e.preventDefault(); remove(id) });
            const nicknameInput = $("<input>").addClass("quickdial_char_nickname").attr({ "maxlength": 255, "placeholder": "Nickname" }).val(nickname || "");
            const nicknameUpdate = $("<button></button>").html("<i class='fi-check'></i>").addClass("btn btn-sm btn-primary quickdial_char_nickname_button")
            .on("click", async function() {
                const characters = await GM.getValue("characters");
                if(!characters || !characters.length) return false;
                const found = findFirstIndex(characters, id);
                characters[found].nickname = $(this).siblings(".quickdial_char_nickname").val();
                const nameBadge = $(this).siblings(".character-select-selected-character").attr({
                    "title": characters[found].nickname,
                    "data-original-title": characters[found].nickname
                });
                nameBadge.tooltip("update");
                await GM.setValue("characters", characters);
                const note = $("<span class='badge badge-success'> Saved</span>");
                $(this).after(note);
                setTimeout(function(){
                    $(note).animate({ opacity: 0 }, { easing: "linear", duration: 500, complete: function(){$(this).remove()} })
                }, 500);
            });
            const badgewrapper = $("<div></div>").addClass("quickdial_char form-inline char-"+id).append([deletebutton, unclickable, nicknameInput, nicknameUpdate]);
            $("#quickdial_char_display").append(badgewrapper);
        }
    }

    function attachCharacter(caller, id) {
        $(".btn[th-clone-trigger]", charcard).eq(0).click();
        const charselect = isCreate ? $(".clone-dst .character-select-widget:last-child", charcard) : $("form.mt-3", charcard);
        $(".character-select-selectors", charselect).addClass("hide");
        $(".character-select-selected", charselect).removeClass("hide");
        $(".character-select-selected-input", charselect).val(id);
        appendButtons(charselect.find(".character-select-selected")[0]);
        const formbadge = $(caller).closest(".character-select-selected-character").clone();
        $(".hide", formbadge).removeClass("hide");
        $(".char-nickname", formbadge).remove();
        $(".character-select-selected-character", charselect).replaceWith(formbadge);
    }

    function createBadge(id, thumb, name) {
        return `<span class='character-select-selected-character char-${id}'>
                        <img src='${thumb}'>
                        <span class='btn btn-sm btn-primary mr-1 character-name-badge'>${name}</span>
                        <small class='hide mr-1 character-name-number'>#${id}</small>
                        </span>`
    }

    async function getName(val) {
        const url = "https://toyhou.se/"+val+"./";
        let found;
        await $.get(url, function(d){
            //if($(d).find(".display-user").eq(0).text().trim() != thisUser) return false;
            found = $(d).find(".profile-name-info h1").text();
        }).fail(function() { found = false; });
        return found;
    }

    async function getThumbnail(val) {
        const name = val + "?" + Date.now();
        const urls = [ "https://file.toyhou.se/characters/" + name, "https://f2.toyhou.se/file/f2-toyhou-se/characters/" + name ];
        let found;
        for(let i=0; i<urls.length; i++) {
            const url = urls[i];
            const img = new Image();
            img.src = url;
            const promise1 = new Promise(function(resolve, reject) {
                img.onload = function(){ resolve(url); }
                img.onerror = function(){ reject(url); }
            });
            await promise1.then(function(url) {
                found = url;
            }, ()=>{});
        }
        console.log(found);
        return found;
    }

    function pluck(objectArray, key){
        let result = [];
        objectArray.forEach(function(item, k) {
            result[k] = item[key];
        });
        return result;
    }

    function findFirstIndex(characters, id) {
        return characters.findIndex(i => i.id == id);
    }

    async function appendButtons(wrapper) {
        if($(".quickdial-add", wrapper).length) return false;
        const id = $(".character-select-selected-input", wrapper).val();
        $(wrapper).addClass("char-"+id);
        const characters = await GM.getValue("characters");
        let hasBeenAdded = false;
        if(pluck(characters, "id").indexOf(id) > -1) hasBeenAdded = true;

        const addButton = poppable.clone().addClass('quickdial-add btn btn-primary ml-1').attr("title", "Add to quickdial").html("<i class='fa fa-plus'></i> <i class='fa fa-phone'></i></a>").on("click", function(e){ e.preventDefault(); callFromForm("add", this); });
      	addButton.tooltip();
        const deleteButton = poppable.clone().addClass('quickdial-delete btn btn-primary ml-1').attr("title", "Remove from quickdial").html("<i class='fa fa-times'></i> <i class='fa fa-phone'></i></a>").on("click", function(e){ e.preventDefault(); callFromForm("remove", this); }).tooltip();
        if(hasBeenAdded) $(addButton).addClass("hide");
        else $(deleteButton).addClass("hide");
        $(wrapper).append([addButton, deleteButton]);
    }

    function addFromURL(url) {
        const matches = [...url.matchAll(/([0-9]+)\.[a-z0-9\-]*/g)];
        const id = matches[matches.length-1][1];
        add(id);
    }

    function callFromForm(fn, caller) {
        const id = $(caller).closest(".character-select-selected").find(".character-select-selected-input").val();
        if(fn == "add") add(id);
        if(fn == "remove") remove(id);
    }

})();

QingJ © 2025

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