VRChat LittleONE

VRChat Little Enhancer

安装此脚本
作者推荐脚本

您可能也喜欢VRChat Web Pages Extender

安装此脚本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         VRChat LittleONE
// @namespace    https://greasyfork.org/zh-TW/scripts/372162-vrchat-littleone
// @version      1.27
// @description  VRChat Little Enhancer
// @author       Tast
// @include      /.*?:\/\/.*?vrchat.*?\..*?(home|launch|api).*?/
// @include      /.*?:\/\/.*?vrchat.*?\..*?(friendlist|favoritelist).*?/
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceURL
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @require      https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @require      https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js
// @resource     JqueryUIcss    https://code.jquery.com/ui/1.12.1/themes/ui-darkness/jquery-ui.css
// @resource     private_image  https://assets.vrchat.com/www/images/default_private_image.png
// @resource     JumpICON       https://image.flaticon.com/icons/svg/1215/1215194.svg
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/locale/zh-tw.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.4.0/chroma.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/pushy/1.3.0/js/pushy.min.js
// @require      https://greasyfork.org/scripts/407743-jquery-simple-websocket/code/jquery-simple-websocket.js?version=830687
// @icon         https://assets.vrchat.com/www/images/favicon.png
// @run-at       document-start
// @compatible   Chrome
// ==/UserScript==
// https://cdnjs.com/libraries/jqueryui
// @require      https://code.jquery.com/jquery-2.2.4.min.js
// @resource     JqueryUIcss    https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/trontastic/theme.min.css

// https://remotestoragejs.readthedocs.io/en/latest/getting-started/initialize-and-configure.html

// Chroma.js
// https://github.com/gka/chroma.js/
// https://gka.github.io/chroma.js/#chroma-blend

// moment https://momentjs.com/
// https://cdnjs.com/libraries/moment.js/

// https://github.com/js-cookie/js-cookie
// https://developer.mozilla.org/zh-TW/docs/Web/API/Window.onpopstate
// https://www.oxxostudio.tw/articles/201706/javascript-promise-settimeout.html

//alert(unescape("%u541B%u306E%u540D%u524D%u306F"));

//<span aria-hidden="true" class="fa fa-cog fa-2x fa-spin"></span>

/*
// https://github.com/jbloemendal/jquery-simple-websocket
var webSocket = $.simpleWebSocket({ url: 'wss://pipeline.vrchat.cloud/?authToken=' });
webSocket.listen(function(message) {
    //console.log(message.text);
    LogInfo(message)
});
*/
var url = window.location.href;

var WorldTemp   = {};
var UserTemp    = {};
let OwnerData   = GM_getValue("ScriptUserData",{ friends:[] });
//var StatusColor = {  "join me":"#1FD1ED", "active":"#19A38F", "busy":"darkred" };
var StatusColor = {  "join me":"#1FD1ED", "active":"lightgreen", "busy":"darkred", "ask me":"#e88134" };
var TrustedData = {//"admin_official_thumbnail" : ["#FFFFFF" , "Admin Thumbnailr" ] by Ex Excelsior (Ex) https://goo.gl/3qPscU
                     "admin_moderator"      : ["#8B0000" , "Admin"       ]  // 暗紅 by Ex Excelsior (Ex) https://goo.gl/3qPscU
                    ,"system_troll"         : ["#808080" , "Troll"       ]  // 灰色 by Ex Excelsior (Ex) https://goo.gl/3qPscU
                    ,"system_probable_troll": ["#808080" , "Troll??"     ]  // 灰色 by Ex Excelsior (Ex) https://goo.gl/3qPscU
                    ,"system_trust_legend"  : ["yellow"  , "Veteran"     ]  // 金色
                    ,"system_trust_veteran" : ["#8143E6" , "Trusted User"]  // 紫色
                    ,"system_trust_trusted" : ["#FF7B42" , "Known User"  ]  // 橘色
                    ,"system_trust_known"   : ["#2BCF5C" , "User"        ]  // 綠色
                    ,"system_trust_intermediate" : ["#000080" , "Intermediate" ] //New User+ (New User > User) by Ex Excelsior (Ex) https://goo.gl/3qPscU
                    ,"system_trust_basic"   : ["#1778FF" , "New User"    ]  // 藍色 
                    ,"system_legend"        : ["#FF0000" , "Legend"      ]};// 紅色 by Ex Excelsior (Ex) https://goo.gl/3qPscU
var RoomType    = {  "private"              : ["Private" , "#1fd1ed"]
                    ,"~hidden"              : ["Friends+", "#8143E6"]
                    ,"~friends"             : ["Friends" , "#FF7B42"]
                    ,"~canRequestInvite"    : ["Invite+" , "#2BCF5C"]
                    ,"~private"             : ["Invite"  , "#1778FF"]
                    ,"~pub"                 : ["Public"  , "yellow" ] };
//                    ,"~null"                : ["Public"  , "yellow" ] };
var WorldType   = {  "public"               : ["check"      , "lightgreen"]
                    ,"hidden"               : ["exclamation", "#ff4d4d"   ]
                    ,"private"              : ["exclamation", "lightpink" ] };
var modType     = {  mute       : { re:"unmute"     , text: "Mute"          } 
                    ,unmute     : { re:"mute"       , text: "UnMute"        }
                    ,hideAvatar : { re:"showAvatar" , text: "HideAvatar"    }
                    ,showAvatar : { re:"hideAvatar" , text: "ShowAvatar"    } 
                    ,block      : { re:"unmute"     , text: "Block"         } 
                    ,unblock    : { re:"block"      , text: "Unblock"       } };


let JqueryUIcss = `<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/ui-darkness/jquery-ui.css" type="text/css">`;

// 延長Cookie登入狀態
function CookieExtend(){ //return;
    if(window.location.href.match("\/login")) return;
    
    var auth    = Cookies.get('auth');
    var apiKey  = Cookies.get('apiKey');
    if(auth)      Cookies.set('auth'  , auth  , { expires: 7 });
    if(apiKey)    Cookies.set('apiKey', apiKey, { expires: 7 });
}

MainStart();
function MainStart(){
    InsertCustomCSS();
    $(document).ready(JumpMainDomainCheck);
    
    if(url.match("\/launch\?")){
        if(url.match(regexUnityID("wld",url) + ":") || url.match(regexUnityID("wrld",url) + ":")){
            var url2 = url .replace(regexUnityID("wld" ,url) + ":",regexUnityID("wld" ,url) + "&instanceId=");
                url2 = url2.replace(regexUnityID("wrld",url) + ":",regexUnityID("wrld",url) + "&instanceId=");
            window.location.href = url2;
            return;
        }
        //$(document).ready(function(){ setTimeout(LaunchPage,1000 * 1); });
    }
    if(url.match("\/home.?"))           HomePage();
    if(url.match("\/friendlist.?"))     FriendList();
    if(url.match("\/favoritelist.?"))   GetFavOfWorlds();
}

function JumpMainDomainCheck(){
    if(document.location.host == "www.vrchat.net" || document.location.host == "vrchat.net") return;
    
    // https://www.w3schools.com/cssref/tryit.asp?filename=trycss_cursor
    $("body").append(""
    + "<div class='noselect' style='display:inline-block;position:fixed;left:10px;bottom:5px;z-index:500;cursor:alias;'>"
        + '<img id="JumpMainDomain" width="17" title="Jump to www.vrchat.net" src="' + GetTMRes("JumpICON","data:image/svg+xml;") + '" />'
    + "</div>")
    
    $("#JumpMainDomain").click(function(){
        document.location.host = "www.vrchat.net";
    })
}

var LastHomePage = document.location.pathname;
function HomePage(){
    $.get( "/api/1/auth/user").done(function( json ){ //console.error(json);
        if(!json["id"])
            json = JSON.parse(json);
        if(json["id"]){
            GM_setValue("ScriptUserData",json);
            OwnerData = json;
        }
    })
    
    /*
    if(url.match("\/home\/launch\?")){
        window.location.href = url.replace("\/home\/launch\?","\/launch\?");
        return;
    }
    */
    
    setTimeout (CookieExtend,1000 *  5);
    setInterval(CookieExtend,1000 * 15);
    window.onbeforeunload = function(e) { CookieExtend(); };
    
    // https://stackoverflow.com/a/30680994
    $("head").append("<style>::-webkit-scrollbar {width: 0px;  /* remove scrollbar space */background: transparent;  /* optional: just make scrollbar invisible */}</style>");
    
    // https://stackoverflow.com/a/12471484
    //$("head").append("<style>img.steam {position: relative;margin: auto;top: 0;left: 0;right: 0;bottom: 0;}</style>");
    //$("head").append("<style>img.steam {align:middle;}</style>");
    //$("head").append("<style>.verticalcenter {display: table-cell;height: auto;vertical-align: middle;}</style>");
    
    setInterval(function(){
        HomePageFunc();
        
        RunOnce("img.img-thumbnail.rounded-circle.float-left.home-avatar:eq(0)"
                                                        ,"UserBaseData"         ,UserBaseData)
        RunOnce("div.home-content div.center-block.text-center > h2:contains('Hello there,'):eq(0)"
                                                        ,"MainHome"             ,MainHome)
        RunOnce("h3.subheader:contains('steam_'):eq(0)" ,"SteamIDLinkToPage"    ,SteamIDLinkToPage);
        RunOnce("div.animated.fadeIn.card > h3.card-header:contains('Password')"
                                                        ,"SetPublicAvatar"      ,SetPublicAvatar);
        //RunOnce("div.card.card-body.bg-primary:hidden"  ,"UserStatus"           ,UserStatus);
        RunOnce("div.usercard.size-huge.card"           ,"UserStatus"           ,UserStatus);
        RunOnce("img.profile-thumbnail.img-rounded"     ,"ExpandWorldThumbnail" ,ExpandWorldThumbnail);
        RunOnce("button#login-form-submit"              ,"LoginGoBack"          ,LoginGoBack);
        RunOnce("div.home-content > div.row:eq(0)"      ,"WorldsWithFriends"    ,WorldsWithFriends);
        RunOnce("div.home-content div.col-md-12 small:contains('— by')"
                                                        ,"WorldToVRCList"       ,WorldToVRCList);
        RunOnce("div.home-content div.col-md-4 div.btn-group:contains('Launch')"
                                                        ,"WorldPageFav"         ,WorldPageFav)
        RunOnce("div.friend-group:eq(0)"                ,"HideOnline"           ,function(element){
            $(element).find("h4:contains('Online'):eq(0)").hide();
        })
        RunOnce("div.flex-shrink-1:eq(0) a.launch-btn:eq(0)"  ,"LaunchOptions"  ,LaunchPage);
        
        if(document.location.pathname != LastHomePage){
            if( document.location.pathname.match(/(\/login|\/register|\/password)/) ){
                //console.error(document.location.pathname);
            }
            else LastHomePage = document.location.pathname;
        }
    },1000 * 0.7);
}

//MainDialogTabs();
function MainDialogTabs(){
    if($("#MainDialogTab").length) return;
    
    $( "#dialog" ).dialog({
      autoOpen: false
    });
    $( "#MainDialogTabs" ).tabs();
    
    $("body").append(`
        <div id="MainDialogTab" title="VRChat LittleONE" style="display:none;"><div id="MainDialogTabs">
            <ul>
                <li><a href="#tabs-1">Nunc tincidunt</a></li>
                <li><a href="#tabs-2">Proin dolor</a></li>
                <li><a href="#tabs-3">Aenean lacinia</a></li>
            </ul>
            <div id="tabs-1">
                <p>ccc1</p>
            </div>
            <div id="tabs-2">
                <p>ccc2</p>
            </div>
            <div id="tabs-3">
                <p>ccc3</p>
            </div>
        </div></div>
    `)
}

function MainHome(){
    $("div.home-content > div:eq(0)")
    //.append(`
    .prepend(`
    <h3>Function Page</h3>
    <button type="button" class="btn btn-primary" id="Lone_WWF_main_Launch">Worlds With Friends</button>
    <button type="button" class="btn btn-primary" id="">
        <a target="_blank" style="color:black;" href="/friendlist">Friend List</a>
    </button>
    <button type="button" class="btn btn-primary" id="">
        <a target="_blank" style="color:black;" href="/favoritelist">Favorite World List</a>
    </button>
    <!--<div class="row">
        <div class="col-12">-->
            <h3>Blocked/Muted/Hided&Showed Avatar by <font color="yellow">someone</font>.<div id="Lone_BlockLoad_Data_Count" style="display:inline;"></div></h3>
            <div class="row">
                <div class="col-md-4">
                    <button type="button" class="btn btn-primary" id="Lone_BlockLoad">Who against you</button>
                    <div id="Lone_BlockLoad_Data"  style="display:-webkit-inline-box;font-family:Segoe UI;"></div>
                    <div id="Lone_BlockLoad_Data2" style="display:-webkit-inline-box;font-family:Segoe UI;"></div>
                </div>
            </div>
        </div>
    <!--</div>-->
    <!--<div class="row">
        <div class="col-12">-->
            <h3>Block/mute/hide&showAvatar someone by <font color="yellow">you</font>.<div id="Lone_BlockLoad_DataYou_Count" style="display:inline;"></div></h3>
            <div class="row">
                <div class="col-md-4">
                    <button type="button" class="btn btn-primary" id="Lone_BlockLoadYou">Load your moderation</button>
                    <div id="Lone_BlockLoad_DataYou"  style="display:-webkit-inline-box;font-family:Segoe UI;"></div>
                    <div id="Lone_BlockLoad_DataYou2" style="display:-webkit-inline-box;font-family:Segoe UI;"></div>
                </div>
            </div>
        </div>
    <!--</div>-->`);
    
    $("#Lone_WWF_main_Launch").click(function(){
        $("div.home-content:eq(0) > div:eq(0)").html(`<div class="row WorldsWithFriends"><div class="col-12"></div></div>`);
        WorldsWithFriends();
        $("#WorldsWithFriends").click();
    })
    
    $("#Lone_BlockLoad").click(function(){
        $("#Lone_BlockLoad").prop('disabled', true);
        $("#Lone_BlockLoad_Data , #Lone_BlockLoad_Data2").html("");
        
        $.get( "/api/1/auth/user/playermoderated").done(function( json ){ //console.error(json);
            GM_setValue("playermoderated",json || []);
            $("#Lone_BlockLoad_Data_Count").html("(" + json.length + ")");
            var block = "", blockCount = 0;
            var mute  = "", muteCount  = 0;
            var hide  = "", hideCount  = 0;
            var show  = "", showCount  = 0;
            $("#Lone_BlockLoad").prop('disabled', false);
            $(json).each(function(index, value){ //console.error( index + ": " + value );
                GM_setValue(value.sourceUserId + "_pastName",value.sourceDisplayName);
                value.created = moment(value.created).format("YYYY-MM-DD[T]HH:mm");
                if(value.type == "block"){ //console.error("block: " + value.sourceDisplayName);
                    blockCount = blockCount + 1;
                    block   = block
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:black;' href='/home/user/" + value.sourceUserId + "'>"
                        + value.sourceDisplayName + "</a>" + "<br>";
                }
                else if(value.type == "mute"){ //console.error("mute: " + value.sourceDisplayName);
                    muteCount = muteCount + 1;
                    mute   = mute
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:#99003d;' href='/home/user/" + value.sourceUserId + "'>"
                        + value.sourceDisplayName + "</a>" + "<br>";
                }
                else if(value.type == "hideAvatar"){ //console.error("mute: " + value.sourceDisplayName);
                    hideCount = hideCount + 1;
                    hide   = hide
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:#99003d;' href='/home/user/" + value.sourceUserId + "'>"
                        + value.sourceDisplayName + "</a>" + "<br>";
                }
                else if(value.type == "showAvatar"){ //console.error("mute: " + value.sourceDisplayName);
                    showCount = showCount + 1;
                    show   = show
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:#6b7886;' href='/home/user/" + value.sourceUserId + "'>"
                        + value.sourceDisplayName + "</a>" + "<br>";
                }
            })
            $("#Lone_BlockLoad_Data").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>'
                + 'Block you:  ' + blockCount + '<br>'
                +  block 
                + '</b></div>');
                
            $("#Lone_BlockLoad_Data").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>' 
                + 'Mute you:  ' + muteCount + '<br>'
                +  mute 
                + '</b></div>');
            
            $("#Lone_BlockLoad_Data2").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>' 
                + 'Hide your Avatar:  ' + hideCount + '<br>'
                +  hide 
                + '</b></div>');
            
            $("#Lone_BlockLoad_Data2").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>' 
                + 'Show your Avatar:  ' + showCount + '<br>'
                +  show 
                + '</b></div>');
            
        }).fail(function( xhr, status, error ) { console.error(error);
            $("#Lone_BlockLoad_Data").html("fetch error");
            $("#Lone_BlockLoad").prop('disabled', false);
        });
    })
    
    $("#Lone_BlockLoadYou").click(function(){
        $("#Lone_BlockLoadYou").prop('disabled', true);
        $("#Lone_BlockLoad_DataYou , #Lone_BlockLoad_DataYou2").html("");
        
        $.get( "/api/1/auth/user/playermoderations").done(function( json ){ //console.error(json);
            GM_setValue("playermoderations",json || []);
            $("#Lone_BlockLoad_DataYou_Count").html("(" + json.length + ")");
            var block = "", blockCount = 0;
            var mute  = "", muteCount  = 0;
            var hide  = "", hideCount  = 0;
            var show  = "", showCount  = 0;
            $("#Lone_BlockLoadYou").prop('disabled', false);
            $(json).each(function(index, value){ //console.error( index + ": " + value );
                GM_setValue(value.targetUserId + "_pastName",value.targetDisplayName);
                value.created = moment(value.created).format("YYYY-MM-DD[T]HH:mm");
                if(value.type == "block"){ //console.error("block: " + value.sourceDisplayName);
                    blockCount = blockCount + 1;
                    block   = block
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:black;' href='/home/user/" + value.targetUserId + "'>"
                        + value.targetDisplayName + "</a>" + "<br>";
                }
                else if(value.type == "mute"){ //console.error("mute: " + value.sourceDisplayName);
                    muteCount = muteCount + 1;
                    mute   = mute
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:#99003d;' href='/home/user/" + value.targetUserId + "'>"
                        + value.targetDisplayName + "</a>" + "<br>";
                }
                else if(value.type == "hideAvatar"){ //console.error("mute: " + value.sourceDisplayName);
                    hideCount = hideCount + 1;
                    hide   = hide
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:#99003d;' href='/home/user/" + value.targetUserId + "'>"
                        + value.targetDisplayName + "</a>" + "<br>";
                }
                else if(value.type == "showAvatar"){ //console.error("mute: " + value.sourceDisplayName);
                    showCount = showCount + 1;
                    show   = show
                        + "<font color='blue'>"   + value.created.split("T")[0] + "</font>&nbsp;"
                        + "<font color='green'>"  + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font>&nbsp;"
                        + "<a target='_blank' style='color:#6b7886;' href='/home/user/" + value.targetUserId + "'>"
                        + value.targetDisplayName + "</a>" + "<br>";
                }
            })
            $("#Lone_BlockLoad_DataYou").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>'
                + 'You block:  ' + blockCount + '<br>'
                +  block 
                + '</b></div>');
                
            $("#Lone_BlockLoad_DataYou").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>' 
                + 'You mute:  ' + muteCount + '<br>'
                +  mute 
                + '</b></div>');
            
            $("#Lone_BlockLoad_DataYou2").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>' 
                + 'You hideAvatar:  ' + hideCount + '<br>'
                +  hide 
                + '</b></div>');
                
            $("#Lone_BlockLoad_DataYou2").append('<br>'
                + '<div class="card card-body bg-primary lone_ignore"><b>' 
                + 'You ShowAvatar:  ' + showCount + '<br>'
                +  show 
                + '</b></div>');            
        }).fail(function( xhr, status, error ) { console.error(error);
            $("#Lone_BlockLoad_DataYou").html("fetch error");
            $("#Lone_BlockLoadYou").prop('disabled', false);
        });
    })
    
    // ===========================================================================
    // CREDIT
    let TM_JumpICON = GetTMRes("JumpICON","data:image/svg+xml;");
    $("div.home-content > div:last")
    .append(`
    <br>
    <div class="row">
        <div class="col-12">
            <h3><h3 style="float:right;"><font color="yellow">Credit</font></h3></h3>
            <div class="row">
                <div class="col-md-4"><img width="20" src="${TM_JumpICON}" /> Icons made by <a href="https://www.flaticon.com/authors/kiranshastry" title="Kiranshastry">Kiranshastry</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
            </div>
        </div>
    </div>
    `)
}

