// ==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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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> "
+ "<font color='green'>" + value.created.split("T")[1].split(/:\d+\..+Z/)[0] + "</font> "
+ "<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>
<a target="_blank" href="%world-by-author%">by `
$("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 : ( <a id="FetchTimes">0</a> )
<br>Request Length: ( <a id="FetchLength"></a> )
`);
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;">
<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>
</span>
<a class="float-right Lone_wrld_author_link_vrclist" style="color:white;" target="_blank"> ►</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 + " ";
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 + " 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> "
+ "<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(" <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>
<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" + `'> `
+ (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>
<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"> '
+ '<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> <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> <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> "
+ "<a target='_blank' style='color:white;' href='https://greasyfork.org/zh-TW/scripts/376747-vrchat-littleone'>"
+ "<font color='#67d781'>VRChat LittleONE</font> 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(' <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> 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%"
+ " %prevText%%InstancesID%'>Public" + "%nextText%"
+ " %prevText%%InstancesID%" + "~hidden(%usr_empty%)'>Friends+" + "%nextText%"
+ " %prevText%%InstancesID%" + "~friends(%usr_empty%)'>Friends" + "%nextText%"
+ " %prevText%%InstancesID%" + "~private~canRequestInvite(%usr_empty%)'>Invite+" + "%nextText%"
+ " %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
+ " " + prevText + "~hidden'>Friends+" + nextText
+ " " + prevText + "~friends'>Friends" + nextText
+ " " + prevText + "~private~canRequestInvite()'>Invite+" + nextText
+ " " + 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____: <%= json[i].id %></li>
-->
<li>___userID____: <%= json[i].id %></li>
<li>____friendKey: <%= json[i].friendKey %></li>
<li>___userName: <%= json[i].username %></li>
<li>___pastName: <%= 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> <small>—
<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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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> content_featured</a>
</span> </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> <small>—
<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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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> content_featured</a>
</span> </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;
}