goatcounter.html (8727B)
1 <script> 2 window.goatcounter = {endpoint: 'https://stats.ybbond.id/count'} 3 4 // GoatCounter: https://www.goatcounter.com 5 // This file (and *only* this file) is released under the ISC license: 6 // https://opensource.org/licenses/ISC 7 ;(function() { 8 'use strict'; 9 10 if (window.goatcounter && window.goatcounter.vars) // Compatibility with very old version; do not use. 11 window.goatcounter = window.goatcounter.vars 12 else 13 window.goatcounter = window.goatcounter || {} 14 15 // Load settings from data-goatcounter-settings. 16 var s = document.querySelector('script[data-goatcounter]') 17 if (s && s.dataset.goatcounterSettings) { 18 try { var set = JSON.parse(s.dataset.goatcounterSettings) } 19 catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) } 20 for (var k in set) 21 if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1) 22 window.goatcounter[k] = set[k] 23 } 24 25 var enc = encodeURIComponent 26 27 // Get all data we're going to send off to the counter endpoint. 28 var get_data = function(vars) { 29 var data = { 30 p: (vars.path === undefined ? goatcounter.path : vars.path), 31 r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer), 32 t: (vars.title === undefined ? goatcounter.title : vars.title), 33 e: !!(vars.event || goatcounter.event), 34 s: [window.screen.width, window.screen.height, (window.devicePixelRatio || 1)], 35 b: is_bot(), 36 q: location.search, 37 } 38 39 var rcb, pcb, tcb // Save callbacks to apply later. 40 if (typeof(data.r) === 'function') rcb = data.r 41 if (typeof(data.t) === 'function') tcb = data.t 42 if (typeof(data.p) === 'function') pcb = data.p 43 44 if (is_empty(data.r)) data.r = document.referrer 45 if (is_empty(data.t)) data.t = document.title 46 if (is_empty(data.p)) data.p = get_path() 47 48 if (rcb) data.r = rcb(data.r) 49 if (tcb) data.t = tcb(data.t) 50 if (pcb) data.p = pcb(data.p) 51 return data 52 } 53 54 // Check if a value is "empty" for the purpose of get_data(). 55 var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' } 56 57 // See if this looks like a bot; there is some additional filtering on the 58 // backend, but these properties can't be fetched from there. 59 var is_bot = function() { 60 // Headless browsers are probably a bot. 61 var w = window, d = document 62 if (w.callPhantom || w._phantom || w.phantom) 63 return 150 64 if (w.__nightmare) 65 return 151 66 if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate) 67 return 152 68 if (navigator.webdriver) 69 return 153 70 return 0 71 } 72 73 // Object to urlencoded string, starting with a ?. 74 var urlencode = function(obj) { 75 var p = [] 76 for (var k in obj) 77 if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false) 78 p.push(enc(k) + '=' + enc(obj[k])) 79 return '?' + p.join('&') 80 } 81 82 // Show a warning in the console. 83 var warn = function(msg) { 84 if (console && 'warn' in console) 85 console.warn('goatcounter: ' + msg) 86 } 87 88 // Get the endpoint to send requests to. 89 var get_endpoint = function() { 90 var s = document.querySelector('script[data-goatcounter]') 91 if (s && s.dataset.goatcounter) 92 return s.dataset.goatcounter 93 return (goatcounter.endpoint || window.counter) // counter is for compat; don't use. 94 } 95 96 // Get current path. 97 var get_path = function() { 98 var loc = location, 99 c = document.querySelector('link[rel="canonical"][href]') 100 if (c) { // May be relative or point to different domain. 101 var a = document.createElement('a') 102 a.href = c.href 103 if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, '')) 104 loc = a 105 } 106 return (loc.pathname + loc.search) || '/' 107 } 108 109 // Run function after DOM is loaded. 110 var on_load = function(f) { 111 if (document.body === null) 112 document.addEventListener('DOMContentLoaded', function() { f() }, false) 113 else 114 f() 115 } 116 117 // Filter some requests that we (probably) don't want to count. 118 goatcounter.filter = function() { 119 if ('visibilityState' in document && (document.visibilityState === 'prerender' || document.visibilityState === 'hidden')) 120 return 'visibilityState' 121 if (!goatcounter.allow_frame && location !== parent.location) 122 return 'frame' 123 if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/)) 124 return 'localhost' 125 if (!goatcounter.allow_local && location.protocol === 'file:') 126 return 'localfile' 127 if (localStorage && localStorage.getItem('skipgc') === 't') 128 return 'disabled with #toggle-goatcounter' 129 return false 130 } 131 132 // Get URL to send to GoatCounter. 133 window.goatcounter.url = function(vars) { 134 var data = get_data(vars || {}) 135 if (data.p === null) // null from user callback. 136 return 137 data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control. 138 139 var endpoint = get_endpoint() 140 if (!endpoint) 141 return warn('no endpoint found') 142 143 return endpoint + urlencode(data) 144 } 145 146 // Count a hit. 147 window.goatcounter.count = function(vars) { 148 var f = goatcounter.filter() 149 if (f) 150 return warn('not counting because of: ' + f) 151 152 var url = goatcounter.url(vars) 153 if (!url) 154 return warn('not counting because path callback returned null') 155 156 var img = document.createElement('img') 157 img.src = url 158 img.style.position = 'absolute' // Affect layout less. 159 img.setAttribute('alt', '') 160 img.setAttribute('aria-hidden', 'true') 161 162 var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) } 163 setTimeout(rm, 10000) // In case the onload isn't triggered. 164 img.addEventListener('load', rm, false) 165 document.body.appendChild(img) 166 } 167 168 // Get a query parameter. 169 window.goatcounter.get_query = function(name) { 170 var s = location.search.substr(1).split('&') 171 for (var i = 0; i < s.length; i++) 172 if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0) 173 return s[i].substr(name.length + 1) 174 } 175 176 // Track click events. 177 window.goatcounter.bind_events = function() { 178 if (!document.querySelectorAll) // Just in case someone uses an ancient browser. 179 return 180 181 var send = function(elem) { 182 return function() { 183 goatcounter.count({ 184 event: true, 185 path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''), 186 title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''), 187 referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''), 188 }) 189 } 190 } 191 192 Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) { 193 if (elem.dataset.goatcounterBound) 194 return 195 var f = send(elem) 196 elem.addEventListener('click', f, false) 197 elem.addEventListener('auxclick', f, false) // Middle click. 198 elem.dataset.goatcounterBound = 'true' 199 }) 200 } 201 202 // Add a "visitor counter" frame or image. 203 window.goatcounter.visit_count = function(opt) { 204 on_load(function() { 205 opt = opt || {} 206 opt.type = opt.type || 'html' 207 opt.append = opt.append || 'body' 208 opt.path = opt.path || get_path() 209 opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')} 210 211 opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?' 212 if (opt.no_branding) opt.attr['src'] += '&no_branding=1' 213 if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style) 214 if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start) 215 if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end) 216 217 var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type] 218 if (!tag) 219 return warn('visit_count: unknown type: ' + opt.type) 220 221 if (opt.type === 'html') { 222 opt.attr['frameborder'] = '0' 223 opt.attr['scrolling'] = 'no' 224 } 225 226 var d = document.createElement(tag) 227 for (var k in opt.attr) 228 d.setAttribute(k, opt.attr[k]) 229 230 var p = document.querySelector(opt.append) 231 if (!p) 232 return warn('visit_count: append not found: ' + opt.append) 233 p.appendChild(d) 234 }) 235 } 236 237 // Make it easy to skip your own views. 238 if (location.hash === '#toggle-goatcounter') { 239 if (localStorage.getItem('skipgc') === 't') { 240 localStorage.removeItem('skipgc', 't') 241 alert('GoatCounter tracking is now ENABLED in this browser.') 242 } 243 else { 244 localStorage.setItem('skipgc', 't') 245 alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.') 246 } 247 } 248 249 if (!goatcounter.no_onload) 250 on_load(function() { 251 goatcounter.count() 252 if (!goatcounter.no_events) 253 goatcounter.bind_events() 254 }) 255 })(); 256 </script> 257 <noscript><img src="https://stats.ybbond.id/count?p=/test-noscript"></noscript>