function WorldPageFav(element){
    var world_id = regexUnityID("(wrld|wld)",window.location.href);
    if(!world_id) return;
    
    $("head").append(`<style>
    .Lone_WorldPageFav .btn-secondary {
        display: none;
    }
    </style>`);
    
    $(element).prepend(`
    <div role="group" class="w-100 btn-group-lg btn-group-vertical Lone_WorldPageFav"><br>
        <button type="button" class="btn btn-primary" id="WorldPageFav">Favorite this World?</button>
        <button type="button" dType="worlds0" class="btn btn-secondary">Worlds1</button>
        <button type="button" dType="worlds2" class="btn btn-secondary">Worlds2</button>
        <button type="button" dType="worlds3" class="btn btn-secondary">Worlds3</button>
        <button type="button" dType="worlds4" class="btn btn-secondary">Worlds4</button>
        <br>
    </div>
    `);
    
    $("#WorldPageFav").click(function(){ $(".Lone_WorldPageFav .btn-secondary").slideToggle("fast"); });
    
    $(".Lone_WorldPageFav .btn-secondary").click(async function(){
        $(".Lone_WorldPageFav .btn-secondary").prop('disabled', true);
        let [data, err] = await getAPI("favorites",{
             method: 'POST'
            ,headers: { 'content-type': 'application/json' } 
            ,body: JSON.stringify({ type: "world", favoriteId: world_id, tags:$(this).attr("dType") })
        });
        $(".Lone_WorldPageFav .btn-secondary").prop('disabled', false);
        if(data)        alert("Favorited!");
        else if(err)    alert( (await err.json()).error.message || "Error!");
    });
}

function UserBaseData(element){
    var userID      = regexUnityID("usr" ,$(element).parent().attr("href"));
    var displayName = $(element).parent().parent().find("p.display-name").html();
    
    if(userID && displayName){
        GM_setValue("cur_userID"        ,userID)
        GM_setValue("cur_displayName"   ,displayName)
    }
    /*
    let ScriptUserData = GM_getValue("ScriptUserData",{ "friends": [] });
    let FriendsLength  = ScriptUserData["friends"].length;
    $(element).parent().parent().find("p.display-name:eq(0)").append(`
        <br><a style="float:right">Friends (${FriendsLength})</a>
    `)
    */
}

function WorldToVRCList(element){
    // "— by "
    $(element).contents().filter(function(){ return this.nodeType === 3; }).eq(0).remove();
    var world_id    = regexUnityID("(wrld|wld)",window.location.href);
    var world_name  = $("small.WorldToVRCList:eq(0)").parent().find("a:eq(0)").html();
    var user_name   = $("small.WorldToVRCList:eq(0) a[href*='usr_']").html();
    //$(element).parents("div.col-md-12:eq(0)").find("")
    var template_VRCWorldList = `
        <a target="_blank" href="%world-by-id%">—</a>&nbsp;
        <a target="_blank" href="%world-by-author%">by&nbsp;`
    
    $("small.WorldToVRCList:eq(0)").prepend(LoadFormatText(template_VRCWorldList,{  
         "%world-by-id%" : encodeURI(
            "https://www.google.com/search?q=site:www.vrcw.net \"" + world_id + "\" | " +
            "\"" + world_name + "\"")
        ,"%world-by-author%" : encodeURI("http://www.vrcw.net/search?utf8=%E2%9C%93&keyword=" + user_name)
    }).out)
}

function WorldsWithFriends(){
    $("div.home-content div.col-12:eq(0)")
    .prepend(`
        <div>
            <h3><a id='WorldsWithFriends' href='#' style='color:#67d781;'>Worlds with Friends</a></h3>
        </div>
    `);
    
    $("#WorldsWithFriends").click(function(){
        let col12 = $(this).parents("div.col-12:eq(0)").html(`
            <br>Fetching Data....
            <br>Request Times : (&nbsp;<a id="FetchTimes">0</a>&nbsp;)
            <br>Request Length: (&nbsp;<a id="FetchLength"></a>&nbsp;)
        `);
        
        let GFM_Online = $.extend(true, {}, GetFriendsMulti);
        GFM_Online.firstRun(async function(status, obj) {
            let json = obj.userArray || [];
            if(status == "keepGoing"){
                $("#FetchTimes" ).html(`${parseInt($("#FetchTimes").html()) + 1}`);
                $("#FetchLength").html(json.length);
                return;
            }
            await sleepNew(50);
            
            var WorldsJson = {};
            for (var i = 0; i < json.length; i++) { //console.error(json[i])
                var location = /(wrld|wld)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:\d+/gmi.exec(json[i].location);
                    location = location ?  location[0] : json[i].location;
                json[i]   .locationBase =  location;
                WorldsJson[location]    =  WorldsJson[location] ||    {"counter"  : 0};
                WorldsJson[location]["counter"] = WorldsJson[location]["counter"] + 1;
                WorldsJson[location]["room"]    = json[i].location;
                WorldsJson[location]["created"] = regexUnityID("usr",json[i].location) || "NoOwner";
                
                // 玩家暫存資料
                UserTemp[json[i].id] = json[i];
                GM_setValue(json[i].id,json[i])
            }
            WorldsJson = sortObject(WorldsJson);
            var privateData = WorldsJson["private"];
            delete            WorldsJson["private"];
            delete            WorldsJson["offline"];
                              WorldsJson["private"] = privateData || { "counter":0 };
            
            //RoomsType["publlic"]
            
            /*
            for (var i = 0; i < json.length; i++) { //console.error(json[i])
                WorldsJson[regexUnityID("wrld",json[i].location) 
                         ||regexUnityID("wld" ,json[i].location) 
                         ||                    json[i].location] = 1;
            }
            */
            
            Append({ "json":json, "WorldsJson":WorldsJson });
            //console.error(WorldsJson);
            //console.error(UserTemp);
            LogInfo("Loaded:World(" + Object.keys(WorldsJson).length + "), Online(" + json.length + ")");
        });
    });
    //<span aria-hidden="true" class="fa fa-crosshairs fa-1x">
    // ▼▲▽△
    var template_top = `
        <h3 id='Lone_WWF_top_desc'>
            Total Online: <font style='color:yellow;'>%online-count%</font>
                          /
                          <font style='color:yellow;'>%online-percent%%</font>
            (Private: <font style='color:#1fd1ed;'>%private-count%</font>
                      /
                      <font style='color:#1fd1ed;'>%private-percent%</font>)
            (Rooms: <font style='color:#67d781;'>%rooms-count%</font> <- 
                    <font style='color:yellow;' id="rooms-public">?</font>
                    /
                    <font style='color:#8143E6;' id="rooms-fplus">?</font>
                    /
                    <font style='color:#FF7B42;' id="rooms-fonly">?</font>)
                    <!-- https://pjchender.blogspot.com/2017/12/5-fontawesome-5.html -->
                    <div class="fa-1x" style="display:inline;">
                        &nbsp;
                        <i class="fas fa-sync" id="Lone_WWF_refresh"></i>
                    </div>
        </h3>`
    var template_world = `
        <!--
        <div class="row %private-type%" style="border-left:%world-color% 5px outset;border-right:%world-color% 5px outset;">
        -->
        <div class="row Lone_world_row %private-type%">
            <div class="col-12 template_world">
                <h3><a class="Lone_wrld" wrld_id="%world-id%" style="color:white;" target="%world-window%" href="%world-page%">%world-name%</a>
                    <a class="btn btn-primary float-right Lone_World_locationJoin" style="
                        width:80px;font-family:Segoe UI;
                        padding:.2rem .75rem;
                        background:%room-color% linear-gradient(180deg, %room-color%, %room-color%) repeat-x;
                        border-color:%room-color%" 
                        href="%locationJoin%">
                            %room-type%
                    </a>
                    
                    <a class="Lone_world_tags_href" target="_blank" href="%world-vrcw-target%">
                        <font class="float-right Lone_world_tags" style="color:white;">▼</font>
                    </a>
                    
                    <a class="btn btn-primary float-right pointer Lone_world_room_people" 
                       room="%room-location%" 
                       world="%world-ido%"
                       href="javascript:void(0);"
                       style="min-width:70px;padding:.2rem .75rem;background:#67d781 linear-gradient(180deg, #67d781, #67d781) repeat-x;border-color:#67d781;color:blue;">%world-room%</a>
                    
                    <a class="float-right" style="color:white;" target="_blank" href="%world-launch-link%">◄</a>
                    
                    <span class="badge badge-secondary float-right" style="display:none;text-transform:capitalize;padding:.14em .4em;">
                        <span aria-hidden="true" class="fa"></span>&nbsp;
                    </span>
                    
                    <a class="float-right Lone_wrld_author_link_vrclist" style="color:white;"  target="_blank">&nbsp;►</a>
                    <a class="float-right Lone_wrld_author_link" style="color:LightCoral;font-size:22px;" target="_blank"></a>
                </h3>
                <h3 class="Lone_world_template_info" style="float:right;z-index:5;border-bottom:unset;">
                    <!--<a style="vertical-align:top;font-size:15px;">Room Created by-->
                    <a class="Lone_room_createdBy" CreatedBy="%CreatedBy%">Room Created by
                        <a class="btn btn-primary pointer Lone_room_createdBy_btn" CreatedBy="%CreatedBy%" target="_blank">
                            <span class="fa fa-database"></span>
                        </a>
                    </a>
                    <a class="Lone_room_people_counter %private-room-counter%">%room-counter%</a>
                    <a class="Lone_room_inviteMe" href="javascript:void(0);" room="%Lone_room_inviteMe%">
                        <img class="Lone_wrld_img %blur%" src="%default_private_image%" />
                    </a><br>
                    %privateRoomMEME%
                </h3>
                <div class="col-md-4" id="%world-ido%" style="%background-color%;position:unset;"></div>
            </div>
        </div>`
    var template_user   = `
    <div class="row text-left friend-row Lone_friend_row" user_id="%user_id%" style="margin-top:2px;margin-bottom:2px;margin-left:auto;">
      <!--<a class="col-12">-->
        <img class="img-thumbnail rounded float-left friend-img Lone_world_friend" src="%img-thumbnail%" title="%display-title%" style="
            width:160px;height:120px;
            border-right-width:6px; border-right-color:%trust-color%;
            border-left:%world-color% 6px outset;
            border-top-width    :%is_system_legend_width%;   border-top-color    :%is_system_legend_corlor%;
            border-bottom-width :%is_system_legend_width%;   border-bottom-color :%is_system_legend_corlor%;">
        <div class="friend-caption text-success">
          <ul><li><a target="_blank" href="%user_page%">
                  <font color="%StatusColor%">
                    <b>%display-name%</b>
                  </font></a></li>
              <li><font color="#ff5767">(%friend_Index%)</font>
                %status-des%
                <font style="opacity:0.0;">-</font>
              </li>
              <!--
              <li><font style="display:%rank-hide%;color:white !important;">Appearing as <font style="color:#67d781;">User</font> Rank</font></li>
              -->
              <li><a style="color:#6610f2;">%user-remarks%</a></li>
          </ul>
        </div>
      <!--</a>-->
    </div>`
    function Append(jsons){
        var ScriptUserData = GM_getValue("ScriptUserData",{ "friends": [] });
        //console.error(ScriptUserData);
        var json        = jsons["json"]
        var WorldsJson  = jsons["WorldsJson"]
        var col12 = $("div.home-content div.row div.col-12:eq(0)").html("" +
            LoadFormatText(template_top,{  
                // "%online-count%"   : json.length + (json.length >= FriendsLimit ? "<a style='color:#90ee90;'>↑</a>":"")
                 "%online-count%"   : json.length
                ,"%online-percent%" : Math.round(json.length / ScriptUserData["friends"].length * 100)
                ,"%private-count%"  : WorldsJson["private"]["counter"] || 0
                ,"%rooms-count%"    : Object.keys(WorldsJson).length - 1
                ,"%private-percent%": Math.round((WorldsJson["private"]["counter"] || 0) / json.length * 100) + "%"
            }).out)
            
        $("#Lone_WWF_refresh").click(function(){
            $("div.home-content div.col-12:eq(0)").html("");
            WorldsWithFriends();
            $("#WorldsWithFriends").click();
        })
            
        $.each(WorldsJson, function(key, value) {
            $(col12).append('' + 
                LoadFormatText(template_world,{
                   //"%world-color%"    : ColorChroma(key)
                     "%private-type%"   : value.room == "private" ? "Lone_private_room" : ""
                    ,"%blur%"           : value.room == "private" ? "" : "blur"
                    ,"%world-id%"       : key.split(":")[0] || key
                    ,"%world-ido%"      : key
                    ,"%world-name%"     : value.room == "private" ? key : "🚧Under Construction🚧" /* key.split(":")[0] || key */
                    ,"%world-room%"     : key.split(":")[1] || "-"
                    ,"%world-page%"     : key.match("private") ? "#" : "/home/world/" + key.split(":")[0] || key
                    ,"%room-type%"      : GetRoomType(value.room)[0]
                    ,"%room-counter%"   : value.counter > 1 ? value.counter : ""
                    ,"%locationJoin%"   : value.room == "private" ? "#" : "vrchat://launch?ref=" + document.location.host + "&id=" + value.room
                    ,"%world-window%"   : value.room == "private" ? ""  : "_blank"
                    ,"%room-color%"     : GetRoomType(value.room)[1]
                    ,"%background-color%": value.room == "private" ? "background-color:#313131;" : ""
                    //,"%world-launch-link%": value.room == "private" ? "" : "https://www.vrchat.net/launch?worldId=" + key.split(":")[0]
                    ,"%world-launch-link%": value.room == "private" ? "" : "https://www.vrchat.net/home/launch?worldId=" + key.split(":")[0]
                    ,"%private-room-counter%": value.room == "private" ? "Lone_room_private_counter" : ""
                    ,"%CreatedBy%"      : value.created || "NoOwner"
                    ,"%default_private_image%": GM_getResourceURL("private_image")
                    ,"%privateRoomMEME%": value.room == "private" ? `<div id="privateIMG" style="display:inline;"></div>` : ""
                    ,"%room-location%"  : value.room
                    ,"%world-vrcw-target%" : "https://en.vrcw.net/world/detail/" + key.split(":")[0] || key
                    ,"%Lone_room_inviteMe%" : "instances/" + value.room + "/invite"
                }).out
            )
            
            setTimeout(function(){ 
                $("a.Lone_room_createdBy_btn[CreatedBy*='usr_']").each(function(){
                    var usr_id      = $(this).attr("CreatedBy");
                    //console.error([usr_id, UserTemp[usr_id]]);
                    if(UserTemp[usr_id]){
                        CreatedByAuto(this, UserTemp[usr_id]["displayName"]);
                    }
                    else {
                        var UserSaved   = GM_getValue(usr_id, null);
                        if(UserSaved){
                            CreatedByAuto(this, UserSaved["displayName"]);
                        }
                    }
                })
            },1)
        });
        
        $("#rooms-public").html( $(".Lone_World_locationJoin:contains('Public')"    ).length || 0);
        $("#rooms-fplus") .html( $(".Lone_World_locationJoin:contains('Friends+')"  ).length || 0);
        $("#rooms-fonly") .html(($(".Lone_World_locationJoin:contains('Friends')"   ).length || 0)- 
                                 $(".Lone_World_locationJoin:contains('Friends+')"  ).length || 0);
                                 
        $("#privateIMG").html(`
        <a target="_blank" href="https://www.reddit.com/r/VRchat/comments/avtboy/meme_when_all_of_your_friends_are_in_private/">
            <img width="250px" height="140px" src="https://media1.tenor.com/images/a7e004f24af8ca4289fe65803a6580ba/tenor.gif" />
        </a>
        <br>
        <img width="250px" height="296px" src="https://i.imgur.com/YgEXLSq.jpg" id="privateMEME" style="display:none;" />
        
        `);
        
        $("#privateIMG").hover(function(){ $("#privateMEME").show(); },function(){ $("#privateMEME").hide() });
        
        //======================================================================
        AppendUserList(json);
        /*
        $.each(".Lone_world_row",function(){
            $(this).find('div.Lone_friend_row').sort(function(a,b) {
                return $(a).data('sid') > $(b).data('sid');
            }).appendTo(this).;
        })
        */
        
        function AppendUserList(userJson, locationBase, exclude = {}){
        $.each(userJson, function(index, value) { //console.error(value.location);
            var  pastName = GM_getValue(value.id + "_pastName",null);
            if( !pastName || value.displayName === pastName)
                 pastName = "";
            //else pastName = " ( " + pastName + " ) ";
            else pastName = pastName + "&nbsp;&nbsp;&nbsp;";
            value.location = value.location || locationBase;
            
            var friendIndex = OwnerData["friends"].indexOf(value.id)
            
            $("div.col-md-4[id='" + ( value.locationBase || locationBase ) + "']").append('' +
                LoadFormatText(template_user,{
                     "%user_id%"        : value.id
                    ,"%friend_Index%"   : friendIndex == -1 ? "?":friendIndex+1
                    ,"%display-name%"   : ""
                        + "<font style='color:white;'>" + value.username + "</font><br>"
                        + "<font style='color:white;'>" + pastName       + "</font>"
                        + value.displayName
                    ,"%world-color%"    : ColorChroma(value.location)
                    ,"%img-thumbnail%"  : value.currentAvatarThumbnailImageUrl
                    ,"%user_page%"      : "/home/user/" + value.id
                    ,"%status-des%"     : value.statusDescription || ""
                    ,"%StatusColor%"    : StatusColor[value.status] || "#798897"
                    ,"%trust-color%"    : GetTrusted(value.tags)[0]
                    ,"%rank-hide%"      : value.tags.toString().match("show_social_rank") ? "none" : "unset"
                    
                    ,"%is_system_legend_width%"
                        : value.tags.toString().match("system_legend")   ? "6px" : "2px"
                    ,"%is_system_legend_corlor%"
                        : value.tags.toString().match("system_legend")   ? "red" : "#798897"
                    
                    ,"%user-remarks%"   : getUserRemarks(value.id, true) || ""
                    
                    ,"%display-title%"  : "<font style='font-size:28px;'>"
                        +   value.displayName
                        + "<br><a style='color:" + StatusColor[value.status] + ";'>"
                        +   value.status
                        + "</a>"
                        + "<br><a style='color:" + GetTrusted(value.tags)[0] + ";'>"
                        +   (GetTrusted(value.tags)[1] || "Visiter")
                        + "</a>"
                        +   (value.tags.toString().match("system_legend") ? " + <a style='color:red;'>Legend</a>" : "")
                        //+ "<br><img src='" + value.currentAvatarImageUrl + "' />"
                            //+ "<br><img src='" + value.currentAvatarThumbnailImageUrl + "' />"
                            + "<br><img src='" + value.currentAvatarImageUrl + "' />"
                        //+ "-"
                + "</font>"}).out
            )
        })
            // https://api.jqueryui.com/tooltip/#option-position
            $(".Lone_world_friend").tooltip({
                position: { at: "right+15 center"}
            });
        }
        
        $("#private").parent().find("img:eq(0)").css("opacity","1.0")
            .css("width","250px").css("height","187.5px");
        
        $(col12).append("<h3>-</h3>");
        
        // ================================================================================================
        //return;
        var worldsREQ = {};
        $.each(WorldsJson, function(key, value) {
            if(key == "private" || worldsREQ[key]) return true; // continue
            worldsREQ[key] = true;
            
            if(WorldTemp[key.split(":")[0]]){
                WorldDataAppend(WorldTemp[key.split(":")[0]], key);
                //console.error([WorldTemp[key.split(":")[0]], key.split(":")[0]]);
                return true; // continue
            }
            
            var e_template_world = $("div.col-md-4[id='" + key + "']").parents("div.template_world:eq(0)");
            jQuery.ajax({
                //,async:false // https://stackoverflow.com/a/2592780
                url: "/api/1/worlds/" + key.split(":")[0],
                success: function(json) { //console.error(json);
                    if(!json.id){
                        $(e_template_world).find("a.Lone_wrld").html("Fetch error...");
                        return false;
                    }
                    WorldTemp[json.id] = json;
                    setTimeout(function(){
                        WorldDataAppend(json, key);
                    },1000)
                },error: (function( xhr, status, error ) { console.error([xhr, status, error]);
                    $("div.col-md-4[id='" + key + "']").parents("div.template_world:eq(0)")
                        .find("a.Lone_wrld")
                        .html("Failed? " + xhr.status);
                })
            });
            //return false;
        })
        LogInfo("VRChatLittleONE Worlds Request: " + Object.keys(worldsREQ).length);
        
        function WorldDataAppend(json, key){
            var e_template_world = $("div.col-md-4[id='" + key + "']").parents("div.template_world:eq(0)");
            //$("div.col-md-4[id='" + key + "']").prepend(json.description);
            
            $(e_template_world).find("a.Lone_wrld")
                .html(json.name /*+ " by " + json.authorName*/);
            
            $(e_template_world).find("img.Lone_wrld_img:eq(0)")
                .removeClass("blur")
                .attr("src",json.thumbnailImageUrl)
                .css("opacity","1.0")
            
            $(e_template_world).find("span.badge:eq(0)").show()
                .append(json.releaseStatus + "&nbsp;V." + json.version)
                .css("color",WorldType[json.releaseStatus][1])
                .attr("title","Occupants: "         + "<font class='Lone_wrld_badge'>" + json.occupants         + "</font>"
                         +"<br>PublicOccupants: "   + "<font class='Lone_wrld_badge'>" + json.publicOccupants   + "</font>"
                         +"<br>PrivateOccupants: "  + "<font class='Lone_wrld_badge'>" + json.privateOccupants  + "</font>"
                         +"<br>Capacity: "          + "<font class='Lone_wrld_badge'>" + json.capacity          + "</font>"
                         +"<br>Author: "            + "<font class='Lone_wrld_badge'>" + json.authorName        + "</font>"
                         +"<br>Description: <br>&nbsp;&nbsp;&nbsp;&nbsp;"
                            + "<font style='color:#1fd1ed;'><b>" + json.description + "</b></font>"
                ).tooltip()
                .find("span.fa:eq(0)")
                .addClass("fa-" + WorldType[json.releaseStatus][0])
            
            $(e_template_world).find(".Lone_world_tags:eq(0)").tooltip()
                .attr("title","World Tags:" + JSON.stringify(json.tags, null, "<br>").slice(1,-1))
                
            $(e_template_world).find(".Lone_wrld_author_link:eq(0)")
                .html(json.authorName)
                .attr("href","/home/user/" + json.authorId)
            
            $(e_template_world).find(".Lone_wrld_author_link_vrclist:eq(0)")
                //.attr("href","http://www.vrcworldlist.net/search?utf8=%E2%9C%93&keyword=" + encodeURI(json.authorName))
                .attr("href","https://en.vrcw.net/world?utf8=%E2%9C%93&keyword=" + encodeURI(json.authorName))
        }
        
        $(".Lone_room_createdBy_btn").click(function(){ CreatedByAuto(this); });
        
        function CreatedByAuto(btn, displayName){ $(btn).unbind("click");
            if(displayName){ StyleLaunch(displayName); return; }
            
            var CreatedByUrl = "/api/1/users/" + $(btn).attr("CreatedBy");
            $.get( CreatedByUrl ).done(function( json ){ //console.error(json);
                StyleLaunch(json.displayName);
                UserTemp[json.id] = json;
            })
            
            function StyleLaunch(displayName){
                $(btn)
                    .html("<br>" + displayName)
                    .css("font-size","20px")
                    .removeClass('btn-primary')
                    .attr("href","/home/user/" + $(btn).attr("CreatedBy"))
            }
        }
        
        $(".Lone_room_inviteMe").click(async function(){
            LogInfo($(this).attr("room"))
            await getAPI($(this).attr("room"), { method: 'POST' });
        })
        
        $(".Lone_world_room_people").click(async function(){
            if(!OwnerData["id"]){
                LogInfo("No OwnerData")
                return
            }
            
            var IsForceQuit = false
            var roomElement = this;
            var userElement = $(this).parents("div.template_world:eq(0)").find("div.col-md-4:eq(0)");
            var room        = $(this).attr("room").replace(":","/");
            var roomOri     = $(this).attr("room");
            if(room == "private") return;
            $(userElement).css("background-color","#1f262e");
            
            var LocationData
            await $.get("https://api.vrchat.cloud/api/1/users/" + OwnerData["id"])
                .done(function(Data){
                    //LogInfo("LocationData", Data)
                    LocationData = Data
                }).fail(function (msg) {
                    console.error(msg)
                    IsForceQuit = true
                })
                
            if(IsForceQuit) return
            
            let UserJoin = PutUserJoin({ "id": OwnerData.id, "room": $(this).attr("room") })
            if(!UserJoin){
                LogInfo("UserJoin failed")
                alert("UserJoin failed")
                return
            }

            $( userElement ).parents("div.col-12.template_world").find("h3.Lone_world_template_info").hide( "fade", "fast", function(){
                $( userElement ).hide( "drop", "slow", function(){
                    $(userElement).html("");//.css("background-color","#798897")
                })
            })
            
            await sleepNew(1000)
            await $.get( "/api/1/worlds/" + room ).done(function( json ){ //console.error(json);
                $.each(json.users, function(index, value) { //console.error(value.location);
                    if(UserTemp[value.id]){
                        //delete json.users[index].locationBase
                        //delete json.users[index].isFriend
                        json.users[index].statusDescription = undefined
                        json.users[index]
                        //      = Object.assign(UserTemp[value.id], json.users[index]);
                           = Object.assign(JSON.parse(JSON.stringify(UserTemp[value.id])), json.users[index]);
                        json.users[index].locationBase = undefined
                        json.users[index].isFriend = UserTemp[value.id].isFriend
                    }
                })
                
                //var RoomCounter = $(userElement).find("div.friend-row").length;
                $(roomElement)
                    .parents("div.template_world:eq(0)")
                    .find("a.Lone_room_people_counter:eq(0)")
                    .html(json.n_users);
                    //.html(RoomCounter);
                
                if(!json.users || !json.users.length)
                    return;
                
                json.users = json.users.sort(function (a, b) {
                    return a.displayName > b.displayName ? 1 : -1;
                });
                json.users = json.users.sort(function (a, b) {
                    if(a.isFriend && b.isFriend)
                        return 0
                    return a.isFriend > b.isFriend ? -1:1;
                    //return a.isFriend == true ? 1:0
                });
                AppendUserList(json.users, $(roomElement).attr("world"));
                
                /*
                $(userElement).find("div.friend-row").each(function(){
                    var user_id = $(this).attr("user_id");
                    
                })
                */
            }).fail(function( xhr, status, error ) { console.error(error);
                
            })
            
            $( userElement ).show( "drop", "slow", function(){
                $( userElement ).parents("div.col-12.template_world").find("h3.Lone_world_template_info").show( "fade", "slow")
            });
            LogInfo("LocationData.location", LocationData.location)
            await sleepNew(1000)
            PutUserJoin({ "id": OwnerData.id, "room": LocationData.location })
        })
        
    }
}

