🏠 Home 

GC Stats Banner Library

This library provides the core functionality to add a stats badge onto profile and cache pages on geocaching.com.

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/389508/880219/GC%20Stats%20Banner%20Library.js

// ==UserScript==
// @exclude     *
// @supportURL	https://github.com/Cryo99/GCStatsBannerLib
// @version     1.0.2
// @include     /^https?://www\.geocaching\.com/(account/dashboard|my|default|geocache|profile|seek/cache_details|p)/
// @exclude     /^https?://www\.geocaching\.com/(login|about|articles|myfriends)/
// @require     https://openuserjs.org/src/libs/sizzle/GM_config.js
// ==UserLibrary==
// @name        GC Stats Banner Library
// @description This library provides the core functionality for adding a stats banner onto profile and cache pages on geocaching.com.
// @copyright   2019-2020, Cryo99 (https://github.com/Cryo99)
// @license     GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// ==/UserScript==
// ==/UserLibrary==
/*jshint esversion: 6 */
var GCStatsBanner = function(cfg){
// ========================= Private members =========================
var _cfg = {},
_cfgDefault = {
cacheTitles: false,
callerVersion: 'Unknown',
elPrefix: '',
imgScript: 'find-badge.php',
logLevel: 'normal',
seriesLevels: ['None'],
seriesLevelDefault: 'None',
seriesName: false,
seri###RL: false
};
// Internal vars.
_cacheName = document.getElementById("ctl00_ContentBody_CacheName"),
// Images can be wider when level names are long. overflow: hidden; on <series>-container prevents images from overlaying the div border.
_css = '',
_profileNameOld = document.getElementById("ctl00_ContentBody_ProfilePanel1_lblMemberName"),
_profileName = document.getElementById("ctl00_ProfileHead_ProfileHeader_lblMemberName"),
_userField = document.getElementsByClassName("user-name");
// ========================= Private methods =========================
// Constructor.
function _const(){
for(cfgItem in _cfgDefault){
if(typeof cfg[cfgItem] === 'undefined' || cfg[cfgItem] === null){
// Required variable is undefined or null
if(_cfgDefault[cfgItem] === false){
throw new Error('GCStatsBannerLib: ' + cfgItem + ' is a required configuration element.');
}
// Use the default value.
_cfg[cfgItem] = _cfgDefault[cfgItem];
// console.warn('GCStatsBannerLib: ' + cfgItem + ' undefined. Using value: ' + _cfgDefault[cfgItem] + '.')
}else{
// Use the configured value.
_cfg[cfgItem] = cfg[cfgItem];
}
}
// If elPrefix is still empty, generate it.
if(!_cfg.elPrefix){
_cfg.elPrefix = _getPrefix(cfg.seriesName);
}
_log(cfg, 'Passed config');
_log(_cfg, 'Generated config');
_generateCSS();
}
// Run the constructor on creation.
_const();
function _getPrefix(name){
var matches = name.match(/\b(\w)/g);
return matches.join('').toLowerCase().padStart(3, '_');
}
function _generateCSS(){
_css = 'div.' + _cfg.elPrefix + '-container { border: 1px solid #b0b0b0; margin-top: 1.5em; padding: 0; text-align: center; overflow: hidden;} ' +
'.WidgetBody div.' + _cfg.elPrefix + '-container { border: none; } ' +
'#ctl00_ContentBody_ProfilePanel1_pnlProfile div.' + _cfg.elPrefix + '-container { border: none; text-align: inherit;} ' +
'a.' + _cfg.elPrefix + '-badge { background-color: white;} ' +
'#ctl00_ContentBody_ProfilePanel1_pnlProfile div.' + _cfg.elPrefix + '-container {float: left}' +
'#StatsComponents {background: white;}';
}
function _log(msg, desc){
if(_cfg.logLevel === 'debug'){
var msgStart = '%c' + _cfg.seriesName + ' Stats Debug (%s):';
console.log(msgStart,  "color: yellow; font-style: italic; background-color: red;padding: 2px", desc, msg);
}
}
function _getHiderName(){
var i,
links = document.getElementsByTagName("a"),
pos;
if(links){
for(i = 0; i < links.length; i++){
pos = links[i].href.indexOf("/seek/nearest.aspx?u=");
if(pos !== -1){
return decodeURIComponent(links[i].href.substr(pos + 21).replace(/\+/g, '%20'));
}
}
}
};
function _parseNames(names){
// Filter out null or undefined entries, convert commas to semicolons, then convert to a comma-separated string.
return encodeURIComponent(names
.filter(function (n){
return n !== undefined;
})
.map(function (n){
return (n + "").replace(/,/g, ";");
})
.join());
};
function _getHtml(uname, brand){
return "<a class='" + _cfg.elPrefix + "-badge' href='https://www." + _cfg.seri###RL + "' title='" + _cfg.seriesName +
" stats.'><img src='https://img." + _cfg.seri###RL + "/awards/" + _cfg.imgScript + "?name=" + uname + "&brand=" + brand + "' /></a>";
};
function _displayStats(stats, page, brand){
var widget = document.createElement("div"),
html = "",
i,
target;
for(i = 0; i < stats.length; i++){
var name = (stats[i].name + "")
.replace(/;/g, ",")
.replace(/'/g, "&apos;")
.replace(/"/g, "&quot;");
if(i === 0 || stats[i].name !== stats[0].name){
html += _getHtml(name, brand);
}
}
_log(html, 'Banner HTML');
switch(page){
case "my":
target = document.getElementById("ctl00_ContentBody_lnkProfile");
break;
case "account":
// New account dashboard.
// The WidgetPanel is too slow to load and causes scripts to block so follow GClhII and just append the widget to the sidebar.
target = document.querySelector(".sidebar-right");
break;
case "cache":
target = document.getElementsByClassName('sidebar')[0];
break;
case "profile":
if(_profileName){
target = document.getElementById("ctl00_ContentBody_ProfilePanel1_lblProfile");
if (target) {
target = target.parentNode;
}
}else if(_profileNameOld){
target = document.getElementById("HiddenProfileContent");
}
break;
}
if(!target){
console.warn(_cfg.seriesName + " Stats: Aborted - couldn't find where to insert widget. You might not be logged in.");
return;
}
if(html){
widget.className = _cfg.elPrefix + "-container";
widget.innerHTML = html;
switch(page){
case "my":
case "profile":
target.parentNode.insertBefore(widget, target.nextSibling);
break;
case "account":
// If the StatsWidget isn't present, create it.
var el = document.getElementById("StatsWidget");
if(!el){
_log('Creating widget.', 'StatsWidget');
var divStats = document.createElement('div');
divStats.id = "StatsWidget";
divStats.classList.add("panel", "collapsible");
divStats.innerHTML = '<div class="panel-header isActive" aria-expanded="true">\
<h1 id="stats-widget-label" class="h5 no-margin">Statistics</h1>\
<button aria-controls="StatsWidget" aria-labelledby="stats-widget-label">\
<svg height="22" width="22" class="opener" role="img">\
<use xlink:href="/account/app/ui-icons/sprites/global.svg#icon-expand-svg-fill"></use>\
</svg>\
</button>\
</div>\
<div id="StatsComponents" class="panel-body">\
<div id="StatsPanel" class="widget-panel"></div>\
</div>';
target.append(divStats);
// Hide the panel if it was previously hidden.
if (!GM_getValue('statsWidget_visible', false)) {
document.querySelector('#StatsWidget .panel-body').style.display = "none";
document.querySelector('#StatsWidget .panel-header').classList.remove('isActive');
_fadeOut(document.querySelector('#StatsWidget .panel-body'));
}
// Add the click handler.
document.querySelector('#StatsWidget .panel-header').addEventListener('click', function() {
if (GM_getValue('statsWidget_visible', true)) {
document.querySelector('#StatsWidget .panel-header').classList.remove('isActive');
_fadeOut(document.querySelector('#StatsWidget .panel-body'));
GM_setValue('statsWidget_visible', false);
}else{
document.querySelector('#StatsWidget .panel-header').classList.add('isActive');
_fadeIn(document.querySelector('#StatsWidget .panel-body'));
GM_setValue('statsWidget_visible', true);
}
});
}
// Finally, append the banner.
document.querySelector('#StatsPanel').appendChild(widget);
break;
default:
target.insertBefore(widget, target.firstChild.nextSibling.nextSibling);
break;
}
}else{
console.warn(_cfg.seriesName + " Stats: didn't generate an award badge.");
}
};
function _fadeOut(element) {
var op = 1;  // initial opacity
var timer = setInterval(function () {
if (op <= 0.1){
clearInterval(timer);
element.style.display = "none";
}
element.style.opacity = op;
op -= 0.1;
}, 30);
};
function _fadeIn(element) {
var op = 0.1;  // initial opacity
element.style.display = "block";
var timer = setInterval(function () {
if (op >= 1){
clearInterval(timer);
}
element.style.opacity = op;
op += 0.1;
}, 30);
};
function _createConfigDlg(){
// Register the menu item.
GM_registerMenuCommand("Options", function(){
GM_config.open();
});
GM_config.init({
'id': _cfg.elPrefix + '_config', 				// The id used for this instance of GM_config
'title': _cfg.seriesName + ' Stats',			// Panel Title
'fields': { 									// Fields object
'branding': {								// This is the id of the field
'label': 'Branding', 					// Appears next to field
'type': 'select', 						// Makes this setting a dropdown
'options': _cfg.seriesLevels,			// Possible choices
'default': _cfg.seriesLevelDefault		// Default value if user doesn't change it
}
},
// Dialogue internal styles.
'css': '#' + _cfg.elPrefix + '_config {position: static !important; width: 75% !important; margin: 1.5em auto !important; border: 10 !important;}' +
'#' + _cfg.elPrefix + '_config_' + _cfg.elPrefix + '_branding_var {padding-top: 30px;} #' + _cfg.elPrefix + '_config button {color: black;}',
'events': {
'open': function(document, window, frame){
// iframe styles.
frame.style.width = '300px';
frame.style.height = '250px';
frame.style.left = parent.document.body.clientWidth / 2 - 150 + 'px';
frame.style.borderWidth = '5px';
frame.style.borderStyle = 'ridge';
frame.style.borderColor = '#999999';
},
'save': function(){
GM_setValue(_cfg.elPrefix + '_branding', GM_config.get('branding'));
location.reload();                      // reload the page when configuration was changed
}
}
});
};
function _init(){
var currentPage,
elCSS = document.createElement("style"),
userName = "",
userNames = [],
stats = [];
// Don't run on frames or iframes
if(window.top !== window.self){
return false;
}
if(/\/my\//.test(location.pathname)){
// On a My Profile page
currentPage = "my";
}else if(/\/account\//.test(location.pathname)){
// On a Profile page
currentPage = "account";
}else{
if(_cacheName){
// On a Geocache page...
// var matcher = new RegExp(_cfg.seriesName, "i");
// if(!matcher.test(_cacheName.innerHTML)){
// ...but not the right cache series.
// 	return;
// }
var titleFound = false;
for(title in _cfg.cacheTitles){
var matcher = new RegExp(_cfg.cacheTitles[title], "i");
if(matcher.test(_cacheName.innerHTML)){
titleFound = true;
}
}
if(!titleFound){
// ...but not the right cache series.
return;
}
currentPage = "cache";
}else{
currentPage = "profile";
}
}
_log(currentPage, 'Detected page');
// We're going to display so we can announce ourselves and prepare the configuration dialogue.
console.info(_cfg.seriesName + " Stats V" + _cfg.callerVersion);
//CONFIG
_createConfigDlg();
var brand = GM_getValue(_cfg.elPrefix + '_branding', _cfg.seriesLevelDefault);
_log(brand, 'Stats branding');
brand = brand.toLowerCase()
// Get hider details.
var hider;
switch(currentPage){
case "profile":
if(_profileName){
userNames = [_profileName.textContent.trim()];
}else if(_profileNameOld){
userNames = [_profileNameOld.textContent.trim()];
}
break;
default:
if(_userField.length > 0){
userNames.push(_userField[0].innerHTML.trim());
}
hider = _getHiderName();
if(typeof hider !== 'undefined'){
userNames.push(hider);
}
break;
}
_log(userNames[0], "Finder's name");
_log(userNames[1], "Hider's name");
for(var i = 0; i < userNames.length; i++){
stats[i] = {name: userNames[i]};
}
_log(stats, 'Statistics');
userName = _parseNames(userNames);
if(!userName){
console.error(_cfg.seriesName + " Stats: Aborted - couldn't work out user name");
return;
}
// Inject widget styling
elCSS.setAttribute('type', 'text/css');
if(elCSS.styleSheet){
elCSS.styleSheet.cssText = _css;
}else{
elCSS.appendChild(document.createTextNode(_css));
}
document.head.appendChild(elCSS);
_displayStats(stats, currentPage, brand);
}
return {
// ========================= Public members =========================
// ========================= Public methods =========================
init: _init
};
};