function LoginGoBack(element){
    if(document.location.pathname == LastHomePage || LastHomePage == "") return;
    /*
    $(element).clone().appendTo($(element).parent())
        .removeAttr("id").removeAttr("name").removeAttr("value")
        .html("Login & goBack")
    */
    
    $(element).parent()
        //.append("&nbsp;&nbsp;&nbsp;<a href='#' id='LoginGoBack' class='btn btn-primary LoginGoBack'>Login & GoBack</a>")
        .append("<a href='#' id='LoginGoBack' class='btn btn-primary LoginGoBack'>Login & GoBack</a>")
        .append("<br>" + LastHomePage)
        
    $("#LoginGoBack").click(function(){
        //console.error(document.location);
        //console.error(LastHomePage);
        
        // https://wp.me/p7qfLb-6E
        // https://stackoverflow.com/a/11960692
        // https://stackoverflow.com/questions/12840410/how-to-get-a-cookie-from-an-ajax-response
        $.ajax({ //async: false, //data: '{ "comment" }',
            type: "GET",
            url: "/api/1/auth/user?apiKey=JlE5Jldo5Jibnk5O5hTx6XVqsJu4WJ26",
            dataType: 'json',
            headers: {
                "Authorization": "Basic " + btoa($("#username_email").val() + ":" + $("#password").val())
            },
            success: function (data, textStatus, request){ //console.error(data);
                CookieExtend();
                window.location.href = document.location.origin + LastHomePage;
                //window.location.href = window.location.href;
                //history.go(-2);
            },
            error: function( jqXHR, textStatus, errorThrown ){
                console.error(jqXHR);
                console.error(textStatus);
                console.error(errorThrown);
                alert("login Failed");
            }
        });
    });
}

function ExpandWorldThumbnail(element){
    $(element).wrap("<a target='_blank' href='" + $(element).attr("src") + "'></a>");
    Expandhumbnail(element);
}

// https://vrchatapi.github.io/#/UserAPI/GetByID
function UserStatus(element){
    var UserID = regexUnityID("usr",window.location.href);
    if(!UserID) return;
    
    var Status = `
        <font id="LO_friend_Index" color="#ff5767"></font>
        <span id="LO_status" aria-hidden="true" class="fa fa-circle"></span>&nbsp;
        <a id="LO_pastName"></a>`
    //var StatusColor = { "join me":"aqua", "active":"lime", "busy":"red" };
    
    $("div.col-md-12 > h2:eq(0)").append(Status);
    //element = $(element).removeAttr("hidden").html("retrieving status data...");
    
    $.get( "/api/1/users/" + UserID).done(function( json ){ //console.error(json);
        var friendIndex = OwnerData["friends"].indexOf(json.id)
        $("#LO_friend_Index").html( friendIndex == -1 ? "":"[" + (friendIndex + 1) + "]" );
        
        var  pastName = GM_getValue(UserID + "_pastName","");
        if(  json.displayName === pastName || json.username === pastName) pastName = "";
        $("#LO_status").css("color",StatusColor[json.status]);
        $("#LO_pastName").html(pastName);
        
        /*
        $(element).html(json.statusDescription == "" ? 
            "<a style='color:blue;'><b>No Description</b></a>" : "<b>" + json.statusDescription + "</b>");
        */
        // https://github.com/VRChatAPI/vrchatapi.github.io/blob/master/UserAPI/Tags.md
        var TagsJson = "<br>" + JSON.stringify(json.tags, null, 2).toString().slice(1,-1);
        var Tags     = LoadFormatText(TagsJson,{ "ignoreSymbol":true
            ,"\",":"<br>"
            ,"\"" :""
            // https://api.vrchat.cloud/home/user/8JoV9XEdpo
            //,"admin_official_thumbnail" :"<font color='#FFFFFF'>%RepSrc%</font> [thumbnail]" 
            
            ,"admin_moderator"          : "<font color='#8B0000'>%RepSrc%</font> [Admin]"
            ,"system_legend"            : "(7/7) <font color='#FF0000'>%RepSrc%</font> [Legend]"
            ,"system_trust_legend"      : "(6/7) <font color='yellow' >%RepSrc%</font> [Veteran]"
            ,"system_trust_veteran"     : "(5/7) <font color='#8143E6'>%RepSrc%</font> [Trusted User]"
            ,"system_trust_trusted"     : "(4/7) <font color='#FF7B42'>%RepSrc%</font> [Known User]"
            ,"system_trust_known"       : "(3/7) <font color='#2BCF5C'>%RepSrc%</font> [User]"
            ,"system_trust_intermediate": "(1/7) <font color='#000080'>%RepSrc%</font> [Intermediate] New User+" // (New User > User)
            ,"system_trust_basic"       : "(1/7) <font color='#1778FF'>%RepSrc%</font> [New User]"
            ,"system_troll"             : "(0/7) <font color='#808080'>%RepSrc%</font> [Troll]"
            // https://api.vrchat.cloud/home/user/usr_837524d7-e1a6-4744-afe5-b23ef1fd4103
            ,"system_probable_troll"    : "(0/7) <font color='#808080'>%RepSrc%</font> [Troll??]" 
            
            ,"show_social_rank"         : "%RepSrc% [ShowLevel]"
        }).out;
        
        $("div.home-content div.col-md-12 > h3.subheader:eq(0)").append(LoadFormatText(
            `<span id="Lone_AvatarCloneStatus" aria-hidden="true" class="fa fa-clone" title="Allow Avatar Clone : %cloneType%" style="color:%cloneColor%;"></span>`
            ,{
                 "%cloneColor%" : json.allowAvatarCopying == true ? "green" : "red"
                ,"%cloneType%"  : json.allowAvatarCopying == true ? "Yes"   : "No"
            }).out).append(`<a id='trustedData' style='color:` + GetTrusted(json.tags)[0] + `;' 
                   title='` + "Tags" + `'>&nbsp;`
                    + (json.tags.toString().match("show_social_rank") ? "":"<s>")
                    + GetTrusted(json.tags)[1]
                    + (json.tags.toString().match("show_social_rank") ? "":"</s>")
                    +
               `</a>`).append(json.tags.toString().match("system_legend") ? ` + <a style="color:red;">Legend</a>`:"");
               
               json.tags.toString().match("show_social_rank") ? "":"</s>"
               
        $("#Lone_AvatarCloneStatus").tooltip();
        
        /*
        $("div.col-md-4").find("img.offline, img.online").eq(0)
            .wrap("<a target='_blank' href='" + json.currentAvatarImageUrl + "'></a>")
            //.attr("src",json.currentAvatarImageUrl)
        */
        $(`<h3> 
                <a id="CheckAvatarOwner" style="color:white;" href="javascript:void();" title="Check avatar's owner of current user.">
                    Owner?
                </a>
                <!--
                <a id='trustedData' style='float:right;color:` + GetTrusted(json.tags)[0] + `;' 
                   title='` + "Tags" + `'>`
                    + (json.tags.toString().match("show_social_rank") ? "":"<s>")
                    + GetTrusted(json.tags)[1]
                    + (json.tags.toString().match("show_social_rank") ? "":"</s>")
                    +
               `</a>-->
            </h3><h3 id="ShowAvatarOwner" style="display:none;"></h3>`)
                .insertAfter("div.home-content div.usercard.card:eq(0)")
            
        $("#CheckAvatarOwner").tooltip().click(function(){
            $("#ShowAvatarOwner").css("display","block").html("Checking...");
            var path = document.location.origin + "/api/1/file/" + regexUnityID("file",json.currentAvatarImageUrl); 
            $.get( path ).done(function( json ){ // console.error(json);
                var isOwner = regexUnityID("usr",window.location.href) == json.ownerId ?
                              "<font style='color:#67d781;'>is</font>" : "is <font style='color:yellow;'>not</font>"
                $("#ShowAvatarOwner")
                    .attr("title",json.name).tooltip()
                    .html("user " + isOwner + " avatar's <a target='_blank' href='/home/user/" + json.ownerId + "'>owner</a>.");
            })
        });
        $("#trustedData").tooltip({ content: function () { return Tags; } });
        
        Expandhumbnail($("img.online, img.offline"));
        Expandhumbnail($("div.card.bg-primary > div.card-body:eq(0) img[src*='file']:eq(0)"));
        
        if(json.last_login.length > 0){
            var last_login = moment(json.last_login).format("YYYY-MM-DD HH:mm:ss");
            var last_login_fromNow = moment(json.last_login).fromNow(true);
            if(json.location != "offline")
                    last_login_fromNow = "上線 " + last_login_fromNow
            else    last_login_fromNow = last_login_fromNow + "前 上線"
            $("#LO_status").parent().append("<div style='float:right;'>" + last_login + "</div>");
            $("div.home-content div.mb-4.row div.col-md-12 h3.subheader:eq(0)").append("<div style='float:right;'>" + last_login_fromNow + "</div>");
        }
        
        // https://stackoverflow.com/a/1986850
        // $("div.world-join")
        if(json.location == "offline"){
            $("img[src*='default_offline_image.png']:eq(0)").parents("div.card.bg-primary:eq(0)")
                .css("cssText", "background-color: #404c58 !important;");
        }
        else if(json.location == "private"){
            $("img[src*='default_private_image.png']:eq(0)").parents("div.card.bg-primary:eq(0)")
                .css("cssText", "background-color: #6b7886 !important;")
                .html("<div class='card-body'><img src='https://assets.vrchat.com/www/images/default_private_image.png' /></div>")
        }
    }).fail(function( xhr, status, error ) { console.error(error);
        //$(element).html("<a style='color:darkred;'><b>ERROR</b></a>");
        //$("#LO_status").css("color","black");
    });
    
    //==========================================================================
    // Remarks for user
    $("head").append(`
    <style>
    .Lone_card_body_remarks {
        /*
        background-color: #1fd1ed !important;
        */
        background-color: #404c58 !important;
        position: relative;
        display: flex;
        flex-direction: column;
        min-width: 0;
        word-wrap: break-word;
        background-color: #404c58;
        background-clip: border-box;
        border: 1px solid rgba(10,10,13,0.125);
        border-radius: .25rem;
        flex: 1 1 auto;
        padding: 1.25rem;
    }
    
    .Lone_remarks_set_textarea {
        width: 100%;
        -webkit-box-sizing: border-box;
        -moz-box-sizing: border-box;
        box-sizing: border-box;
    }
    <style>`)
    
    let UserRemarks     = GM_getValue(UserID + "_remarks", null)
        UserRemarks     = UserRemarks ? UserRemarks.replace(new RegExp("\n","gm"),"<br>"):null
    let Remarks_default = "Type your personal remarks to this user, Only you can see.<br>Data saved to local (this PC)"
    
    $("div.home-content div.w-100.btn-group-lg.btn-group-vertical").parents("div.col-md-4:eq(0)").prepend(`
        <div role="group" class="w-100 btn-group-lg btn-group-vertical" style="margin-bottom:20px;">
            <button type="button" class="btn btn-primary Lone_remarks_set">Remarks</button>
        </div>`)
        
    $(` <div class="Lone_card_body_remarks" style="margin-top:10px;">
            <b class="Lone_card_body_remarks_description">` + (UserRemarks || Remarks_default) + `</b>
            <div class="Lone_remarks_set_div" style="display:none;">
                <br>
                <textarea class="Lone_remarks_set_textarea"></textarea>
                <br>
                <button class="Lone_remarks_set_button_set btn btn-secondary">Set</button>&nbsp;&nbsp;
                <button class="Lone_remarks_set_button_cancel btn btn-secondary">Cancel</button>
            </div>
        </div>
    `).insertAfter("div.home-content div.card.card-body.bg-primary:eq(0)")
    
    $(".Lone_remarks_set").click(function(){
        $("div.Lone_remarks_set_div").show();
        $("textarea.Lone_remarks_set_textarea").val( GM_getValue(UserID + "_remarks", null) || "" );
    })
    
    $("button.Lone_remarks_set_button_set").click(function(){
        let remarks_description = $("textarea.Lone_remarks_set_textarea").val()
            remarks_description = remarks_description == "" ? null:remarks_description
        GM_setValue(UserID + "_remarks", remarks_description);
        $(".Lone_card_body_remarks_description").html(remarks_description.replace(new RegExp("\n","gm"),"<br>"));
        $("div.Lone_remarks_set_div").hide()
    })
    
    $("button.Lone_remarks_set_button_cancel").click(function(){
        $("div.Lone_remarks_set_div").hide()
    })
    
    modToUser();
}

function modToUser(){
    var UserID = regexUnityID("usr",window.location.href);
    if(!UserID) return;
    
    // Mute UnMute HideAvatar ShowAvatar
    // <span aria-hidden="true" class="fa fa-star-half-alt"></span>
    // <span aria-hidden="true" class="fa fa-volume-off"></span> 
    // btn-primary // btn-secondary
    $("head").append(`<style>
        .Lone_PlayerModerations .btn-secondary {
            display: none;
        }
        
        .Lone_PlayerModerations .btn-secondary[dType="unmute"], 
        .Lone_PlayerModerations .btn-secondary[dType="showAvatar"] {
            color: darkred;
        }
    </style>`);
    $("button.btn.btn-secondary:contains('Block') , button.btn.btn-secondary:contains('Unblock')")
    .parents("div.col-md-4:eq(0)").append(`
    <div role="group" class="w-100 btn-group-lg btn-group-vertical Lone_PlayerModerations"><br>
        <button type="button"                    class="btn btn-primary"  >Your Moderations </button>
        <button type="button" dType="mute"       class="btn btn-secondary">Mute             </button>
        <button type="button" dType="hideAvatar" class="btn btn-secondary">HideAvatar       </button>
        <button type="button" dType="reset"      class="btn btn-secondary">Reset            </button>
    </div>
    `);
    
    let Moderations = GM_getValue("playermoderations",[]).filter( val => val.targetUserId == UserID );
    if(Moderations.length){
        let modTypeTemp = Moderations[0].type;
        $(`.btn-secondary[dType="${modTypeTemp}"]`)
            .attr("dType",modType[modTypeTemp]["re"])
            .html(modType[modType[modTypeTemp]["re"]]["text"])
    }
    
    $(".Lone_PlayerModerations .btn-primary:eq(0)").click(function(){
        $(".Lone_PlayerModerations .btn-secondary").slideToggle("fast");
    });
    
    $(".Lone_PlayerModerations .btn-secondary").click( async function(){
        $(".Lone_PlayerModerations .btn-secondary").prop('disabled', true);
        
        $(".Lone_PlayerModerations .btn-primary").html("Read list for modify");
        let [modList, err0] = await getAPI("auth/user/playermoderations"); LogInfo("modList:", modList.length);
        if ( modList ) GM_setValue("playermoderations",modList);
             modList = (modList || []).filter( val => val.targetUserId == UserID && val.type != "block" );
        
        $(".Lone_PlayerModerations .btn-primary").html("Delete duplicate data");
        for(let i = 0; i < modList.length;i++){ if(modList[i].type == "block") continue;
            let [result, del_err] = await getAPI("auth/user/playermoderations/" + modList[i].id, { method: 'DELETE' });
            LogInfo("user data in modList has deleted", result, del_err);
        }
        
        let dType = $(this).attr("dType");
        if( dType == "reset"){
            dType = "unmute";
            $(`.btn-secondary[dType="unmute"]`)
                .attr("dType","mute")
                .html(modType["mute"]["text"]);
            $(`.btn-secondary[dType="showAvatar"]`)
                .attr("dType","hideAvatar")
                .html(modType["hideAvatar"]["text"]);
        }
        
        $(".Lone_PlayerModerations .btn-primary").html("Setting Player Moderations");
        // https://vrchatapi.github.io/#/ModerationAPI/SendPlayerModerations
        let value = await getAPI("auth/user/playermoderations",{
             method: 'POST'
            ,headers: { 'content-type': 'application/json' } 
            ,body: JSON.stringify({ type: dType, moderated: UserID })
        });
        if(value[1]){ alert("Error"); return; }
        value = [value[0]]; LogInfo("SetPlayerModerations:", value);
        
        // https://stackoverflow.com/a/37585362
        let array_out   = GM_getValue("playermoderations",[])
                            .map(obj => value.find(o => o.targetUserId === obj.targetUserId) || obj);
        GM_setValue("playermoderations",array_out);
            
        let modTypeTemp = value[0].type;
        $(`.btn-secondary[dType="${modTypeTemp}"]`)
             .attr("dType",modType[modTypeTemp]["re"])
             .html(modType[modType[modTypeTemp]["re"]]["text"]);
                
        $(".Lone_PlayerModerations .btn-primary").html("Your Moderations");
        $(".Lone_PlayerModerations .btn-secondary").prop('disabled', false);
    });
}

// http://www.wibibi.com/info.php?tid=79
// http://www.wibibi.com/info.php?tid=CSS3_background-size_%E5%B1%AC%E6%80%A7
// http://www.wibibi.com/info.php?tid=75
// https://goo.gl/BlGwtZ
function Expandhumbnail(e, url){
    $(e).hover(function() {
        $("div.home-content:eq(0) > div:eq(0)")
            .css("background-repeat","no-repeat")
            .css("background-size","contain") // 100%
            .css("background-image","url(" + (url || $(e).attr("src")) + ")")
            .css("background-position","center top") // 100%
            .find("div").css("opacity","0.3") //.fadeTo( "fast" , 0.1)
    }, function(){
        $("div.home-content:eq(0) > div:eq(0)")
            .css("background-image","")
            .find("div").css("opacity","1.0")
    });
}

// https://fontawesome.com/icons?d=gallery
function SetPublicAvatar(element){
    var e_AvatarThumbnail = $("img.img-thumbnail.rounded-circle.float-left.home-avatar:eq(0)").attr("src");
    
    var styleElement = `<hr>
    <div class="animated fadeIn card">
      <h3 class="card-header">CHANGE AVATAR (PUBLIC ONLY)</h3>
      <div class="card-body"><div><div class="center-panel"><form>
        <div class="row">
          <div class="col-1" style="text-align: right;">
            <span aria-hidden="true" class="fa fa-portrait fa-2x" id="SetAvatarPortrait" style="color:#FA5882;"></span>
          </div>
          <div class="col-10"><div class="row">
              <!--<textarea class="form-control" name="avatar-bluPrintID" id="avatar-bluPrintID"></textarea>-->
              <input type="text" class="form-control" id="avatar-bluPrintID" name="avatar-bluPrintID"></input>
          </div></div>
        </div>
      </div>
      <div class="row"><div class="col-1"></div>
        <div class="col-10"><div class="row">
          <div id="SetAvatarStatusDiv" style="display:none">
            <div id="SetAvatarStatus" style=""></div>
          </div>
        </div></div>
      </div>
      <div class="row">
        <div class="col-4 offset-4">
          <button class="btn btn-primary w-100" value="Use Default"   id="SetDefaultAvatar">Use Default</button>
        </div>
        <div class="col-4">
          <button class="btn btn-primary w-100" value="Change Avatar" id="SetPublicAvatar">Change Avatar</button>
        </div>
      </div>
    </form></div></div>
    </div>
    </div><hr>`
    
    $(styleElement).insertAfter( $(element).parent() );
    
    /*
    var styleElement = $(''
        + '<div class="card row"><h3>Change Avatar (Public only)</h3>'
        + '<div><div class="center-panel">'
        + '<form class="form-horizontal" name="update-status" action="#">'
        + '<div class="form-group"><div class="row"></div>'
        + '<div class="row">'
        + '<div class="col-1">'
            // https://fontawesome.com/icons?d=gallery&c=images
            // https://fontawesome.com/icons/portrait?style=solid
        + '<span aria-hidden="true" class="fa fa-portrait fa-4x">'
        + '</span>'
        + '</div>'
        + '<textarea class="col-md-10" name="avatar-bluPrintID" id="avatar-bluPrintID"></textarea>'
        //+ '<div class="col-1 d-none" id="SetPublicAvatarChecked"><span aria-hidden="true" class="fa fa-check fa-2x text-success"></span></div>'
        + '</div></div><div class="form-group">'
        + '<div class="row"><div class="offset-8">' //<div class="col-4 offset-8">'
        //+ '<input class="btn btn-primary w-100" value="Change Avatar" type="button" id="SetPublicAvatar">'
        + '<input class="btn btn-primary" value="Change Avatar" type="button" id="SetPublicAvatar">&nbsp;'
        + '<input class="btn btn-primary" value="Use Default" type="button" id="SetDefaultAvatar">'
        + '</div></div></div>'
        + '</form>'
        //+ '<div class="form-group" id="SetAvatarStatusDiv" style="display:none;"><div class="row"><div class="col-4"><div><span color="success" aria-hidden="true" class="fa fa-check"></span>&nbsp;<a id="SetAvatarStatus"></a></div></div><div class="col-4 offset-4"></div></div></div>'
        + '<div class="form-group" id="SetAvatarStatusDiv" style="display:none;"><div class="row"><div class="col-4"><div><span color="success" aria-hidden="true" class="fa fa-asterisk"></span>&nbsp;<a id="SetAvatarStatus"></a></div></div><div class="col-4 offset-4"></div></div></div>'
        + '</div></div></div>').insertAfter( $(element).parent() );
    */
    $("#avatar-bluPrintID").change(bluPrintIDchanged).keyup(bluPrintIDchanged);
    function bluPrintIDchanged(){
        if(regexUnityID("avtr",$("#avatar-bluPrintID").val())){
                //$("#SetPublicAvatarChecked").removeClass("d-none");
                $("#SetAvatarPortrait").css("color","lightgreen");
        }
        else {
                //$("#SetPublicAvatarChecked").addClass("d-none");
                $("#SetAvatarPortrait").css("color","#FA5882");
        }
    }
    
    $("#SetDefaultAvatar").click(function(){ 
        $("#avatar-bluPrintID").val("avtr_53856003-8ff2-4002-b78f-da5d028b22bd").trigger("keyup");
        $("#SetPublicAvatar").click();
    })
    
    $("#SetPublicAvatar").click(function(){ 
        var AvatarID = regexUnityID("avtr",$("#avatar-bluPrintID").val());
        if(!AvatarID || !confirm("Change Avatar?")) return;
        
        $("#SetPublicAvatar").prop('disabled', true);
        $("#SetAvatarStatus").html("");
        
        $.ajax({
            url: "/api/1/avatars/" + AvatarID + "/select",
            type: "PUT"
        }).done(function( json ) { //console.error(html);
            if(json.currentAvatar == AvatarID){
                $("img.img-thumbnail.rounded-circle.float-left.home-avatar:eq(0)")
                    .removeAttr("src").attr("src",json.currentAvatarThumbnailImageUrl)
                $("#SetAvatarStatus").html("Avatar Changed.");
            }
            else {
                $("#SetAvatarStatus").html("error?");
            }
            
            //$("#SetAvatarStatus").html($("#SetAvatarStatus").html() + "<img src='" + json.currentAvatarThumbnailImageUrl + "' />");
            $("#SetAvatarStatus").html($("#SetAvatarStatus").html() 
                + "<a target='_blank' href='" + json.currentAvatarImageUrl + "'><br>"
                + "<img style='width:200px;height:150px;' src='" + json.currentAvatarThumbnailImageUrl + "' /></a>");
            
            $("#SetAvatarStatusDiv").show();
            $("#SetPublicAvatar").prop('disabled', false);
            $(styleElement).find("span.fa-portrait").css("color","#F4FA58");
        }).fail(function( xhr, status, error ) {  console.error(status); console.error(error);
            $("#SetAvatarStatusDiv").show();
            $("#SetPublicAvatar").prop('disabled', false);
            //alert(" Failed to Change Avatar. Private avatar is not available. \n Or maybe you type a wrong Avatar ID");
            $("#SetAvatarStatus").html(" Failed to Change Avatar. <br> Wrong Avatar ID? <br> Private avatar is not available.");
            $(styleElement).find("span.fa-portrait").css("color","black");
        })
    });
}



var PageStates = {};
function HomePageFunc(){
    var MenuNode = $("a.btn-secondary.text-left[title='worlds']:eq(0)");
    if(!$(MenuNode).length || $(MenuNode).hasClass("LittleONE_done")) return;
    /*
    $("div.friend-container:eq(0)")
        .css("height","calc(100% - 145px)")
        //.parent().find("h3:eq(0)").css("white-space","nowrap").hide();
    */
    
    $('head').append('<link rel="stylesheet" href="' + GM_getResourceURL("JqueryUIcss") + '" type="text/css" />');
    
    $(MenuNode).addClass("LittleONE_done");
    
    $("span.copyright:eq(0)").html( ""
        + "<a target='_blank' style='color:#BDBDBD;' href='https://greasyfork.org/zh-TW/scripts?set=327266'>" 
        +   $("span.copyright:eq(0)").html() 
        + "</a>&nbsp;&nbsp;"
        + "<a target='_blank' style='color:white;' href='https://greasyfork.org/zh-TW/scripts/376747-vrchat-littleone'>" 
        +   "<font color='#67d781'>VRChat LittleONE</font>&nbsp;v<font color='#0040FF'>" + GM_info.script.version + "</font>"
        + "</a>");
        
    //$("div.w-100.btn-group-lg.btn-group-vertical > a[title='home']:eq(0)").append('&nbsp;<span aria-hidden="true" class="fa fa-angle-right fa-1.5x"></span>');
    
    // HomePage Home button Yellow
    $("a.btn.btn-secondary.text-left[title='home']:eq(0)").css("color","yellow");
    // HomePage World button green 
    $("a.btn.btn-secondary.text-left[title='worlds']:eq(0)").css("color","#67d781");
    
    // 左側邊欄縮排
    /*
    var leftBar = $("div.container-fluid div.bg-gradient-secondary.leftbar:eq(0)")
    $(leftBar)
        //.width("3.7%") // .width("55px")
        .find("a.btn.btn-secondary.text-left").each(function(){
            $(this).contents().filter(function(){ return this.nodeType === 3; }).eq(0).remove();
        })
    */
    /*
    $( leftBar ).animate({
        width: "fit-content",
        height: "toggle"
        }, {
        duration: 5000,
        specialEasing: {
          width: "linear",
          height: "easeOutBounce"
        },
        complete: function() {
          $( this ).after( "<div>Animation complete.</div>" );
        }
    });
    */
    // 中間資訊區域靠左並延展
    /*
    var widthPercentage = GetWidthPercentage($(leftBar)) * 1.8;
    var flexPercentage  = (100 - GetWidthPercentage($("div.bg-gradient-secondary.rightbar:eq(0)")) * 1.35) + "%";
    $("div.container-fluid div.offset-lg-2.col-lg-7.col-xs-12:eq(0)")
        .css("margin-left",widthPercentage + "%").css("flex","0 0 " + flexPercentage).css("max-width",flexPercentage)
    */
    return;
    //==================================================================================================================
    /*
    window.onpopstate = function(event) {
        //alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
        console.error("location: " + document.location + ", state: " + JSON.stringify(event.state));
    };
    */
    
    // https://www.vrchat.com/api/1/auth/user/playermoderated
    $(MenuNode).clone().html("<span aria-hidden='true' class='fa fa-user'></span>&nbsp;&nbsp;Friends History")
    .attr("title","FriendsHistory").insertAfter($(MenuNode)).attr("href","#").removeAttr("href")
    .click(function(){
        if(PageStates.Last != "FriendsHistory"){
            PageStates.Last2 = PageStates.Last;
            PageStates.Last  = "FriendsHistory";
        }
        
        $("a.btn.btn-secondary.text-left[title='download']:eq(0)").click();
        return;
        
        //$("div.home-content:eq(0)").append('<div id="dialog" title="Friends History"><p>Working Progress....</p></div>');
        /*
        $( "#dialog" ).dialog({
             position: { my: "left top", at: "left top", of: $("div.home-content:eq(0)") }
            ,width: $("div.home-content:eq(0)").width()
        });
        */
        //$("div.home-content:eq(0) > div:eq(0) > div:eq(0)").html("ccc");
        //$("div.home-content:eq(0) > div:eq(0)").html(""
        
        $("div.home-content:eq(0) > div:eq(0)").html(""
            + '<div class="center-block text-center justify-content-center mb-4 row">'
            +   '<div class="col-6">'
            +       '<h3> Working Progress....</h2>'
            +   '</div>'
            + '</div>').attr("class","");
        
        //history.pushState({"key":"FriendsHistory"}, "FriendsHistory", "/home#FriendsHistory");
        history.pushState({"key":"FriendsHistory"}, "FriendsHistory", "/home/FriendsHistory");
        //history.replaceState({"key":"FriendsHistory"}, "DownloadPage", "/home/FriendsHistory");
        
        var currentState = history.state;
        console.error("location: " + document.location + ", state: " + JSON.stringify(history));
        
        /*
        var currentState = history.state;
        console.error("location: " + document.location + ", state: " + JSON.stringify(currentState));
        
        
        
        //location.hash = "ccc";
        history.replaceState({"key":"FriendsHistory"}, "FriendsHistory", "/home/FriendsHistory");
        */
    })
    
    $("a.btn.btn-secondary.text-left:not(.LittleONE_done)").click(function(){
        var CurrentTitle = $(this).attr("title");
        
        if(history.state == null || history.state.key == null) return;
        
        if(CurrentTitle == "home" && PageStates.Last == "FriendsHistory"){
            history.replaceState({"key":PageStates["download"]}, "download", "/home");
            //$("a.btn.btn-secondary.text-left[title='download']:eq(0)").click();
            //$("a.btn.btn-secondary.text-left[title='home']:eq(0)").click();
        }
        
        PageStates.Last2                        = PageStates.Last;
        PageStates.Last                         = CurrentTitle;
        PageStates[PageStates.Last]             = history.state.key;
        PageStates["Location_" + CurrentTitle]  = document.location;
        
        console.error("location: " + document.location + ", state: " + JSON.stringify(history.state.key));
        console.error(PageStates);
        
    });
    //==================================================================================================================
}
    
function SteamIDLinkToPage(element){
    // https://zh.wikipedia.org/zh-tw/File:Steam_icon_logo.svg
    //<img class='steam' width='30.4' height='30.4' src='https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Steam_icon_logo.svg/240px-Steam_icon_logo.svg.png'/>
    $(element).html("<a target='_blank' style='color:white;' href='https://steamcommunity.com/profiles/" + $(element).html().substring(6) + "'>" + $(element).html() + "</a>");
    
    var avatarIMG = $("img.online[src*='steam'],img.offline[src*='steam']").eq(0);
    $(avatarIMG).attr("src", $(avatarIMG).attr("src").replace("_medium.","_full."));
}

function LaunchPage(element){ // Beta
    var WorldID     = getQueryString("worldId");
    var InstancesID = getRndInteger(100, 99999);
    var usr_empty   = "usr_30ae0fcd-ed69-4f55-b4f3-34f5272f7344";
    var htmlSrc     = `<a href="vrchat://launch?id=` + WorldID + `:%room-id%~%launch-link%" class="btn btn-primary launch-btn" style="color:black;background-color:%Launch-bcolor%;min-width:230px;text-align:left;">%launch-text%</a>`
    
    var btns        = [
         LoadFormatText(htmlSrc,{ "%room-id%"        : "1"
            ,"%launch-link%"    : "pub"
            ,"%Launch-bcolor%"  : "yellow"
            ,"%launch-text%"    : "Public:1"}).out
        ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
            ,"%launch-link%"    : "pub"
            ,"%Launch-bcolor%"  : "yellow"
            ,"%launch-text%"    : "Public:" + InstancesID}).out
        ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
            ,"%launch-link%"    : "hidden(%usr_empty%)"
            ,"%usr_empty%"      : usr_empty
            ,"%Launch-bcolor%"  : "#8143E6"
            ,"%launch-text%"    : "Friends+"}).out
        ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
            ,"%launch-link%"    : "canRequestInvite(%usr_empty%)"
            ,"%usr_empty%"      : usr_empty
            ,"%Launch-bcolor%"  : "#2BCF5C"
            ,"%launch-text%"    : "Invite+"}).out
    ]
    
    var userID      = GM_getValue("cur_userID"      ,null);
    var displayName = GM_getValue("cur_displayName" ,null);
    //userID = null;
    
    if(userID && displayName){
        btns.push(
             LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
                ,"%launch-link%"    : ""
                ,"%Launch-bcolor%"  : "#f5b501"
                ,"%launch-text%"    : displayName}).out
            ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
                ,"%launch-link%"    : "friends(" + userID + ")"
                ,"%Launch-bcolor%"  : "#FF7B42"
                ,"%launch-text%"    : "Friends"}).out
            ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
                ,"%launch-link%"    : "private(" + userID + ")"
                ,"%Launch-bcolor%"  : "#1778FF"
                ,"%launch-text%"    : "Invite"}).out)
    } else {
        btns.push(LoadFormatText(htmlSrc,{
             "%room-id%"        : ""
            ,"%launch-link%"    : ""
            ,"%usr_empty%"      : ""
            ,"%Launch-bcolor%"  : "red"
            ,"%launch-text%"    : "LoadData..."}).out)
    }
    
    $(element).parent().append("<br>");
    $.each(btns, function(i, value) { //console.error(key + " - " + value);
        $(element).parent().append(value).append("<br>");
    })
    
    if(userID && displayName) return;
    $.get( "/api/1/auth/user").done(function( json ){ //console.error(json);
        json = JSON.parse(json);
        if(!json.id){
            $("a.launch-btn:contains('LoadData...'):eq(0)").html("Not login");
            return;
        }
        
        GM_setValue("cur_userID"      ,json.id);
        GM_setValue("cur_displayName" ,json.displayName);
        
        var btns2       = [
             LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
                ,"%launch-link%"    : ""
                ,"%Launch-bcolor%"  : "#f5b501"
                ,"%launch-text%"    : json.displayName}).out
            ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
                ,"%launch-link%"    : "friends(" + json.id + ")"
                ,"%Launch-bcolor%"  : "#FF7B42"
                ,"%launch-text%"    : "Friends"}).out
            ,LoadFormatText(htmlSrc,{ "%room-id%"        : InstancesID
                ,"%launch-link%"    : "private(" + json.id + ")"
                ,"%Launch-bcolor%"  : "#1778FF"
                ,"%launch-text%"    : "Invite"}).out
        ]
        
        $("a.launch-btn:contains('LoadData...'):eq(0)").remove();
        $.each(btns2, function(i, value) { //console.error(key + " - " + value);
            $(element).parent().append(value);
        })
    }).fail(function( xhr, status, error ) { 
        $("a.launch-btn:contains('LoadData...'):eq(0)").html("Not login");
        console.error(xhr);
        console.error(JSON.stringify(xhr.responseJSON, null, 4));
        alert(JSON.stringify(xhr.responseJSON.error, null, 4));
    })
    
    
    
    // -----------------------------------------------------------------------------------------------------------------
    return;
    // https://stackoverflow.com/a/30680994
    $("head").append("<style>::-webkit-scrollbar {width: 0px;  /* remove scrollbar space */background: transparent;  /* optional: just make scrollbar invisible */}");
    
    var WorldID = getQueryString("worldId");
    $("span.world:eq(0)").html("<a href='/home/world/" + WorldID + "' target='_blank'>" + $("span.world:eq(0)").html() + "</a>");
    
    var InstancesID = getRndInteger(100, 99999)
    var prevText    = "<a class='btn btn-primary btn-lg' href='vrchat://launch?ref=" + document.location.host + "&id=" + WorldID + ":";
    var nextText    = "</a>";
    
    $("#launch").parent()
        .after(LoadFormatText("<p>"
            +       "%prevText%1            '>Public:1"                                           + "%nextText%"
            + "&nbsp;%prevText%%InstancesID%'>Public"                                             + "%nextText%"
            + "&nbsp;%prevText%%InstancesID%" + "~hidden(%usr_empty%)'>Friends+"                  + "%nextText%"
            + "&nbsp;%prevText%%InstancesID%" + "~friends(%usr_empty%)'>Friends"                  + "%nextText%"
            + "&nbsp;%prevText%%InstancesID%" + "~private~canRequestInvite(%usr_empty%)'>Invite+" + "%nextText%"
            + "&nbsp;%prevText%%InstancesID%" + "~private(%usr_empty%)'>Invite"                   + "%nextText%"
            + "</p>"
        ,{
             "%prevText%"   : prevText
            ,"%InstancesID%": InstancesID
            ,"%nextText%"   : nextText
            // usr_id from https://forum.gamer.com.tw/Co.php?bsn=60076&sn=48167935
            ,"%usr_empty%"  : "usr_30ae0fcd-ed69-4f55-b4f3-34f5272f7344"
        }).out);
    
        /*
        //.after("<p>"   + prevText + "~public'>Public"   + nextText
        .after("<p>"   + prevText + "'>Public"          + nextText
            + "&nbsp;" + prevText + "~hidden'>Friends+" + nextText
            + "&nbsp;" + prevText + "~friends'>Friends" + nextText
            + "&nbsp;" + prevText + "~private~canRequestInvite()'>Invite+" + nextText
            + "&nbsp;" + prevText + "~private'>Invite"  + nextText
            + "</p>");
        */
            
    $("div.row > div:eq(0)").css("opacity","0.75");
    $("body").css("background-color","black");
    //$("div.bg").css("position","relative");
}

function FriendList(){
    $("body").html(`<h2 align="center">This page is Incomplete / Under Construction v0.4</h2><br>`);
    $("head").append(`
    <title>VRChat LittleONE Friend List</title>
    <link rel="stylesheet" href="/public/css/vrchatstrap.css" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,400i,700" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Dosis:300,400,400i,700" rel="stylesheet">
    <link rel="stylesheet" href="https://assets.vrchat.com/www/font-awesome/css/font-awesome.min.css" type="text/css">
    <link rel="stylesheet" href="https://assets.vrchat.com/www/css/animate.min.css" type="text/css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
    
    <style>
        body { 
            background-color: #1a2026; 
            color: white;
        }
        
        /* https://www.w3schools.com/howto/howto_css_custom_checkbox.asp */
        /* Customize the label (the container) */
        .checkboxContainer {
          display: block;
          position: relative;
          padding-left: 35px;
          margin-bottom: 1px;
          cursor: pointer;
          font-size: 20px;
          -webkit-user-select: none;
          -moz-user-select: none;
          -ms-user-select: none;
          user-select: none;
        }

        /* Hide the browser's default checkbox */
        .checkboxContainer input {
          position: absolute;
          opacity: 0;
          cursor: pointer;
          height: 0;
          width: 0;
        }

        /* Create a custom checkbox */
        .checkmark {
          position: absolute;
          top: 0;
          left: 0;
          height: 25px;
          width: 25px;
          background-color: #eee;
        }

        /* On mouse-over, add a grey background color */
        .checkboxContainer:hover input ~ .checkmark {
          background-color: #ccc;
        }

        /* When the checkbox is checked, add a blue background */
        .checkboxContainer input:checked ~ .checkmark {
          background-color: #2196F3;
        }
        
        .checkboxContainer input[disabled] ~ .checkmark {
          background-color: red;
        }

        /* Create the checkmark/indicator (hidden when not checked) */
        .checkmark:after {
          content: "";
          position: absolute;
          display: none;
        }

        /* Show the checkmark when checked */
        .checkboxContainer input:checked ~ .checkmark:after {
          display: block;
        }

        /* Style the checkmark/indicator */
        .checkboxContainer .checkmark:after {
          left: 9px;
          top: 5px;
          width: 5px;
          height: 10px;
          border: solid white;
          border-width: 0 3px 3px 0;
          -webkit-transform: rotate(45deg);
          -ms-transform: rotate(45deg);
          transform: rotate(45deg);
        }
        
        /* https://stackoverflow.com/a/13356401 */
        div.topcorner {
            display: none;
            
            width: 667px;
            height: 500px;
            position: fixed;
            top: 0;
            right: 0;
            z-index: 10;
            opacity: 0.9;
            
            background-repeat: no-repeat;
            background-size: contain;
            background-position: center top;
            
            /*  position: absolute;
                bottom: 0; 
                left: 0;
                margin: auto;
                background-color: black;
            */
        }
        
        div.topcorner#ImageShow2 {
            z-index: 15;
            opacity: 1.0;
        }
    </style>
    `);
    
    $("body").append(`
    <div>
        <h3>Load Options <a class="dataContent" id="LoadStatus"></a></h3>
    <div>
    <table border="0">
    <tr><td>
    <label class="checkboxContainer"> Load User Data
        <input class="LoadOptions" type="checkbox" id="LUD" data-func="getUserData" disabled checked> 
        <span class="checkmark"></span>
    </label>
    
    </td><td>:</td><td style="float:right;">
        <div class="dataContent" id="LUD_Status" data-func="getUserData" style="display:inline;"></div>
    </td></tr>
    
    <tr><td>
    <label class="checkboxContainer"> Load Block you Data
        <input class="LoadOptions" type="checkbox" id="LByD" data-func="getBlockDataByUser" checked>
        <span class="checkmark"></span>
    </label>
    </td><td>:</td><td style="float:right;">
        <div class="dataContent" id="LByD_Status" data-func="getBlockDataByUser" style="display:inline;"></div>
    </td></tr>
    
    <tr><td>
    <label class="checkboxContainer"> Load you Block Data
        <input class="LoadOptions" type="checkbox" id="LyBD" data-func="getBlockDataByYou" checked>
        <span class="checkmark"></span>
    </label>
    </td><td>:</td><td style="float:right;">
        <div class="dataContent" id="LyBD_Status" data-func="getBlockDataByYou" style="display:inline;"></div>
    </td></tr>
    
    <tr><td> 
    <label class="checkboxContainer"> Load Online  Friends
        <input class="LoadOptions" type="checkbox" id="LOnF" data-func="getFriendsOnline" checked>
        <span class="checkmark"></span>
    </label>
    </td><td>:</td><td style="float:right;">
        <div class="dataContent" id="LOnF_Status" data-func="getFriendsOnline" style="display:inline;"></div>
    </td></tr>
    
    <tr><td> 
    <label class="checkboxContainer"> Load Offline Friends
        <input class="LoadOptions" type="checkbox" id="LOfF" data-func="getFriendsOffline" checked>
        <span class="checkmark"></span>
    </label>
    </td><td>:</td><td style="float:right;">
        <div class="dataContent" id="LOfF_Status" data-func="getFriendsOffline" style="display:inline;"></div>
    </td></tr>
    </table>
    <br>
    </div>
    
    <h3>Appearance</h3>
    <div>
    <table border="0">
    <tr><td>
    <label class="checkboxContainer"> Show Avatar  Thumbnail
        <input class="LoadOptions" type="checkbox" id="SAT">
    <span class="checkmark"></span>
    </label>
    </td></tr>
    </table>
    <br>
    </div>
    
    <h3>Sort Options</h3>
    <button type="button" class="btn btn-primary" id="">displayName</button>
    <button type="button" class="btn btn-primary" id="">userName</button>
    <button type="button" class="btn btn-primary" id="">NotFriend</button>
    
    <br><h3></h3>
    <button type="button" class="btn btn-primary" id="Lone_GetFriend">Get Friend List</button>
    <div class="dataContent" id="userContent"></div>
    <div class="topcorner"   id="ImageShow"></div>
    <div class="topcorner"   id="ImageShow2"></div>
    `);
    
    
    let template_user   = `
    <% for(let i=0; i < json.length; i++) { %>
    <% let onlineColor  = json[i].location == 'offline' ? "white":"green" %>
    <% let statusColor  = StatusColor[json[i].status] %>
    <% let trustColor   = GetTrusted(json[i].tags)[0] %>
    <% let pastName     = GM_getValue(json[i].id + "_pastName","") %>
    <% let image        = $("#SAT").prop("checked") == true ? json[i].currentAvatarThumbnailImageUrl:"https://d348imysud55la.cloudfront.net/thumbnails/1904009139.thumbnail-500.png" %>
    
    <div class="text-left friend-row" 
         style="display:flex;margin-left:15px;margin-top:10px;"
         user_id="<%= json[i].id %>">
        
        <img class="img-thumbnail rounded float-left friend-img Lone_world_friend" 
             bak="<%= json[i].currentAvatarThumbnailImageUrl %>" 
             ba2="<%= json[i].currentAvatarImageUrl %>" 
             ori="<%= json[i].currentAvatarImageUrl %>" 
             src="<%= image %>" 
             title="%display-title%" 
             style="width:160px;height:120px;border-right-width:6px;border-right-color:<%= trustColor %>;border-left:<%= onlineColor %> 6px outset;">
             
        <div class="friend-caption text-success" style="display:none;">
          <ul>
            <!--
              <li>____index____:&nbsp;<%= json[i].id %></li>
            -->
              <li>___userID____:&nbsp;<%= json[i].id %></li>
              <li>____friendKey:&nbsp;<%= json[i].friendKey %></li>
              <li>___userName:&nbsp;<%= json[i].username %></li>
              <li>___pastName:&nbsp;<%= pastName %></li>
              <li>displayName:<a target="_blank" href="/home/user/<%= json[i].id %>" style="display:inline;">
                  <font color="<%= statusColor %>"><b><%= json[i].displayName %></b></font></a></li>
              <li><%= json[i].statusDescription %><font style="opacity:0.0;">-</font></li>
              <!--
              <li><font style="display:%rank-hide%;color:white !important;">Appearing as <font style="color:#67d781;">User</font> Rank</font></li>
              -->
          </ul>
        </div>
        
        
        <div class="friend-caption text-success">
        <table border="0" style="margin-left:5px;color:white;">
        <tr><td style="float:right;">userID:</td>
            <td><a style="float:right;"><%= json[i].id %></a></td></tr>
        <tr><td style="float:right;">friendKey:</td>
            <td><a style="float:right;"><%= json[i].friendKey %></a></td></tr>
        <tr><td style="float:right;">userName:</td>
            <td><%= json[i].username %></td></tr>
        <tr><td style="float:right;">pastName:</td>
            <td><b><%= pastName %></b></td></tr>
        <tr><td style="float:right;">displayName:</td>
            <td><a target="_blank" href="/home/user/<%= json[i].id %>" style="display:inline;"><font color="<%= statusColor %>"><b><%= json[i].displayName %></b></font></a></td></tr>
        <tr><td style="float:right;">Description:</td>
            <td><%= json[i].statusDescription %><font style="opacity:0.0;">-</font></td></tr>
        <tr><td style="float:right;">LastLogin:</td>
            <td class="last_login"><%= json[i].last_login %></td></tr>
        <tr><td style="float:right;">LastLogin:</td>
            <td class="last_login_fromNow"><%= json[i].last_login %></td></tr>
        </table>
        </div>
        
    </div>
    
    <% } %>`
    
    $("#Lone_GetFriend").click( async function(){ 
        $(".dataContent").html("");
        $( "#LoadStatus" ).append(" > Loading")
        
        let LoadOptionsChecked = $( ".LoadOptions:checked[data-func]" ).toArray().map(v => $(v).attr("data-func"));
        let FL = $.extend(true, {}, FriendLister);
        FL.runSpecific(LoadOptionsChecked,function(status, data){
            if(status.status == "keepGoing"){
                let kv      = FL["init"][status.key.substr(3)];
                let Length  = kv.length || kv.toString().length || "";
                $(`.dataContent[data-func="${status.key}"]`).html(Length);
                return;
            }
            
            $( "#LoadStatus" ).append(` > Done.`)
            let parse       = eval(compile(template_user));
            let json        = FL["init"]["FriendsOnline"].concat(FL["init"]["FriendsOffline"]);
            let dataFriends = parse(json);
            $("#userContent").append(dataFriends);
            
            $(".img-thumbnail").hover(function(){
                let bak = $(this).attr("bak");
                let ba2 = $(this).attr("ba2");
                //$("#ImageShow").html(`<img src="${src}" width="667" height="500" />`);
                $("#ImageShow") .css("background-image",`url("${bak}")`).show();
                $("#ImageShow2").css("background-image",`url("${ba2}")`).show();
                
            },function(){ 
                $("#ImageShow , #ImageShow2").css("background-image",``).hide();
            })
            
            $( "#LoadStatus" ).append(` ( ${FL["init"]["LoadTime"]} ms )`)
            
            $(".last_login").each(function(){
                $(this).html( moment($(this).html()).format("YYYY-MM-DD[T]HH:mm") )
            })
            
            $(".last_login_fromNow").each(function(){
                $(this).html( moment($(this).html()).fromNow(true) )
            })
            
        });
    })
}

let GetFriendsMulti = { //New Version
    def           : {
         max       : 100
        ,offset    : 0
        ,userArray : []
        ,offline   : false
        ,multi     : 2
        ,running   : 0
        ,end       : false
    },
    firstRun:  async function(callback, obj) { if(!callback) return;
        let self    = this;
        self        = Object.assign(self, self.def || {});
        self        = Object.assign(self, obj      || {});
        self.offset-= self.max;
        self.run(callback);
    },
    run:       async function(callback, obj)   { let self  = this;
        for(let i = 1; i <= self.multi;i++) {
            self.getNew(async function(...args) {
                                      callback.apply(this, ["keepGoing", self]);
                if(self.running == 0) callback.apply(this, ["success"  , self]);
            });
        }
    },
    getNew:    async function(callback)        { let self  = this;
        if(self.end) { callback.apply(this, []); return; }
        
        self.running   ++
        self.offset    += self.max;
        let offsetTemp  = self.offset;
        let [data, err] = await getAPI("auth/user/friends/?offline=" + self.offline + "&n=" + self.max + "&offset=" + self.offset);
        if(data.length == 0 || data.length < self.max) self.end = true;
        self.userArray  = self.userArray.concat(data);
        self.running   --
        LogInfo("offset:", offsetTemp, "length:", data.length);
        callback.apply(this, []);
        if(!self.end) self.getNew(callback);
    }
}

// https://stackoverflow.com/questions/4616202/self-references-in-object-literals-initializers
let FriendLister = {
    init: {
         UserData           : {}
        ,BlockDataByUser    : []
        ,BlockDataByYou     : []
        ,FriendsOnline      : []
        ,FriendsOffline     : []
        
        ,LoadTime           : 0
        ,error: null
    },
    runAll:         async function(callback){
        const keys = Object.keys(this).filter(val => val.match("^get"));
		for(let i = 0;i < keys.length;i++)       this[keys[i]](callback);
    },
    runAllawait:    async function(callback){
        const keys = Object.keys(this).filter(val => val.match("^get"));
		for(let i = 0;i < keys.length;i++) await this[keys[i]](callback);
        return this.init;
    },
    runAllEndback:  async function(callback){ if(!callback) return false;
        const timestamp     = Date.now();
        const keys          = Object.keys(this).filter(val => val.match("^get"));
        let EndbackCounter  = 1;
		for(let i = 0;i < keys.length;i++){
            this[keys[i]](function(status, ...args){
                callback.apply(this, [{ status: "keepGoing", key: keys[i] }, { d: args } ]);
                if(status == "success" && EndbackCounter++ >= keys.length){
                    LogInfo("runAllEndback used:", Date.now() - timestamp, "ms.");
                    callback.apply(this, [{ status: "success" }, { d: args } ]);
                    return true;
                }
            });
        }
        return false;
    },
    runSpecific:        async function(keys, callback){ if(!callback) return false;
        //keys = keys.filter(f => array.includes(f));    // https://stackoverflow.com/a/41169035
        const timestamp     = Date.now();
        let self            = this;
        let EndbackCounter  = 1;
        for(let i = 0;i < keys.length;i++){
            this[keys[i]](function(status, ...args){
                callback.apply(this, [{ status: "keepGoing", key: keys[i] }, { d: args } ]);
                if(status == "success" && EndbackCounter++ >= keys.length){
                    self.init.LoadTime = Date.now() - timestamp;
                    LogInfo("runSpecific used:", self.init.LoadTime, "ms.");
                    callback.apply(this, [{ status: "success" }, { d: args } ]);
                    return true;
                }
            });
        }
        return false
    },
    getUserData:        async function(callback){
        let [UserData, UD_err0] = await getAPI("auth/user");
        this.init.UserData = UserData || {};
        if(callback) callback.apply(this, ["success", UserData, UD_err0]);
    },
    getBlockDataByUser: async function(callback){
        let [BlockDataByUser, LBD_err0] = await getAPI("auth/user/playermoderated");
        this.init.BlockDataByUser = BlockDataByUser || {};
        if(callback) callback.apply(this, ["success", BlockDataByUser, LBD_err0]);
    },
    getBlockDataByYou:  async function(callback){
        let [BlockDataByYou, LBD_err1] = await getAPI("auth/user/playermoderations");
        this.init.BlockDataByYou = BlockDataByYou || {};
        if(callback) callback.apply(this, ["success", BlockDataByYou, LBD_err1]);
    },
    getFriendsOnline:   async function(callback){ let self = this;
        let GFM_Online = $.extend(true, {}, GetFriendsMulti);
        GFM_Online.firstRun(function(status, obj) {
            //if(status == "keepGoing"){ return; }
            //if(status != "success"  ){ return; }
            self.init.FriendsOnline = obj.userArray || [];
            //console.error("getFriendsOnline", status, obj.userArray);
            if(callback) callback.apply(self, [status, obj.userArray]);
        }, { offline: false } );
    },
    getFriendsOffline: async function(callback){ let self = this;
        let GFM_Offline = $.extend(true, {}, GetFriendsMulti);
        GFM_Offline.firstRun(function(status, obj) {
            //if(status == "keepGoing"){ return; }
            //if(status != "success"  ){ return; }
            self.init.FriendsOffline = obj.userArray || [];
            //console.error("getFriendsOffline", status, obj.userArray);
            if(callback) callback.apply(self, [status, obj.userArray]);
        }, { offline: true } );
    }
}

//let [worldsFav0, err]= await getAPI("favorites/?type=world&n=100&tags=worlds0");
async function GetFavOfWorlds(){
    $("body").html(`
        <!--
            <h2 align="center">This page is Incomplete / Under Construction</h2><br>
        -->
        <h2 align="center">Your Favorited Worlds v1.0</h2><br>
        <div class="fa-8x" style="display: flex;align-items: center;justify-content: center;" id="wLoading">
            <i class="fas fa-atom fa-spin" style="color:#67d781;margin: 0;"></i>
        </div>
        <div class="centerImageUrl pointer"></div>
    `);
    $("div.centerImageUrl").click(function(){ $(this).hide(); });
    
    $("head").append(`
    ${JqueryUIcss}
    
    <title>VRChat LittleONE Worlds List</title>
    <link rel="stylesheet" href="/public/css/vrchatstrap.css" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,400i,700" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Dosis:300,400,400i,700" rel="stylesheet">
    <link rel="stylesheet" href="https://assets.vrchat.com/www/font-awesome/css/font-awesome.min.css" type="text/css">
    <link rel="stylesheet" href="https://assets.vrchat.com/www/css/animate.min.css" type="text/css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
    
    <style>
        body { 
            background-color: #1a2026; 
            color: white;
        }
        
        span.wRStatus[data-status="public"] {
            color: #ff0;
        }
        span.wRStatus[data-status="friends"] {
            color: #FF7B42;
        }
        span.wRStatus[data-status="private"] {
            color: #1fd1ed;
        }
        
        div.wRStatus[data-status="public"] {
            background-color: #ff0 !important;
        }
        div.wRStatus[data-status="friends"] {
            background-color: #FF7B42 !important;
        }
        div.wRStatus[data-status="private"] {
            background-color: #1fd1ed !important;
        }
        
        .wTagNum {
            text-decoration: underline;
            //text-decoration-color: #FF7B42;
            text-decoration-color: #1fd1ed;
        }
        
        .wTagNum[data-wTagNum="32"] {
            text-decoration-color: red;
            font-weight: bold;
            color: gray;
        }
        
        .worldRow {
            display: flex;
            flex-wrap: wrap;
            //flex-direction:column;
            max-width: 90%;
            //margin-left: 10px;
            margin-left: auto;
            margin-right: auto;
            
            // https://ithelp.ithome.com.tw/articles/10211068
            //justify-content: space-around;
            
        }
        
        .fDiv {
            width:336px;
            margin-left: 5px;
        }
        
        .wThumb {
            height:252px;
            background-color: #1f262e;
            position:relative;
            
        }
        
        .wThumb .corner {
            display:none;
        }
        
        /* https://stackoverflow.com/questions/23985018/simple-css-animation-loop-fading-in-out-loading-text */
        @keyframes fadeIn { 
            from { opacity: 0; } 
        }
        
        .wThumb:hover .corner {
            display:inline-block;
            animation: fadeIn 0.5s
        }
        
        .corner {
            z-index: 5;
        }
        
        .wLeftTop {
            display: inline;
            position: absolute;
            left: 2px;
            top: 0px;
        }
        
        .wLeftBottom {
            display: inline;
            position: absolute;
            left: 2px;
            bottom: 2px;
        }
        
        .wRightBottom {
            display: inline;
            position: absolute;
            right: 5px;
            bottom: 1px;
        }
        
        .wCenterCenter {
            display: inline;
            position: absolute;
            top: 50%;
            left: 50%;
            right: 50%;
            bottom: 50%;
        }
        
        .wRightTop {
            display: inline;
            position: absolute;
            right: 5px;
            top: 5px;
        }
        
        .wThumb img {
            z-index: 3;
            border: 0px solid red;
            padding: 5px;
            /*border-bottom-left-radius: 10px 10px;*/
            border-radius: 25px;
        }
        
        .wThumb span.fa {
            width: 24px;
            height: 24px;
            display:none;
        }
        
        .fa-exclamation {
            color: lightpink;
        }
        
        .fa-check {
            color: lightgreen;
        }
        
        .fa-times-circle {
            color: darkred;
        }
        
        .fOpacity:hover {
            opacity: 1.0
        }
        
        .fOpacity {
            opacity: 0.6;
        }
        
        .search-container {
            border:1px solid teal;
            box-shadow:10px 10px 8px 10px #111;
            margin:20px;
            padding:20px;
        }
        .search-container a {
            color:yellow !important;
        }
        
        .search-container .wInfo {
            color:#67d781;
            font-weight:bold;
        }
        
        .wThumbImg {
            min-width: -webkit-fill-available;
        }
        
        .wThumbImg2 {
            max-width:500px;
            max-height:500px;
            
        }
        
        .search-container .wInfo[dType="description"] {
            color: 1fd1ed !important;
            font-weight:bold;
        }
        
        /* https://stackoverflow.com/a/13356401 */
        div.centerImageUrl {
            display: none;
            width: 100%;
            height: 100%;
            background-color: black;
            
            /* position: absolute; */
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            z-index: 10;
            margin: auto;
            
            background-repeat: no-repeat;
            background-size: contain;
            background-position: center top;
        }
        
    </style>
    `)
    
    //https://github.com/cssmagic/CSS-Secrets/issues/11
    let FavTempAll         = {};
    let [groupData , errG] = await getAPI("favorite/groups?type=world");            LogInfo("GetFavOfWorlds groupData:", groupData.length);
    let [worldsFav0, err0] = await getAPI("favorites/?type=world&n=100&offset=0");  
    let [worldsFav1, err1] = await getAPI("favorites/?type=world&n=100&offset=100");
    let worldsAll          = worldsFav0.concat(worldsFav1);                         LogInfo("GetFavOfWorlds worldsAll:", worldsAll.length);
    //console.error(groupData.filter(val => val.type == "world"), worldsAll);
    
    let groupList = {
         worlds0: { dis: "worlds0", cnt: -1, status: "none?" }
      //,worlds1: { dis: "worlds1", cnt: -1, status: "none?" } // canceled by VRC?
        ,worlds2: { dis: "worlds2", cnt: -1, status: "none?" }
        ,worlds3: { dis: "worlds3", cnt: -1, status: "none?" }
        ,worlds4: { dis: "worlds4", cnt: -1, status: "none?" }
    };
    
    LogInfo("GetFavOfWorlds worldsAll&groupData with TAG...");
    $.each(groupData.concat(worldsAll),function(i, v){
        let worldTag = v.tags[0] || v.name || "TagNameError";
        groupList[worldTag]             = groupList[worldTag]   || { tag: worldTag, cnt: 0, status: "none?" };
        groupList[worldTag]["dis"]      = v.displayName         || groupList[worldTag]["dis"];
        groupList[worldTag]["status"]   = v.visibility          || groupList[worldTag]["status"] || "none?";
        groupList[worldTag]["cnt"]++
    })
    
    LogInfo("GetFavOfWorlds groupList appending...");
    $.each(groupList,function(k, v){
        let rowName  = v["dis"];
        let worldTag = k;
        let worldCnt = v["cnt"];
        let status   = v["status"];
        // card-body
        $("body").append(`
            <h3><font>
                    <span title="${status}" aria-hidden="true" class="fa fa-user-shield wRStatus" data-status="${status}"></span>
                    ${worldTag} => 32 / 
                    <font class="wTagNum" id="num_${worldTag}" data-wTagNum="${worldCnt}">${worldCnt}</font>
                </font>
                <div class="card bg-primary wRowTitle n-size wRStatus" data-status="${status}" wTag="${worldTag}">
                    <font color="#8143E6" align="center"><b>${rowName}</b></font>
                </div>
            </h3>
            <div class="worldRow" id="${worldTag}" style="display:none;"></div>
        `)
    });
    
    $(".wRowTitle").click(function(){  $("#" + $(this).attr("wTag")).slideToggle("fast");  });
    
    LogInfo("GetFavOfWorlds worldsAll listing..");
    $.each(worldsAll,function(i, v){
        let favId    = v.id;
        let worldTag = v.tags[0] || "TagNameError";
        let worldId  = v.favoriteId;
        
        $(`#${worldTag}`).append(`
        <div class="fDiv" fid="${favId}" wid="${worldId}">
            <div class="wThumb">
                <div class="corner wLeftTop fa-2x">
                    <a class="wLink" target="_blank" href="/home/world/${worldId}">
                        <i class="fas fa-home"></i>
                    </a>
                    <!--
                    <i class="fas fa-info-circle"></i>
                    -->
                </div>
                <div class="corner wLeftBottom fa-2x">
                    <!--
                    <i class="fas fa-rocket"></i>
                    <i class="fas fa-swatchbook pointer"></i>
                    -->
                </div>
                <div class="corner wRightBottom fa-2x">
                    <!--
                    <i class="fas fa-minus-square pointer" style="color:#FF7B42;background-color:black;border: 1px solid #FF7B42;"></i>
                    -->
                    <i class="fas fa-trash pointer" style="color:#FF7B42;"></i>
                </div>
                <div class="corner wRightTop fa-2x">
                    <span class="badge badge-secondary">
                        <span aria-hidden="true" class="fa fa-exclamation"></span>
                        <span aria-hidden="true" class="fa fa-check"></span>
                        <span aria-hidden="true" class="fa fa-times-circle"></span>
                    </span>
                </div>
                <img class="wThumbImg w-size"/>
            </div>
            <div class="wName" style="position:relative;height:50px;">
                Processing...<br>${favId}
            </div>
        </div>
        `)
        
        // https://developer.mozilla.org/zh-TW/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes
        $(`#${worldTag}`).append(`
        <div class="search-container row" fid="${favId}" style="display:none;">
            <div class="col-12 col-md-4">
                <!--
                <a class="wLink" target="_blank" href="/home/world/${worldId}">
                    <img class="wThumbImg wThumbImg2 w-100" src="" big="">
                </a>
                -->
                <img class="wThumbImg wThumbImg2 w-100" src="" big="">
            </div>
            <div class="col-12 col-md-8">
                <h4>
                    <a class="wInfo wLink" dType="name" target="_blank" href="/home/world/${worldId}">???</a>&nbsp;<small>— &nbsp;
                    <a class="wiUser wInfo" dType="authorName" target="_blank" href="/home/user/">??</a></small>
                </h4>
                <div class="row">
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-user"></span>
                            &nbsp;Players</h6>
                        <span class="wInfo" dType="occupants">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-star"></span>
                            &nbsp;Favorites</h6>
                        <span class="wInfo" dType="favorites">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-thermometer-empty"></span>
                            &nbsp;Heat</h6>
                        <span class="wInfo" dType="heat">-----</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-award"></span>
                            &nbsp;Popularity(聲望)</h6>
                        <span class="wInfo" dType="popularity">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-eye"></span>
                            &nbsp;Visits</h6>
                        <span class="wInfo" dType="visits">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-users"></span>
                            &nbsp;Capacity</h6>
                        <span class="wInfo" dType="capacity">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-plus-square"></span>
                            &nbsp; Created</h6>
                        <span class="wInfo" dType="created_at">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Published" aria-hidden="true" class="fa fa-bullhorn"></span>
                            &nbsp; Published</h6>
                        <span class="wInfo" dType="publicationDate">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-plus-square"></span>
                            &nbsp; Updated</h6>
                        <span class="wInfo" dType="updated_at">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Lab Published" aria-hidden="true" class="fa fa-bullhorn"></span>
                            &nbsp; Lab Published</h6>
                        <span class="wInfo" dType="labsPublicationDate">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Version" aria-hidden="true" class="fa fa-code-branch"></span>
                            &nbsp; version</h6>
                        <span class="wInfo" dType="version">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Release" aria-hidden="true" class="fa fa-user-shield"></span>
                            &nbsp; Release</h6>
                        <span class="wInfo" dType="releaseStatus">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Release" aria-hidden="true" class="fa fa-play-circle"></span>
                            &nbsp; Youtube Trailer</h6>
                        <span class="wInfo" dType="previewYoutubeId">?</span>
                    </div>
                    
                    <!-------------------------------------------------------
                    <div class="col-12 col-sm-6 col-md-6">
                        <p class="tags">
                        <span>
                            <span class="badge badge-secondary">
                            <a href="/home/search/tag:content_featured"><span title="content_featured" aria-hidden="true" class="fa fa-hashtag">
                        </span> &nbsp;content_featured</a>
                        </span>&nbsp;</span>
                        </p>
                    </div>
                    --------------------------------------------------------->
                    
                </div>
                <div class="row"><p class="wInfo" dType="description"></p></div>
            </div>
        </div>
        `)
        
        $("img.wThumbImg2").click(function(){
            let imageSrc = $(this).attr("big");
            $("div.centerImageUrl").css("background-image",`url('${imageSrc}')`).fadeIn("fast");
        });
        
        if(!worldId){
            getAPI(`favorites/${favId}`).then(async function(value){ value = value[0];
                if(!value.favoriteId){
                    console.error("value.favoriteId: ", value.favoriteId);
                    return;
                }
                
                $(`.search-container[fid="${favId}"] .wLink`).attr("href","/home/world/" + value.favoriteId);
                $(`.fDiv[fid="${favId}"] .wLink:eq(0)`).attr("href","/home/world/" + value.favoriteId);
                $(`.fDiv[fid="${favId}"]  img:eq(0)`).addClass("fOpacity");
                
                let wData = await getWorldData(value.favoriteId);
                if(!wData){
                    $(`.fdiv[fid="${favId}"] .fa-times-circle`).css("display","inline-block");
                    //$(`.fDiv[fid="${favId}"] .wThumb:eq(0)`).css("background-color","#798897");
                    $(`.fDiv[fid="${favId}"] .wThumb:eq(0)`).css("background-color","black");
                    $(`.fdiv[fid="${favId}"] div.wname:eq(0)`).html(`
                        <font color="red"><b>Not Available ( Deleted )</b></font> => 
                        <a target="_blank" href="https://www.google.com/search?q=site:www.vrcw.net ${value.favoriteId}">
                                Search on Google
                           </a>
                    `);
                } else {
                    $(`.fdiv[fid="${favId}"] .fa-exclamation`).css("display", "inline-block");
                    $(`.fDiv[fid="${favId}"] .wThumb:eq(0)`).css("background-color", "lightpink");
                    $(`.fdiv[fid="${favId}"] div.wname:eq(0)`).css("font-weight", "bold").css("color","lightpink")
                }
            })
            return;
        }
        
        getWorldData(worldId);
        async function getWorldData(worldNewId){ if(!worldNewId) return null;
        return await getAPI(`worlds/${worldNewId}`).then(value => { if( !value || !value[0] || value[1] ) return null;
            value = value[0];
            FavTempAll[favId] = value;
            //console.error(worldNewId);
            //console.error(value);
            //console.error(value.thumbnailImageUrl, value.name)
            
            let worldThumb = value.thumbnailImageUrl;
            //let worldThumb = value.imageUrl;
            let worldName  = value.name;
            $(`.fdiv[fid="${favId}"] img.wThumbImg`).attr("src",worldThumb);
            $(`.fdiv[fid="${favId}"] div.wname:eq(0)`).html(`${worldName}`);
            if(value.releaseStatus == "public"){
                //$(`.fdiv[fid="${favId}"] .fa-check`).css("display","inline-block");
                $(`.fdiv[fid="${favId}"] span.badge`).hide();
            }
            
            $(`.fdiv[fid="${favId}"] div.wThumb:eq(0) img:eq(0)`).click(function(){
                $(`.search-container[fid!="${favId}"]`).hide("fast");
                $(`.search-container[fid="${favId}"]:eq(0)`).toggle("fast");
            });
            
            $(`.search-container[fid="${favId}"] img.wThumbImg`)
                .attr("src",worldThumb)
                .attr("big",value.imageUrl);
            $(`.search-container[fid="${favId}"] .wiUser`).attr("href","/home/user/" + value.authorId);
            $(`.search-container[fid="${favId}"] .wInfo`).each(function(){
                let dataTag  = value[ $(this).attr("dType") ] || null;
                let dataTime = 
                    /\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2]\d|3[0-1])T(?:[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d.\d[0-9]+?Z/
                        .test(dataTag) 
                            ? moment(dataTag).format("YYYY-MM-DD _ HH:mm:ss") + "<br>" + moment(dataTag).fromNow()
                            : null;
                let DataLink = ( $(this).attr("dType") == "previewYoutubeId" && dataTag && dataTag.length > 0 )
                        ? `<a target="_blank" href="https://youtu.be/${dataTag}">Youtube Link</a>`
                        : null
                        
                $(this).html(dataTime || DataLink || dataTag || "-");
            });
            
            let heatFire = (value.heat || 0);
            for(let i = 0; i < value.heat || 0; i++){
                heatFire = heatFire + ( (i == 0 && value.heat > 0) ? ":":"" );
                heatFire = heatFire + `<span aria-hidden="true" class="fa fa-fire"></span>`;
            }
            $(`.search-container[fid="${favId}"] .wInfo[dType="heat"]`).html(heatFire);
            
            return value;
        });
        }
        //return false;
    });
    
    $("body").append("<h3><br>-</h3><br>");
    $("#wLoading").effect( "blind", "slow" );
    
    let DeleteDialog = `
    <div id="dialog-confirm" title="Delete this favorited ITEM?"><p>
      <span class="ui-icon ui-icon-alert" style="float:left; margin:12px 12px 20px 0;"></span>
      Are you sure delete this item?
    </p></div>`;
    
    $(".fa-trash").click(function(){
        let trash = this;
        $(DeleteDialog).dialog({ 
            resizable: false, height: "auto", width: 400, modal: true,
            //position: { my: "center", at: "center", of: trash },
            position: { my: "right top", at: "right bottom", of: trash },
            buttons: { "Delete It!": function() { 
                $( this ).dialog( "close" );
                DeleteFav($(trash).parents("div.fDiv:eq(0)").attr("fid"));
            }, Cancel: function() { $( this ).dialog( "close" ); }}
        });
    });
    
    function DeleteFav(favId){
        $(`.fdiv[fid="${favId}"]`).css("opacity","0.3");
        getAPI(`favorites/${favId}`, {method: "DELETE" } ).then(value => {
            if(!value[0]){
                console.error(value);
                alert("Error!")
                return;
            }
            $(`.fdiv[fid="${favId}"]`).hide("slow");
            //alert(value[0]["success"]["message"]);
        });
    }
}

async function GetFavOfWorlds2(){ // v1.11
    $("body").html(`
        
            <h2 align="center">This page is Incomplete / Under Construction</h2><br>
        
        <h2 align="center">Your Favorited Worlds v1.01</h2><br>
        <div class="fa-8x" style="display: flex;align-items: center;justify-content: center;" id="wLoading">
            <i class="fas fa-atom fa-spin" style="color:#67d781;margin: 0;"></i>
            <div id="GFoW_Status" class=""> 0 / 5</div>
        </div>
        <div class="centerImageUrl pointer"></div>
    `);
    $("div.centerImageUrl").click(function(){ $(this).hide(); });
    
    $("head").append(`
    ${JqueryUIcss}
    
    <title>VRChat LittleONE Worlds List</title>
    <link rel="stylesheet" href="/public/css/vrchatstrap.css" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,400i,700" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Dosis:300,400,400i,700" rel="stylesheet">
    <link rel="stylesheet" href="https://assets.vrchat.com/www/font-awesome/css/font-awesome.min.css" type="text/css">
    <link rel="stylesheet" href="https://assets.vrchat.com/www/css/animate.min.css" type="text/css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
    
    <style>
        body { 
            background-color: #1a2026; 
            color: white;
        }
        
        span.wRStatus[data-status="public"] {
            color: #ff0;
        }
        span.wRStatus[data-status="friends"] {
            color: #FF7B42;
        }
        span.wRStatus[data-status="private"] {
            color: #1fd1ed;
        }
        
        div.wRStatus[data-status="public"] {
            background-color: #ff0 !important;
        }
        div.wRStatus[data-status="friends"] {
            background-color: #FF7B42 !important;
        }
        div.wRStatus[data-status="private"] {
            background-color: #1fd1ed !important;
        }
        
        .wTagNum {
            text-decoration: underline;
            //text-decoration-color: #FF7B42;
            text-decoration-color: #1fd1ed;
        }
        
        .wTagNum[data-wTagNum="32"] {
            text-decoration-color: red;
            font-weight: bold;
            color: gray;
        }
        
        .worldRow {
            display: flex;
            flex-wrap: wrap;
            //flex-direction:column;
            max-width: 90%;
            //margin-left: 10px;
            margin-left: auto;
            margin-right: auto;
            
            // https://ithelp.ithome.com.tw/articles/10211068
            //justify-content: space-around;
            
        }
        
        .fDiv {
            width:336px;
            margin-left: 5px;
        }
        
        .wThumb {
            height:252px;
            background-color: #1f262e;
            position:relative;
            
        }
        
        .wThumb .corner {
            display:none;
        }
        
        /* https://stackoverflow.com/questions/23985018/simple-css-animation-loop-fading-in-out-loading-text */
        @keyframes fadeIn { 
            from { opacity: 0; } 
        }
        
        .wThumb:hover .corner {
            display:inline-block;
            animation: fadeIn 0.5s
        }
        
        .corner {
            z-index: 5;
        }
        
        .wLeftTop {
            display: inline;
            position: absolute;
            left: 2px;
            top: 0px;
        }
        
        .wLeftBottom {
            display: inline;
            position: absolute;
            left: 2px;
            bottom: 2px;
        }
        
        .wRightBottom {
            display: inline;
            position: absolute;
            right: 5px;
            bottom: 1px;
        }
        
        .wCenterCenter {
            display: inline;
            position: absolute;
            top: 50%;
            left: 50%;
            right: 50%;
            bottom: 50%;
        }
        
        .wRightTop {
            display: inline;
            position: absolute;
            right: 5px;
            top: 5px;
        }
        
        .wThumb img {
            z-index: 3;
            border: 0px solid red;
            padding: 5px;
            /*border-bottom-left-radius: 10px 10px;*/
            border-radius: 25px;
        }
        
        .wThumb span.fa {
            width: 24px;
            height: 24px;
            display:none;
        }
        
        .fa-exclamation {
            color: lightpink;
        }
        
        .fa-check {
            color: lightgreen;
        }
        
        .fa-times-circle {
            color: darkred;
        }
        
        .fOpacity:hover {
            opacity: 1.0
        }
        
        .fOpacity {
            opacity: 0.6;
        }
        
        .search-container {
            border:1px solid teal;
            box-shadow:10px 10px 8px 10px #111;
            margin:20px;
            padding:20px;
        }
        .search-container a {
            color:yellow !important;
        }
        
        .search-container .wInfo {
            color:#67d781;
            font-weight:bold;
        }
        
        .wThumbImg2 {
            max-width:500px;
            max-height:500px;
        }
        
        .search-container .wInfo[dType="description"] {
            color: 1fd1ed !important;
            font-weight:bold;
        }
        
        /* https://stackoverflow.com/a/13356401 */
        div.centerImageUrl {
            display: none;
            width: 100%;
            height: 100%;
            background-color: black;
            
            /* position: absolute; */
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            z-index: 10;
            margin: auto;
            
            background-repeat: no-repeat;
            background-size: contain;
            background-position: center top;
        }
        
    </style>
    `);
    
    let [favWorlds0, err00] = await getAPI("worlds/favorites/?n=100&offset=0");     $("#GFoW_Status").html(" 1 / 5");
    let [favWorlds1, err10] = await getAPI("worlds/favorites/?n=100&offset=100");   $("#GFoW_Status").html(" 2 / 5");
    let worldsAllData       = favWorlds0.concat(favWorlds1);
    LogInfo("GetFavOfWorlds worldsAllData:", worldsAllData.length);
    
    //let [worldsFav0, err]= await getAPI("favorites/?type=world&n=100&tags=worlds0");
    //https://github.com/cssmagic/CSS-Secrets/issues/11
    let FavTempAll         = {};
    let [groupData , errG] = await getAPI("favorite/groups?type=world");            $("#GFoW_Status").html(" 3 / 5");
    LogInfo("GetFavOfWorlds groupData:", groupData.length);
    let [worldsFav0, err0] = await getAPI("favorites/?type=world&n=100&offset=0");  $("#GFoW_Status").html(" 4 / 5");
    let [worldsFav1, err1] = await getAPI("favorites/?type=world&n=100&offset=100");$("#GFoW_Status").html(" 5 / 5");
    let worldsAll          = worldsFav0.concat(worldsFav1);
    LogInfo("GetFavOfWorlds worldsAll:", worldsAll.length);
    //console.error(groupData.filter(val => val.type == "world"), worldsAll);
    
    let groupList = {
         worlds0: { dis: "worlds0", cnt: -1, status: "none?" }
      //,worlds1: { dis: "worlds1", cnt: -1, status: "none?" } // canceled by VRC?
        ,worlds2: { dis: "worlds2", cnt: -1, status: "none?" }
        ,worlds3: { dis: "worlds3", cnt: -1, status: "none?" }
        ,worlds4: { dis: "worlds4", cnt: -1, status: "none?" }
    };
    
    LogInfo("GetFavOfWorlds worldsAll&groupData with TAG...");
    $.each(groupData.concat(worldsAll),function(i, v){
        let worldTag = v.tags[0] || v.name || "TagNameError";
        groupList[worldTag]             = groupList[worldTag]   || { tag: worldTag, cnt: 0, status: "none?" };
        groupList[worldTag]["dis"]      = v.displayName         || groupList[worldTag]["dis"];
        groupList[worldTag]["status"]   = v.visibility          || groupList[worldTag]["status"] || "none?";
        groupList[worldTag]["cnt"]++
    })
    
    LogInfo("GetFavOfWorlds groupList appending...");
    $.each(groupList,function(k, v){
        let rowName  = v["dis"];
        let worldTag = k;
        let worldCnt = v["cnt"];
        let status   = v["status"];
        // card-body
        $("body").append(`
            <h3><font>
                    <span title="${status}" aria-hidden="true" class="fa fa-user-shield wRStatus" data-status="${status}"></span>
                    ${worldTag} => 32 / 
                    <font class="wTagNum" id="num_${worldTag}" data-wTagNum="${worldCnt}">${worldCnt}</font>
                </font>
                <div class="card bg-primary wRowTitle n-size wRStatus" data-status="${status}" wTag="${worldTag}">
                    <font color="#8143E6" align="center"><b>${rowName}</b></font>
                </div>
            </h3>
            <div class="worldRow" id="${worldTag}" style="display:none;"></div>
        `)
    });
    
    $(".wRowTitle").click(function(){  $("#" + $(this).attr("wTag")).slideToggle("fast");  });
    
    LogInfo("GetFavOfWorlds worldsAll listing..");
    //$.each(worldsAll,function(i, v){
    $.each(worldsAllData,function(i, v){
        let favId    = v.favoriteId;
        //let worldTag = v.tags[0] || "TagNameError";
        let worldId  = v.id;
        let worldTag = worldsAll.find(o => o.id === v.favoriteId).tags[0] || "TagNameError";
        
        $(`#${worldTag}`).append(`
        <div class="fDiv" fid="${favId}" wid="${worldId}">
            <div class="wThumb">
                <div class="corner wLeftTop fa-2x">
                    <a class="wLink" target="_blank" href="/home/world/${worldId}">
                        <i class="fas fa-home"></i>
                    </a>
                    <!--
                    <i class="fas fa-info-circle"></i>
                    -->
                </div>
                <div class="corner wLeftBottom fa-2x">
                    <!--
                    <i class="fas fa-rocket"></i>
                    <i class="fas fa-swatchbook pointer"></i>
                    -->
                </div>
                <div class="corner wRightBottom fa-2x">
                    <!--
                    <i class="fas fa-minus-square pointer" style="color:#FF7B42;background-color:black;border: 1px solid #FF7B42;"></i>
                    -->
                    <i class="fas fa-trash pointer" style="color:#FF7B42;"></i>
                </div>
                <div class="corner wRightTop fa-2x">
                    <span class="badge badge-secondary">
                        <span aria-hidden="true" class="fa fa-exclamation"></span>
                        <span aria-hidden="true" class="fa fa-check"></span>
                        <span aria-hidden="true" class="fa fa-times-circle"></span>
                    </span>
                </div>
                <img class="wThumbImg w-size" src="${v.thumbnailImageUrl}"/>
            </div>
            <div class="wName" style="position:relative;height:50px;">
                <!--Processing...<br>${favId}-->
                ${v.name}
            </div>
        </div>
        `);
        
        // https://developer.mozilla.org/zh-TW/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes
        $(`#${worldTag}`).append(`
        <div class="search-container row" fid="${favId}" style="display:none;">
            <div class="col-12 col-md-4">
                <!--
                <a class="wLink" target="_blank" href="/home/world/${worldId}">
                    <img class="wThumbImg wThumbImg2 w-100" src="" big="">
                </a>
                -->
                <img class="wThumbImg wThumbImg2 w-100" src="${v.thumbnailImageUrl}" big="${v.imageUrl || v.thumbnailImageUrl}">
            </div>
            <div class="col-12 col-md-8">
                <h4>
                    <a class="wInfo wLink" dType="name" target="_blank" href="/home/world/${worldId}">${v.name}</a>&nbsp;<small>— &nbsp;
                    <a class="wiUser wInfo" dType="authorName" target="_blank">${v.authorName}</a></small>
                </h4>
                <div class="row">
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-user"></span>
                            &nbsp;Players</h6>
                        <span class="wInfo" dType="occupants">${v.occupants || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-star"></span>
                            &nbsp;Favorites</h6>
                        <span class="wInfo" dType="favorites">${v.favorites || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-thermometer-empty"></span>
                            &nbsp;Heat</h6>
                        <span class="wInfo" dType="heat">-----</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-award"></span>
                            &nbsp;Popularity(聲望)</h6>
                        <span class="wInfo" dType="popularity">${v.popularity || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-eye"></span>
                            &nbsp;Visits</h6>
                        <span class="wInfo" dType="visits">${v.visits || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-users"></span>
                            &nbsp;Capacity</h6>
                        <span class="wInfo" dType="capacity">${v.capacity || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-plus-square"></span>
                            &nbsp; Created</h6>
                        <span class="wInfo" dType="created_at">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Published" aria-hidden="true" class="fa fa-bullhorn"></span>
                            &nbsp; Published</h6>
                        <span class="wInfo" dType="publicationDate">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span aria-hidden="true" class="fa fa-plus-square"></span>
                            &nbsp; Updated</h6>
                        <span class="wInfo" dType="updated_at">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Lab Published" aria-hidden="true" class="fa fa-bullhorn"></span>
                            &nbsp; Lab Published</h6>
                        <span class="wInfo" dType="labsPublicationDate">?</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Version" aria-hidden="true" class="fa fa-code-branch"></span>
                            &nbsp; version</h6>
                        <span class="wInfo" dType="version">${v.version || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Release" aria-hidden="true" class="fa fa-user-shield"></span>
                            &nbsp; Release</h6>
                        <span class="wInfo" dType="releaseStatus">${v.releaseStatus || "none"}</span>
                    </div>
                    <div class="col-4 col-sm-3 col-md-3">
                        <h6><span title="Release" aria-hidden="true" class="fa fa-play-circle"></span>
                            &nbsp; Youtube Trailer</h6>
                        <span class="wInfo" dType="previewYoutubeId">?</span>
                    </div>
                    
                    <!-------------------------------------------------------
                    <div class="col-12 col-sm-6 col-md-6">
                        <p class="tags">
                        <span>
                            <span class="badge badge-secondary">
                            <a href="/home/search/tag:content_featured"><span title="content_featured" aria-hidden="true" class="fa fa-hashtag">
                        </span> &nbsp;content_featured</a>
                        </span>&nbsp;</span>
                        </p>
                    </div>
                    --------------------------------------------------------->
                    
                </div>
                <div class="row"><p class="wInfo" dType="description"></p></div>
            </div>
        </div>
        `);
        
        $("img.wThumbImg2").click(function(){
            let imageSrc = $(this).attr("big");
            $("div.centerImageUrl").css("background-image",`url('${imageSrc}')`).fadeIn("fast");
        });
        
        $(`.fdiv[fid="${favId}"] div.wThumb:eq(0) img:eq(0)`).click(function(){
            $(`.search-container[fid!="${favId}"]`).hide("fast");
            $(`.search-container[fid="${favId}"]:eq(0)`).toggle("fast");
        });
        
        
        /*
        if(!worldId){
            getAPI(`favorites/${favId}`).then(async function(value){ value = value[0];
                if(!value.favoriteId){
                    console.error("value.favoriteId: ", value.favoriteId);
                    return;
                }
                
                $(`.search-container[fid="${favId}"] .wLink`).attr("href","/home/world/" + value.favoriteId);
                $(`.fDiv[fid="${favId}"] .wLink:eq(0)`).attr("href","/home/world/" + value.favoriteId);
                $(`.fDiv[fid="${favId}"]  img:eq(0)`).addClass("fOpacity");
                
                let wData = await getWorldData(value.favoriteId);
                if(!wData){
                    $(`.fdiv[fid="${favId}"] .fa-times-circle`).css("display","inline-block");
                    //$(`.fDiv[fid="${favId}"] .wThumb:eq(0)`).css("background-color","#798897");
                    $(`.fDiv[fid="${favId}"] .wThumb:eq(0)`).css("background-color","black");
                    $(`.fdiv[fid="${favId}"] div.wname:eq(0)`).html(`
                        <font color="red"><b>Not Available ( Deleted )</b></font> => 
                        <a target="_blank" href="https://www.google.com/search?q=site:www.vrcw.net ${value.favoriteId}">
                                Search on Google
                           </a>
                    `);
                } else {
                    $(`.fdiv[fid="${favId}"] .fa-exclamation`).css("display", "inline-block");
                    $(`.fDiv[fid="${favId}"] .wThumb:eq(0)`).css("background-color", "lightpink");
                    $(`.fdiv[fid="${favId}"] div.wname:eq(0)`).css("font-weight", "bold").css("color","lightpink")
                }
            })
            return;
        }
        
        getWorldData(worldId);
        async function getWorldData(worldNewId){ if(!worldNewId) return null;
        return await getAPI(`worlds/${worldNewId}`).then(value => { if( !value || !value[0] || value[1] ) return null;
            value = value[0];
            FavTempAll[favId] = value;
            //console.error(worldNewId);
            //console.error(value);
            //console.error(value.thumbnailImageUrl, value.name)
            
            let worldThumb = value.thumbnailImageUrl;
            let worldName  = value.name;
            $(`.fdiv[fid="${favId}"] img.wThumbImg`).attr("src",worldThumb);
            $(`.fdiv[fid="${favId}"] div.wname:eq(0)`).html(`${worldName}`);
            if(value.releaseStatus == "public"){
                //$(`.fdiv[fid="${favId}"] .fa-check`).css("display","inline-block");
                $(`.fdiv[fid="${favId}"] span.badge`).hide();
            }
            
            $(`.search-container[fid="${favId}"] img.wThumbImg`)
                .attr("src",worldThumb)
                .attr("big",value.imageUrl);
            $(`.search-container[fid="${favId}"] .wiUser`).attr("href","/home/user/" + value.authorId);
            $(`.search-container[fid="${favId}"] .wInfo`).each(function(){
                let dataTag  = value[ $(this).attr("dType") ] || null;
                let dataTime = 
                    /\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2]\d|3[0-1])T(?:[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d.\d[0-9]+?Z/
                        .test(dataTag) 
                            ? moment(dataTag).format("YYYY-MM-DD _ HH:mm:ss") + "<br>" + moment(dataTag).fromNow()
                            : null;
                let DataLink = ( $(this).attr("dType") == "previewYoutubeId" && dataTag && dataTag.length > 0 )
                        ? `<a target="_blank" href="https://youtu.be/${dataTag}">Youtube Link</a>`
                        : null
                        
                $(this).html(dataTime || DataLink || dataTag || "-");
            });
            
            let heatFire = (value.heat || 0);
            for(let i = 0; i < value.heat || 0; i++){
                heatFire = heatFire + ( (i == 0 && value.heat > 0) ? ":":"" );
                heatFire = heatFire + `<span aria-hidden="true" class="fa fa-fire"></span>`;
            }
            $(`.search-container[fid="${favId}"] .wInfo[dType="heat"]`).html(heatFire);
            
            return value;
        });
        }
        //return false;
        */
    });
    
    $("body").append("<h3><br>-</h3><br>");
    $("#wLoading").effect( "blind", "slow" );
    
    let DeleteDialog = `
    <div id="dialog-confirm" title="Delete this favorited ITEM?"><p>
      <span class="ui-icon ui-icon-alert" style="float:left; margin:12px 12px 20px 0;"></span>
      Are you sure delete this item?
    </p></div>`;
    
    $(".fa-trash").click(function(){
        let trash = this;
        $(DeleteDialog).dialog({ 
            resizable: false, height: "auto", width: 400, modal: true,
            //position: { my: "center", at: "center", of: trash },
            position: { my: "right top", at: "right bottom", of: trash },
            buttons: { "Delete It!": function() { 
                $( this ).dialog( "close" );
                DeleteFav($(trash).parents("div.fDiv:eq(0)").attr("fid"));
            }, Cancel: function() { $( this ).dialog( "close" ); }}
        });
    });
    
    function DeleteFav(favId){
        $(`.fdiv[fid="${favId}"]`).css("opacity","0.3");
        getAPI(`favorites/${favId}`, {method: "DELETE" } ).then(value => {
            if(!value[0]){
                console.error(value);
                alert("Error!")
                return;
            }
            $(`.fdiv[fid="${favId}"]`).hide("slow");
            //alert(value[0]["success"]["message"]);
        });
    }
}

async function getAPI(url, header){
    let err2 = null;
    header   = Object.assign(header || {}, { credentials: 'same-origin' });
    return [await fetch("/api/1/" + url, header)
        .then(async response => response.ok 
            ? response.json().then(value => { return value; })
            : ( async function(){
                err2 = response.clone(); // Avoid reader once limit : https://stackoverflow.com/a/54115314
                console.error(`Promise.reject: ${response.status} ${response.statusText}\n${await response.text()}`);
                return;
            })()
        ), err2];
}

// 取得網址標記
function getQueryString( paramName,paramURL){
	if(paramURL     == undefined) paramURL = window.location.href;
	
	paramName       = paramName.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]").toLowerCase();
	var reg         = "[\\?&]"+paramName +"=([^&#]*)";
	var regex       = new RegExp( reg );
	var regResults  = regex.exec( paramURL.toLowerCase());
	if( regResults  == null )
            return "";
	else    return regResults [1];
}

// 確認資料是不是json  https://stackoverflow.com/a/25416299
function isJSON(MyTestStr){
    try {
        var MyJSON = JSON.stringify(MyTestStr);
        var json = JSON.parse(MyJSON);
        if(typeof(MyTestStr) == 'string')
            if(MyTestStr.length == 0)
                return false;
    }
    catch(e){ return false; }
    return true;
}

function RunOnce(element, className, callback){
    if(!$(element).length || $(element).hasClass(className) || $(element).hasClass("lone_ignore")) return;
        $(element).addClass(className);
    callback.apply(this, [element, className]);
}

// https://www.w3schools.com/js/js_random.asp
function getRndInteger(min, max) {
    return Math.floor(Math.random() * (max - min + 1) ) + min;
}

function regexUnityID(typeID,text){
    if(typeID == null || typeID == "" ) return null;
    var regexUID = new RegExp("(" + typeID + "_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})");
    var result   = regexUID.exec(text,"gm");
    if(result) return result[1];
    return null;
}

// https://stackoverflow.com/a/18786024
function GetWidthPercentage(e){ return $(e).width() / $(e).parent().width() * 100; }


/*
LoadFormatText("textfor%1 to %2 + ?=%3",{
	"%1":"ccc",
	"%2":"ccc2",
	"%3":"ccc3",
	"file":"local/readme.js"
},function(data){
	//console.error(JSON.stringify(data))
})

var sun = LoadFormatText("%posg + %1 + 5421",{
	"%1":"ccc",
	"%2":"ccc2",
	"%3":"ccc3",
	"%posg":"fuckyou"
})
console.error(JSON.stringify(sun.out))
*/
function LoadFormatText(Source,data,callback){
	if(data.file && !data.queried){
		LoadLocalTextFile2(data.file,function(fText){ 		//取得本地檔案內容
			data.queried = true								//設定已取得檔案內容
			LoadFormatText(fText,data,function(fTextNew){ 	//執行一次字串格式化
				callback.apply(this, [fTextNew]);			//異步回傳資料
			})
		})
	}
	else {
		data.src	= Source								//備份原始字串
		var object 	= Object.keys(data)						//取得每個元素標題
		for(var i = 0;i < object.length;i++){
			var RepTXT = data[object[i]]
			if(!data.ignoreSymbol && !object[i].match("%")) continue;   //跳過非替代內容
			else if(RepTXT == undefined) RepTXT = "undefined"
			else if(typeof(RepTXT) == "number" && isNaN(RepTXT)) RepTXT = "NaN"
            RepTXT = ("" + RepTXT).replace(new RegExp("%RepSrc%","gm"),object[i]);
			Source = Source.replace(new RegExp(object[i],"gm"),RepTXT);
			//console.error(object[i] + " : " + data[object[i]]);
		}
		//console.error(Source);
		data.out = Source;
		if(callback) callback.apply(this, [data]);
		return data;
	}
}

// https://stackoverflow.com/a/6116502
function GetTrusted(levels){
    var ret = [null,null];
    $.each(TrustedData, function(key, value) {
        if ( $.inArray(key, levels) > -1 ){
            ret = value;
            return false;
        }
    })
    return ret;
}

function GetRoomType(location){
    var ret = RoomType["~pub"];
    $.each(RoomType, function(key, value) {
        if(location.match(key)){
            ret = value;
            return false;
        }
    })
    return ret;
}

// https://stackoverflow.com/a/15734408
$.widget("ui.tooltip", $.ui.tooltip, {
    options: {
        content: function () {
            return $(this).prop('title');
        }
    }
});

// https://stackoverflow.com/a/29622653
function sortObject(o) {
    return Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {});
}

function GetTMRes(name, replace){
    return GM_getResourceURL(name)
            .replace("data:application;",replace || "data:application;");
}

function ColorChroma(unity_id){
    var code = /_([0-9a-f]){8}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f])([0-9a-f]){11}/g.exec(unity_id)
        code = code || ["","efe2dc"]
        code.shift();
        code = "#" + code.toString().replace(/,/gm,"");
    return chroma(code).brighten(1);
}

function ajaxFail( xhr, status, error ){
    console.error([xhr, status, error]);
    console.error(JSON.stringify(xhr.responseJSON, null, 4));
    alert(JSON.stringify(xhr.responseJSON.error, null, 4));
}

// https://blog.camel2243.com/2016/06/18/javascript-sleep%E5%87%BD%E6%95%B8%E5%AF%A6%E4%BD%9C/
async function sleep(ms = 0) { return new Promise(r => setTimeout(r, ms)); }

// https://codereview.stackexchange.com/a/37533
function getByteCount( s ){
    var count = 0, stringLength = s.length, i;
    s = String( s || "" );
    for( i = 0 ; i < stringLength ; i++ ){
        var partCount = encodeURI( s[i] ).split("%").length;
        count += partCount==1?1:partCount-1;
    }
    return count;
}

async function PutUserJoin(dataIn){
    if(dataIn.room == "offline"){
        let worldId     = "wrld_0b4e5774-473d-4943-bb22-aac0c1b706f3"
        let roomNumber  = getRndInteger(1234, 9999)
        dataIn.room     = `${worldId}:${roomNumber}~private(${dataIn.id})~nonce(${_uuid()})~canRequestInvite`
    }
    // https://stackoverflow.com/questions/8032938/jquery-ajax-put-with-parameters
    // https://petetasker.com/using-async-await-jquerys-ajax/
    let data = { "userId":dataIn.id, "worldId":dataIn.room }
    await $.ajax({
        type: 'PUT',
        url: 'https://api.vrchat.cloud/api/1/joins',
        contentType: 'application/json',
        data: JSON.stringify(data) // access in body
    }).done(function (data) {
        //console.error('put user to World SUCCESS');
        return data
    }).fail(function (msg) {
        console.error('put user to World  FAIL', msg);
        return false
    })/*
    .always(function (msg) {
        console.error('put user to World  ALWAYS', msg);
    });*/
}

// https://cythilya.github.io/2017/03/12/uuid/
function _uuid() {
  var d = Date.now();
  if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
    d += performance.now(); //use high-precision timer if available
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = (d + Math.random() * 16) % 16 | 0;
    d = Math.floor(d / 16);
      return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}

function getUserRemarks(userID, newLine){
    let     UserRemarks = GM_getValue(userID + "_remarks", null)
    return  UserRemarks = (UserRemarks && newLine) ? UserRemarks.replace(new RegExp("\n","gm"),"<br>"):UserRemarks
}

// https://stackoverflow.com/questions/7505623/colors-in-javascript-console/42551926#42551926
// https://stackoverflow.com/questions/676721/calling-dynamic-function-with-dynamic-parameters-in-javascript
function LogInfo( ...infoText ){ console.info("%c VRChatLittleONE", "color:DodgerBlue", ...infoText); }

function InsertCustomCSS(){
    $("head").append(`<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pushy/1.3.0/css/pushy.min.css" type="text/css">`);
    
    if(!$(`link[rel="icon"]`).length)
        $("head").append(`<link rel="icon" href="https://assets.vrchat.com/www/images/favicon.png">`);
    
    
    
    // https://www.cnblogs.com/pigtail/archive/2013/03/11/2953848.html
    document.head.insertAdjacentHTML('beforeend', `<style>
    /* https://stackoverflow.com/a/4407335 */
    .noselect {
      -webkit-touch-callout: none; /* iOS Safari */
        -webkit-user-select: none; /* Safari */
         -khtml-user-select: none; /* Konqueror HTML */
           -moz-user-select: none; /* Firefox */
            -ms-user-select: none; /* Internet Explorer/Edge */
                user-select: none; /* Non-prefixed version, currently
                                      supported by Chrome and Opera */
    }
    
    .pointer { cursor: pointer ; }
    .n-size  { cursor: n-resize; }
    .w-size  { cursor: w-resize; }
    
    div.WorldsWithFriends .friend-row li {
        list-style-type: none !important;
    }
    
    div.WorldsWithFriends .friend-row a {
        display: unset;
    }
    
    div.WorldsWithFriends h3 {
        text-transform: unset;
    }
    
    h2, h3 {
        text-transform: unset !important;
    }
    
    div.WorldsWithFriends div.template_world div.col-md-4 {
        flex: unset !important;
        max-width: unset !important;
    }
    
    div.WorldsWithFriends div.template_world div.friend-caption.text-success {
        /* margin-left: 125px !important; */
        min-width: max-content;
        
        padding:.1rem !important;
        
        font-size: 20px !important;
    }
    
    .Lone_wrld_badge {
        float       :right;
        color       :lightgreen;
        font-weight :bold;
    }
    
    a.Lone_room_people_counter {
        /* vertical-align:middle; */
        position:absolute;
        right   :170px;
        top     :126px;
    }
    
    a.Lone_room_private_counter {
        right   :268px;
        top     :198px;
    }
    
    a.Lone_room_createdBy {
        vertical-align:top;
        font-size: 15px;
        top: 44px;
        position: absolute;
        right: 208px;
    }
    
    a.Lone_room_createdBy_btn {
        vertical-align:top;
        font-size:10px;
        background:#1a2026 linear-gradient(180deg, #1a2026, #1a2026) repeat-x;
    }
    
    div.Lone_private_room a.Lone_room_createdBy, 
    div.Lone_private_room a.Lone_room_createdBy_btn,
    a.Lone_room_createdBy[CreatedBy='NoOwner'], 
    a.Lone_room_createdBy_btn[CreatedBy='NoOwner']
    {
        display:none;
    }
    
    /* https://www.w3schools.com/howto/howto_css_image_effects.asp */
    img.blur {
        -webkit-filter: blur(5px); /* Safari 6.0 - 9.0 */
        filter: blur(5px);
    }
    
    img.Lone_wrld_img {
        width:150px;
        height:115px;
        opacity:0.3;
    }
    
    .Lone_world_room_people:hover {
        color:white !important;
        -webkit-filter: blur(1px); /* Safari 6.0 - 9.0 */
        filter: blur(1px);
    }
    
    /* https://stackoverflow.com/questions/12956586/width-of-jquery-ui-tooltip-widget */
    .ui-tooltip {
        max-width: 500px !important;
    }
    
    /* 左邊欄縮略 */
    /* https://stackoverflow.com/questions/7839164/is-there-a-css-cross-browser-value-for-width-moz-fit-content */
    div.container-fluid div.bg-gradient-secondary.leftbar:first-child {
        width: fit-content;      /* chrome  */
        width: -moz-fit-content; /* firefox */
    }
    
    /* 去除瀏覽器垂直捲動條 */
    /* https://www.reddit.com/r/FirefoxCSS/comments/8ka8jd/hide_scrollbar_firefox_60/ */
    browser {
        margin-bottom: -17px !important;
        overflow-y: scroll;
        overflow-x: hidden;
    }
    
    </style>`);
}
//=================
// %appdata%/../locallow/VRChat

// https://stackoverflow.com/questions/3680429/click-through-a-div-to-underlying-elements

/*
            // https://stackoverflow.com/a/8054797
            // https://stackoverflow.com/a/1553727
            // http://api.jquery.com/category/events/event-object/
            $('*').click(function(event) {
                if (this === event.currentTarget) { // only fire this handler on the original element
                    event.stopImmediatePropagation();
                    console.error($(event.target).attr("href"));
                }
            });
            */
            
// https://gist.github.com/roselan/3176700
(function( $ ){
  $.fn.observe = function( callback, options ) {  

    var settings = $.extend( {
            attributes   : true, 
            childList    : true, 
            characterData: true
        }, 
        options );

    return this.each(function() {        
        var self = this,
            observer,
            MutationObserver = window.MutationObserver       || 
                               window.WebKitMutationObserver || 
                               window.MozMutationObserver; 
        
        if (MutationObserver && callback) {
            observer = new MutationObserver(function(mutations) { 
                callback.call(self, mutations);
            });              
            observer.observe(this, settings);
        }         
    });
  };
})( jQuery );

// https://stackoverflow.com/questions/33289726/combination-of-async-function-await-settimeout
const sleepNew = m => new Promise(r => setTimeout(r, m))

// https://stackoverflow.com/questions/23984629/how-to-set-min-font-size-in-css
// https://www.bestcssbuttongenerator.com/
// ES
// https://juejin.im/post/5b2a186cf265da596d04a648
// https://medium.com/@peterchang_82818/es6-10-features-javascript-developer-must-know-98b9782bef44
// https://zi.media/@yidianzixun/post/pW8JXi
// https://blog.camel2243.com/2016/06/18/javascript-sleep%E5%87%BD%E6%95%B8%E5%AF%A6%E4%BD%9C/
// http://es6.ruanyifeng.com/#docs/async

// Day 05: ES6篇 - let與const
// https://ithelp.ithome.com.tw/articles/10185142
// http://es6.ruanyifeng.com/#docs/let

// ECMA 6
// http://es6.ruanyifeng.com/#docs/string#%E5%AE%9E%E4%BE%8B%EF%BC%9A%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91

// 鐵人賽:ES6 原生 Fetch 遠端資料方法
// https://wcc723.github.io/javascript/2017/12/28/javascript-fetch/
function compile(template){
  const evalExpr = /<%=(.+?)%>/g;
  const expr = /<%([\s\S]+?)%>/g;

  template = template
    .replace(evalExpr, '`); \n  echo( $1 ); \n  echo(`')
    .replace(expr, '`); \n $1 \n  echo(`');

  template = 'echo(`' + template + '`);';

  let script =
  `(function parse(data){
    let output = "";

    function echo(html){
      output += html;
    }

    ${ template }

    return output;
  })`;

  return script;
}