1e56d7b71SSascha Leib"use strict"; 2e56d7b71SSascha Leib/* DokuWiki BotMon Plugin Script file */ 3e56d7b71SSascha Leib/* 14.10.2025 - 0.5.0 - pre-release */ 4e56d7b71SSascha Leib/* Author: Sascha Leib <ad@hominem.info> */ 5e56d7b71SSascha Leib 6e56d7b71SSascha Leib// enumeration of user types: 7e56d7b71SSascha Leibconst BM_USERTYPE = Object.freeze({ 8e56d7b71SSascha Leib 'UNKNOWN': 'unknown', 9e56d7b71SSascha Leib 'KNOWN_USER': 'user', 10e56d7b71SSascha Leib 'PROBABLY_HUMAN': 'human', 11e56d7b71SSascha Leib 'LIKELY_BOT': 'likely_bot', 12e56d7b71SSascha Leib 'KNOWN_BOT': 'known_bot' 13e56d7b71SSascha Leib}); 14e56d7b71SSascha Leib 15e56d7b71SSascha Leib// enumeration of log types: 16e56d7b71SSascha Leibconst BM_LOGTYPE = Object.freeze({ 17e56d7b71SSascha Leib 'SERVER': 'srv', 18e56d7b71SSascha Leib 'CLIENT': 'log', 19e56d7b71SSascha Leib 'TICKER': 'tck' 20e56d7b71SSascha Leib}); 21e56d7b71SSascha Leib 22e56d7b71SSascha Leib// enumeration of IP versions: 23e56d7b71SSascha Leibconst BM_IPVERSION = Object.freeze({ 24e56d7b71SSascha Leib 'IPv4': 4, 25e56d7b71SSascha Leib 'IPv6': 6 26e56d7b71SSascha Leib}); 27e56d7b71SSascha Leib 28e56d7b71SSascha Leib/* BotMon root object */ 29e56d7b71SSascha Leibconst BotMon = { 30e56d7b71SSascha Leib 31e56d7b71SSascha Leib init: function() { 32e56d7b71SSascha Leib //console.info('BotMon.init()'); 33e56d7b71SSascha Leib 34e56d7b71SSascha Leib // find the plugin basedir: 350edf1a56SSascha Leib this._baseDir = document.currentScript.src.substring(0, document.currentScript.src.lastIndexOf('/')+1); 36e56d7b71SSascha Leib 37e56d7b71SSascha Leib // read the page language from the DOM: 38e56d7b71SSascha Leib this._lang = document.getRootNode().documentElement.lang || this._lang; 39e56d7b71SSascha Leib 40e56d7b71SSascha Leib // get the time offset: 41e56d7b71SSascha Leib this._timeDiff = BotMon.t._getTimeOffset(); 42e56d7b71SSascha Leib 43e56d7b71SSascha Leib // get yesterday's date: 44e56d7b71SSascha Leib let d = new Date(); 45e56d7b71SSascha Leib d.setDate(d.getDate() - 1); 46e56d7b71SSascha Leib this._datestr = d.toISOString().slice(0, 10); 47e56d7b71SSascha Leib 48e56d7b71SSascha Leib // init the sub-objects: 49e56d7b71SSascha Leib BotMon.t._callInit(this); 50e56d7b71SSascha Leib }, 51e56d7b71SSascha Leib 52e56d7b71SSascha Leib _baseDir: null, 53e56d7b71SSascha Leib _lang: 'en', 54e56d7b71SSascha Leib _datestr: '', 55e56d7b71SSascha Leib _timeDiff: '', 56e56d7b71SSascha Leib 57e56d7b71SSascha Leib /* internal tools */ 58e56d7b71SSascha Leib t: { 59e56d7b71SSascha Leib /* helper function to call inits of sub-objects */ 60e56d7b71SSascha Leib _callInit: function(obj) { 61e56d7b71SSascha Leib //console.info('BotMon.t._callInit(obj=',obj,')'); 62e56d7b71SSascha Leib 63e56d7b71SSascha Leib /* call init / _init on each sub-object: */ 64e56d7b71SSascha Leib Object.keys(obj).forEach( (key,i) => { 65e56d7b71SSascha Leib const sub = obj[key]; 66e56d7b71SSascha Leib let init = null; 67e56d7b71SSascha Leib if (typeof sub === 'object' && sub.init) { 68e56d7b71SSascha Leib init = sub.init; 69e56d7b71SSascha Leib } 70e56d7b71SSascha Leib 71e56d7b71SSascha Leib // bind to object 72e56d7b71SSascha Leib if (typeof init == 'function') { 73e56d7b71SSascha Leib const init2 = init.bind(sub); 74e56d7b71SSascha Leib init2(obj); 75e56d7b71SSascha Leib } 76e56d7b71SSascha Leib }); 77e56d7b71SSascha Leib }, 78e56d7b71SSascha Leib 79e56d7b71SSascha Leib /* helper function to calculate the time difference to UTC: */ 80e56d7b71SSascha Leib _getTimeOffset: function() { 81e56d7b71SSascha Leib const now = new Date(); 82e56d7b71SSascha Leib let offset = now.getTimezoneOffset(); // in minutes 83e56d7b71SSascha Leib const sign = Math.sign(offset); // +1 or -1 84e56d7b71SSascha Leib offset = Math.abs(offset); // always positive 85e56d7b71SSascha Leib 86e56d7b71SSascha Leib let hours = 0; 87e56d7b71SSascha Leib while (offset >= 60) { 88e56d7b71SSascha Leib hours += 1; 89e56d7b71SSascha Leib offset -= 60; 90e56d7b71SSascha Leib } 91e56d7b71SSascha Leib return ( hours > 0 ? sign * hours + ' h' : '') + (offset > 0 ? ` ${offset} min` : ''); 92e56d7b71SSascha Leib }, 93e56d7b71SSascha Leib 94e56d7b71SSascha Leib /* helper function to create a new element with all attributes and text content */ 95e56d7b71SSascha Leib _makeElement: function(name, atlist = undefined, text = undefined) { 96e56d7b71SSascha Leib var r = null; 97e56d7b71SSascha Leib try { 98e56d7b71SSascha Leib r = document.createElement(name); 99e56d7b71SSascha Leib if (atlist) { 100e56d7b71SSascha Leib for (let attr in atlist) { 101e56d7b71SSascha Leib r.setAttribute(attr, atlist[attr]); 102e56d7b71SSascha Leib } 103e56d7b71SSascha Leib } 104e56d7b71SSascha Leib if (text) { 105e56d7b71SSascha Leib r.textContent = text.toString(); 106e56d7b71SSascha Leib } 107e56d7b71SSascha Leib } catch(e) { 108e56d7b71SSascha Leib console.error(e); 109e56d7b71SSascha Leib } 110e56d7b71SSascha Leib return r; 111e56d7b71SSascha Leib }, 112e56d7b71SSascha Leib 113e56d7b71SSascha Leib /* helper to convert an ip address string to a normalised format: */ 114e56d7b71SSascha Leib _ip2Num: function(ip) { 115e56d7b71SSascha Leib if (!ip) { 116e56d7b71SSascha Leib return 'null'; 117e56d7b71SSascha Leib } else if (ip.indexOf(':') > 0) { /* IP6 */ 118e56d7b71SSascha Leib return (ip.split(':').map(d => ('0000'+d).slice(-4) ).join(':')); 119e56d7b71SSascha Leib } else { /* IP4 */ 120e56d7b71SSascha Leib return ip.split('.').map(d => ('000'+d).slice(-3) ).join('.'); 121e56d7b71SSascha Leib } 122e56d7b71SSascha Leib }, 123e56d7b71SSascha Leib 124e56d7b71SSascha Leib /* helper function to format a Date object to show only the time. */ 125e56d7b71SSascha Leib /* returns String */ 126e56d7b71SSascha Leib _formatTime: function(date) { 127e56d7b71SSascha Leib 128e56d7b71SSascha Leib if (date) { 129e56d7b71SSascha Leib return date.getHours() + ':' + ('0'+date.getMinutes()).slice(-2) + ':' + ('0'+date.getSeconds()).slice(-2); 130e56d7b71SSascha Leib } else { 131e56d7b71SSascha Leib return null; 132e56d7b71SSascha Leib } 133e56d7b71SSascha Leib 134e56d7b71SSascha Leib }, 135e56d7b71SSascha Leib 136e56d7b71SSascha Leib /* helper function to show a time difference in seconds or minutes */ 137e56d7b71SSascha Leib /* returns String */ 138e56d7b71SSascha Leib _formatTimeDiff: function(dateA, dateB) { 139e56d7b71SSascha Leib 140e56d7b71SSascha Leib // if the second date is ealier, swap them: 141e56d7b71SSascha Leib if (dateA > dateB) dateB = [dateA, dateA = dateB][0]; 142e56d7b71SSascha Leib 143e56d7b71SSascha Leib // get the difference in milliseconds: 144e56d7b71SSascha Leib let ms = dateB - dateA; 145e56d7b71SSascha Leib 146e56d7b71SSascha Leib if (ms > 50) { /* ignore small time spans */ 147e56d7b71SSascha Leib const h = Math.floor((ms / (1000 * 60 * 60)) % 24); 148e56d7b71SSascha Leib const m = Math.floor((ms / (1000 * 60)) % 60); 149e56d7b71SSascha Leib const s = Math.floor((ms / 1000) % 60); 150e56d7b71SSascha Leib 151e56d7b71SSascha Leib return ( h>0 ? h + 'h ': '') + ( m>0 ? m + 'm ': '') + ( s>0 ? s + 's': ''); 152e56d7b71SSascha Leib } 153e56d7b71SSascha Leib 154e56d7b71SSascha Leib return null; 155e56d7b71SSascha Leib 156e56d7b71SSascha Leib }, 157e56d7b71SSascha Leib 158e56d7b71SSascha Leib // calcualte a reduced ration between two numbers 159e56d7b71SSascha Leib // adapted from https://stackoverflow.com/questions/3946373/math-in-js-how-do-i-get-a-ratio-from-a-percentage 160e56d7b71SSascha Leib _getRatio: function(a, b, tolerance) { 161e56d7b71SSascha Leib 162e56d7b71SSascha Leib var bg = b; 163e56d7b71SSascha Leib var sm = a; 164e56d7b71SSascha Leib if (a > b) { 165e56d7b71SSascha Leib var bg = a; 166e56d7b71SSascha Leib var sm = b; 167e56d7b71SSascha Leib } 168e56d7b71SSascha Leib 169e56d7b71SSascha Leib for (var i = 1; i < 1000000; i++) { 170e56d7b71SSascha Leib var d = sm / i; 171e56d7b71SSascha Leib var res = bg / d; 172e56d7b71SSascha Leib var howClose = Math.abs(res - res.toFixed(0)); 173e56d7b71SSascha Leib if (howClose < tolerance) { 174e56d7b71SSascha Leib if (a > b) { 175e56d7b71SSascha Leib return res.toFixed(0) + ':' + i; 176e56d7b71SSascha Leib } else { 177e56d7b71SSascha Leib return i + ':' + res.toFixed(0); 178e56d7b71SSascha Leib } 179e56d7b71SSascha Leib } 180e56d7b71SSascha Leib } 181e56d7b71SSascha Leib } 182e56d7b71SSascha Leib } 183e56d7b71SSascha Leib}; 184e56d7b71SSascha Leib 185e56d7b71SSascha Leib/* everything specific to the "Latest" tab is self-contained in the "live" object: */ 186e56d7b71SSascha LeibBotMon.live = { 187e56d7b71SSascha Leib init: function() { 1880edf1a56SSascha Leib console.info('BotMon.live.init()'); 189e56d7b71SSascha Leib 190e56d7b71SSascha Leib // set the title: 191e56d7b71SSascha Leib const tDiff = '<abbr title="Coordinated Universal Time">UTC</abbr> ' + (BotMon._timeDiff != '' ? ` (offset: ${BotMon._timeDiff}` : '' ) + ')'; 192e56d7b71SSascha Leib BotMon.live.gui.status.setTitle(`Data for <time datetime="${BotMon._datestr}">${BotMon._datestr}</time> ${tDiff}`); 193e56d7b71SSascha Leib 194e56d7b71SSascha Leib // init sub-objects: 195e56d7b71SSascha Leib BotMon.t._callInit(this); 196e56d7b71SSascha Leib }, 197e56d7b71SSascha Leib 198e56d7b71SSascha Leib data: { 199e56d7b71SSascha Leib init: function() { 200e56d7b71SSascha Leib //console.info('BotMon.live.data.init()'); 201e56d7b71SSascha Leib 202e56d7b71SSascha Leib // call sub-inits: 203e56d7b71SSascha Leib BotMon.t._callInit(this); 204e56d7b71SSascha Leib }, 205e56d7b71SSascha Leib 206e56d7b71SSascha Leib // this will be called when the known json files are done loading: 207e56d7b71SSascha Leib _dispatch: function(file) { 208e56d7b71SSascha Leib //console.info('BotMon.live.data._dispatch(,',file,')'); 209e56d7b71SSascha Leib 210e56d7b71SSascha Leib // shortcut to make code more readable: 211e56d7b71SSascha Leib const data = BotMon.live.data; 212e56d7b71SSascha Leib 213e56d7b71SSascha Leib // set the flags: 214e56d7b71SSascha Leib switch(file) { 215e56d7b71SSascha Leib case 'rules': 216e56d7b71SSascha Leib data._dispatchRulesLoaded = true; 217e56d7b71SSascha Leib break; 218e56d7b71SSascha Leib case 'ipranges': 219e56d7b71SSascha Leib data._dispatchIPRangesLoaded = true; 220e56d7b71SSascha Leib break; 221e56d7b71SSascha Leib case 'bots': 222e56d7b71SSascha Leib data._dispatchBotsLoaded = true; 223e56d7b71SSascha Leib break; 224e56d7b71SSascha Leib case 'clients': 225e56d7b71SSascha Leib data._dispatchClientsLoaded = true; 226e56d7b71SSascha Leib break; 227e56d7b71SSascha Leib case 'platforms': 228e56d7b71SSascha Leib data._dispatchPlatformsLoaded = true; 229e56d7b71SSascha Leib break; 230e56d7b71SSascha Leib default: 231e56d7b71SSascha Leib // ignore 232e56d7b71SSascha Leib } 233e56d7b71SSascha Leib 234e56d7b71SSascha Leib // are all the flags set? 235e56d7b71SSascha Leib if (data._dispatchBotsLoaded && data._dispatchClientsLoaded && data._dispatchPlatformsLoaded && data._dispatchRulesLoaded && data._dispatchIPRangesLoaded) { 236e56d7b71SSascha Leib // chain the log files loading: 237e56d7b71SSascha Leib BotMon.live.data.loadLogFile(BM_LOGTYPE.SERVER, BotMon.live.data._onServerLogLoaded); 238e56d7b71SSascha Leib } 239e56d7b71SSascha Leib }, 240e56d7b71SSascha Leib // flags to track which data files have been loaded: 241e56d7b71SSascha Leib _dispatchBotsLoaded: false, 242e56d7b71SSascha Leib _dispatchClientsLoaded: false, 243e56d7b71SSascha Leib _dispatchPlatformsLoaded: false, 244e56d7b71SSascha Leib _dispatchIPRangesLoaded: false, 245e56d7b71SSascha Leib _dispatchRulesLoaded: false, 246e56d7b71SSascha Leib 247e56d7b71SSascha Leib // event callback, after the server log has been loaded: 248e56d7b71SSascha Leib _onServerLogLoaded: function() { 249e56d7b71SSascha Leib //console.info('BotMon.live.data._onServerLogLoaded()'); 250e56d7b71SSascha Leib 251e56d7b71SSascha Leib // chain the client log file to load: 252e56d7b71SSascha Leib BotMon.live.data.loadLogFile(BM_LOGTYPE.CLIENT, BotMon.live.data._onClientLogLoaded); 253e56d7b71SSascha Leib }, 254e56d7b71SSascha Leib 255e56d7b71SSascha Leib // event callback, after the client log has been loaded: 256e56d7b71SSascha Leib _onClientLogLoaded: function() { 257e56d7b71SSascha Leib //console.info('BotMon.live.data._onClientLogLoaded()'); 258e56d7b71SSascha Leib 259e56d7b71SSascha Leib // chain the ticks file to load: 260e56d7b71SSascha Leib BotMon.live.data.loadLogFile(BM_LOGTYPE.TICKER, BotMon.live.data._onTicksLogLoaded); 261e56d7b71SSascha Leib 262e56d7b71SSascha Leib }, 263e56d7b71SSascha Leib 264e56d7b71SSascha Leib // event callback, after the tiker log has been loaded: 265e56d7b71SSascha Leib _onTicksLogLoaded: function() { 266e56d7b71SSascha Leib //console.info('BotMon.live.data._onTicksLogLoaded()'); 267e56d7b71SSascha Leib 268e56d7b71SSascha Leib // analyse the data: 269e56d7b71SSascha Leib BotMon.live.data.analytics.analyseAll(); 270e56d7b71SSascha Leib 271e56d7b71SSascha Leib // sort the data: 272e56d7b71SSascha Leib // #TODO 273e56d7b71SSascha Leib 274e56d7b71SSascha Leib // display the data: 275e56d7b71SSascha Leib BotMon.live.gui.overview.make(); 276e56d7b71SSascha Leib 277e56d7b71SSascha Leib //console.log(BotMon.live.data.model._visitors); 278e56d7b71SSascha Leib 279e56d7b71SSascha Leib }, 280e56d7b71SSascha Leib 281e56d7b71SSascha Leib // the data model: 282e56d7b71SSascha Leib model: { 283e56d7b71SSascha Leib // visitors storage: 284e56d7b71SSascha Leib _visitors: [], 285e56d7b71SSascha Leib 286e56d7b71SSascha Leib // find an already existing visitor record: 287e56d7b71SSascha Leib findVisitor: function(visitor, type) { 288e56d7b71SSascha Leib //console.info('BotMon.live.data.model.findVisitor()', type); 289e56d7b71SSascha Leib //console.log(visitor); 290e56d7b71SSascha Leib 291e56d7b71SSascha Leib // shortcut to make code more readable: 292e56d7b71SSascha Leib const model = BotMon.live.data.model; 293e56d7b71SSascha Leib 294e56d7b71SSascha Leib const timeout = 60 * 60 * 1000; // session timeout: One hour 295e56d7b71SSascha Leib 296e56d7b71SSascha Leib if (visitor._type == BM_USERTYPE.KNOWN_BOT) { // known bots match by their bot ID: 297e56d7b71SSascha Leib 298e56d7b71SSascha Leib for (let i=0; i<model._visitors.length; i++) { 299e56d7b71SSascha Leib const v = model._visitors[i]; 300e56d7b71SSascha Leib 301e56d7b71SSascha Leib // bots match when their ID matches: 302e56d7b71SSascha Leib if (v._bot && v._bot.id == visitor._bot.id) { 303e56d7b71SSascha Leib return v; 304e56d7b71SSascha Leib } 305e56d7b71SSascha Leib } 306e56d7b71SSascha Leib } else { // other types match by their DW/PHPIDs: 307e56d7b71SSascha Leib 308e56d7b71SSascha Leib // loop over all visitors already registered and check for ID matches: 309e56d7b71SSascha Leib for (let i=0; i<model._visitors.length; i++) { 310e56d7b71SSascha Leib const v = model._visitors[i]; 311e56d7b71SSascha Leib 312e56d7b71SSascha Leib if ( v.id == visitor.id) { // match the DW/PHP IDs 313e56d7b71SSascha Leib return v; 314e56d7b71SSascha Leib } 315e56d7b71SSascha Leib } 316e56d7b71SSascha Leib 317e56d7b71SSascha Leib // if not found, try to match IP address and user agent: 318e56d7b71SSascha Leib for (let i=0; i<model._visitors.length; i++) { 319e56d7b71SSascha Leib const v = model._visitors[i]; 320e56d7b71SSascha Leib if ( v.ip == visitor.ip && v.agent == visitor.agent) { 321e56d7b71SSascha Leib return v; 322e56d7b71SSascha Leib } 323e56d7b71SSascha Leib } 324e56d7b71SSascha Leib } 325e56d7b71SSascha Leib 326e56d7b71SSascha Leib return null; // nothing found 327e56d7b71SSascha Leib }, 328e56d7b71SSascha Leib 329e56d7b71SSascha Leib /* if there is already this visit registered, return the page view item */ 330e56d7b71SSascha Leib _getPageView: function(visit, view) { 331e56d7b71SSascha Leib 332e56d7b71SSascha Leib // shortcut to make code more readable: 333e56d7b71SSascha Leib const model = BotMon.live.data.model; 334e56d7b71SSascha Leib 335e56d7b71SSascha Leib for (let i=0; i<visit._pageViews.length; i++) { 336e56d7b71SSascha Leib const pv = visit._pageViews[i]; 337e56d7b71SSascha Leib if (pv.pg == view.pg) { 338e56d7b71SSascha Leib return pv; 339e56d7b71SSascha Leib } 340e56d7b71SSascha Leib } 341e56d7b71SSascha Leib return null; // not found 342e56d7b71SSascha Leib }, 343e56d7b71SSascha Leib 344e56d7b71SSascha Leib // register a new visitor (or update if already exists) 345e56d7b71SSascha Leib registerVisit: function(nv, type) { 346e56d7b71SSascha Leib //console.info('registerVisit', nv, type); 347e56d7b71SSascha Leib 348e56d7b71SSascha Leib // shortcut to make code more readable: 349e56d7b71SSascha Leib const model = BotMon.live.data.model; 350e56d7b71SSascha Leib 351e56d7b71SSascha Leib // is it a known bot? 352e56d7b71SSascha Leib const bot = BotMon.live.data.bots.match(nv.agent); 353e56d7b71SSascha Leib 354e56d7b71SSascha Leib // enrich new visitor with relevant data: 355e56d7b71SSascha Leib if (!nv._bot) nv._bot = bot ?? null; // bot info 356e56d7b71SSascha Leib nv._type = ( bot ? BM_USERTYPE.KNOWN_BOT : ( nv.usr && nv.usr !== '' ? BM_USERTYPE.KNOWN_USER : BM_USERTYPE.UNKNOWN ) ); // user type 357e56d7b71SSascha Leib if (bot && bot.geo) { 358e56d7b71SSascha Leib if (!nv.geo || nv.geo == '' || nv.geo == 'ZZ') nv.geo = bot.geo; 359e56d7b71SSascha Leib } else if (!nv.geo ||nv.geo == '') { 360e56d7b71SSascha Leib nv.geo = 'ZZ'; 361e56d7b71SSascha Leib } 362e56d7b71SSascha Leib 363e56d7b71SSascha Leib // update first and last seen: 364e56d7b71SSascha Leib if (!nv._firstSeen) nv._firstSeen = nv.ts; // first-seen 365e56d7b71SSascha Leib nv._lastSeen = nv.ts; // last-seen 366e56d7b71SSascha Leib 367e56d7b71SSascha Leib // country name: 368e56d7b71SSascha Leib try { 369e56d7b71SSascha Leib nv._country = ( nv.geo == 'local' ? "localhost" : "Unknown" ); 370e56d7b71SSascha Leib if (nv.geo && nv.geo !== '' && nv.geo !== 'ZZ' && nv.geo !== 'local') { 371e56d7b71SSascha Leib const countryName = new Intl.DisplayNames(['en', BotMon._lang], {type: 'region'}); 372e56d7b71SSascha Leib nv._country = countryName.of(nv.geo.substring(0,2)) ?? nv.geo; 373e56d7b71SSascha Leib } 374e56d7b71SSascha Leib } catch (err) { 375e56d7b71SSascha Leib console.error(err); 376e56d7b71SSascha Leib nv._country = 'Error'; 377e56d7b71SSascha Leib } 378e56d7b71SSascha Leib 379e56d7b71SSascha Leib // check if it already exists: 380e56d7b71SSascha Leib let visitor = model.findVisitor(nv, type); 381e56d7b71SSascha Leib if (!visitor) { 382e56d7b71SSascha Leib visitor = nv; 383e56d7b71SSascha Leib visitor._seenBy = [type]; 384e56d7b71SSascha Leib visitor._pageViews = []; // array of page views 385e56d7b71SSascha Leib visitor._hasReferrer = false; // has at least one referrer 386e56d7b71SSascha Leib visitor._jsClient = false; // visitor has been seen logged by client js as well 387e56d7b71SSascha Leib visitor._client = BotMon.live.data.clients.match(nv.agent) ?? null; // client info 388e56d7b71SSascha Leib visitor._platform = BotMon.live.data.platforms.match(nv.agent); // platform info 389e56d7b71SSascha Leib model._visitors.push(visitor); 390e56d7b71SSascha Leib } else { // update existing 391e56d7b71SSascha Leib if (visitor._firstSeen > nv.ts) { 392e56d7b71SSascha Leib visitor._firstSeen = nv.ts; 393e56d7b71SSascha Leib } 394e56d7b71SSascha Leib } 395e56d7b71SSascha Leib 396e56d7b71SSascha Leib // find browser 397e56d7b71SSascha Leib 398e56d7b71SSascha Leib // is this visit already registered? 399e56d7b71SSascha Leib let prereg = model._getPageView(visitor, nv); 400e56d7b71SSascha Leib if (!prereg) { 401e56d7b71SSascha Leib // add new page view: 402e56d7b71SSascha Leib prereg = model._makePageView(nv, type); 403e56d7b71SSascha Leib visitor._pageViews.push(prereg); 404e56d7b71SSascha Leib } else { 405e56d7b71SSascha Leib // update last seen date 406e56d7b71SSascha Leib prereg._lastSeen = nv.ts; 407e56d7b71SSascha Leib // increase view count: 408e56d7b71SSascha Leib prereg._viewCount += 1; 409e56d7b71SSascha Leib prereg._tickCount += 1; 410e56d7b71SSascha Leib } 411e56d7b71SSascha Leib 412e56d7b71SSascha Leib // update referrer state: 413e56d7b71SSascha Leib visitor._hasReferrer = visitor._hasReferrer || 414e56d7b71SSascha Leib (prereg.ref !== undefined && prereg.ref !== ''); 415e56d7b71SSascha Leib 416e56d7b71SSascha Leib // update time stamp for last-seen: 417e56d7b71SSascha Leib if (visitor._lastSeen < nv.ts) { 418e56d7b71SSascha Leib visitor._lastSeen = nv.ts; 419e56d7b71SSascha Leib } 420e56d7b71SSascha Leib 421e56d7b71SSascha Leib // if needed: 422e56d7b71SSascha Leib return visitor; 423e56d7b71SSascha Leib }, 424e56d7b71SSascha Leib 425e56d7b71SSascha Leib // updating visit data from the client-side log: 426e56d7b71SSascha Leib updateVisit: function(dat) { 427e56d7b71SSascha Leib //console.info('updateVisit', dat); 428e56d7b71SSascha Leib 429e56d7b71SSascha Leib // shortcut to make code more readable: 430e56d7b71SSascha Leib const model = BotMon.live.data.model; 431e56d7b71SSascha Leib 432e56d7b71SSascha Leib const type = BM_LOGTYPE.CLIENT; 433e56d7b71SSascha Leib 434e56d7b71SSascha Leib let visitor = BotMon.live.data.model.findVisitor(dat, type); 435e56d7b71SSascha Leib if (!visitor) { 436e56d7b71SSascha Leib visitor = model.registerVisit(dat, type); 437e56d7b71SSascha Leib } 438e56d7b71SSascha Leib if (visitor) { 439e56d7b71SSascha Leib 440e56d7b71SSascha Leib if (visitor._lastSeen < dat.ts) { 441e56d7b71SSascha Leib visitor._lastSeen = dat.ts; 442e56d7b71SSascha Leib } 443e56d7b71SSascha Leib if (!visitor._seenBy.includes(type)) { 444e56d7b71SSascha Leib visitor._seenBy.push(type); 445e56d7b71SSascha Leib } 446e56d7b71SSascha Leib visitor._jsClient = true; // seen by client js 447e56d7b71SSascha Leib } 448e56d7b71SSascha Leib 449e56d7b71SSascha Leib // find the page view: 450e56d7b71SSascha Leib let prereg = BotMon.live.data.model._getPageView(visitor, dat); 451e56d7b71SSascha Leib if (prereg) { 452e56d7b71SSascha Leib // update the page view: 453e56d7b71SSascha Leib prereg._lastSeen = dat.ts; 454e56d7b71SSascha Leib if (!prereg._seenBy.includes(type)) prereg._seenBy.push(type); 455e56d7b71SSascha Leib prereg._jsClient = true; // seen by client js 456e56d7b71SSascha Leib } else { 457e56d7b71SSascha Leib // add the page view to the visitor: 458e56d7b71SSascha Leib prereg = model._makePageView(dat, type); 459e56d7b71SSascha Leib visitor._pageViews.push(prereg); 460e56d7b71SSascha Leib } 461e56d7b71SSascha Leib prereg._tickCount += 1; 462e56d7b71SSascha Leib }, 463e56d7b71SSascha Leib 464e56d7b71SSascha Leib // updating visit data from the ticker log: 465e56d7b71SSascha Leib updateTicks: function(dat) { 466e56d7b71SSascha Leib //console.info('updateTicks', dat); 467e56d7b71SSascha Leib 468e56d7b71SSascha Leib // shortcut to make code more readable: 469e56d7b71SSascha Leib const model = BotMon.live.data.model; 470e56d7b71SSascha Leib 471e56d7b71SSascha Leib const type = BM_LOGTYPE.TICKER; 472e56d7b71SSascha Leib 473e56d7b71SSascha Leib // find the visit info: 474e56d7b71SSascha Leib let visitor = model.findVisitor(dat, type); 475e56d7b71SSascha Leib if (!visitor) { 476e56d7b71SSascha Leib console.info(`No visitor with ID “${dat.id}” found, registering as a new one.`); 477e56d7b71SSascha Leib visitor = model.registerVisit(dat, type); 478e56d7b71SSascha Leib } 479e56d7b71SSascha Leib if (visitor) { 480e56d7b71SSascha Leib // update visitor: 481e56d7b71SSascha Leib if (visitor._lastSeen < dat.ts) visitor._lastSeen = dat.ts; 482e56d7b71SSascha Leib if (!visitor._seenBy.includes(type)) visitor._seenBy.push(type); 483e56d7b71SSascha Leib 484e56d7b71SSascha Leib // get the page view info: 485e56d7b71SSascha Leib let pv = model._getPageView(visitor, dat); 486e56d7b71SSascha Leib if (!pv) { 487e56d7b71SSascha Leib console.info(`No page view for visit ID “${dat.id}”, page “${dat.pg}”, registering a new one.`); 488e56d7b71SSascha Leib pv = model._makePageView(dat, type); 489e56d7b71SSascha Leib visitor._pageViews.push(pv); 490e56d7b71SSascha Leib } 491e56d7b71SSascha Leib 492e56d7b71SSascha Leib // update the page view info: 493e56d7b71SSascha Leib if (!pv._seenBy.includes(type)) pv._seenBy.push(type); 494e56d7b71SSascha Leib if (pv._lastSeen.getTime() < dat.ts.getTime()) pv._lastSeen = dat.ts; 495e56d7b71SSascha Leib pv._tickCount += 1; 496e56d7b71SSascha Leib 497e56d7b71SSascha Leib } 498e56d7b71SSascha Leib }, 499e56d7b71SSascha Leib 500e56d7b71SSascha Leib // helper function to create a new "page view" item: 501e56d7b71SSascha Leib _makePageView: function(data, type) { 502e56d7b71SSascha Leib // console.info('_makePageView', data); 503e56d7b71SSascha Leib 504e56d7b71SSascha Leib // try to parse the referrer: 505e56d7b71SSascha Leib let rUrl = null; 506e56d7b71SSascha Leib try { 507e56d7b71SSascha Leib rUrl = ( data.ref && data.ref !== '' ? new URL(data.ref) : null ); 508e56d7b71SSascha Leib } catch (e) { 509e56d7b71SSascha Leib console.warn(`Invalid referer: “${data.ref}”.`); 510e56d7b71SSascha Leib console.info(data); 511e56d7b71SSascha Leib } 512e56d7b71SSascha Leib 513e56d7b71SSascha Leib return { 514e56d7b71SSascha Leib _by: type, 515e56d7b71SSascha Leib ip: data.ip, 516e56d7b71SSascha Leib pg: data.pg, 517e56d7b71SSascha Leib lang: data.lang || '??', 518e56d7b71SSascha Leib _ref: rUrl, 519e56d7b71SSascha Leib _firstSeen: data.ts, 520e56d7b71SSascha Leib _lastSeen: data.ts, 521e56d7b71SSascha Leib _seenBy: [type], 522e56d7b71SSascha Leib _jsClient: ( type !== BM_LOGTYPE.SERVER), 523e56d7b71SSascha Leib _viewCount: 1, 524e56d7b71SSascha Leib _tickCount: 0 525e56d7b71SSascha Leib }; 526e56d7b71SSascha Leib } 527e56d7b71SSascha Leib }, 528e56d7b71SSascha Leib 529e56d7b71SSascha Leib // functions to analyse the data: 530e56d7b71SSascha Leib analytics: { 531e56d7b71SSascha Leib 532e56d7b71SSascha Leib /** 533e56d7b71SSascha Leib * Initializes the analytics data storage object: 534e56d7b71SSascha Leib */ 535e56d7b71SSascha Leib init: function() { 536e56d7b71SSascha Leib //console.info('BotMon.live.data.analytics.init()'); 537e56d7b71SSascha Leib }, 538e56d7b71SSascha Leib 539e56d7b71SSascha Leib // data storage: 540e56d7b71SSascha Leib data: { 541e56d7b71SSascha Leib totalVisits: 0, 542e56d7b71SSascha Leib totalPageViews: 0, 543e56d7b71SSascha Leib humanPageViews: 0, 544e56d7b71SSascha Leib bots: { 545e56d7b71SSascha Leib known: 0, 546e56d7b71SSascha Leib suspected: 0, 547e56d7b71SSascha Leib human: 0, 548e56d7b71SSascha Leib users: 0 549e56d7b71SSascha Leib } 550e56d7b71SSascha Leib }, 551e56d7b71SSascha Leib 552e56d7b71SSascha Leib // sort the visits by type: 553e56d7b71SSascha Leib groups: { 554e56d7b71SSascha Leib knownBots: [], 555e56d7b71SSascha Leib suspectedBots: [], 556e56d7b71SSascha Leib humans: [], 557e56d7b71SSascha Leib users: [] 558e56d7b71SSascha Leib }, 559e56d7b71SSascha Leib 560e56d7b71SSascha Leib // all analytics 561e56d7b71SSascha Leib analyseAll: function() { 562e56d7b71SSascha Leib //console.info('BotMon.live.data.analytics.analyseAll()'); 563e56d7b71SSascha Leib 564e56d7b71SSascha Leib // shortcut to make code more readable: 565e56d7b71SSascha Leib const model = BotMon.live.data.model; 566e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 567e56d7b71SSascha Leib 568e56d7b71SSascha Leib BotMon.live.gui.status.showBusy("Analysing data …"); 569e56d7b71SSascha Leib 570e56d7b71SSascha Leib // loop over all visitors: 571e56d7b71SSascha Leib model._visitors.forEach( (v) => { 572e56d7b71SSascha Leib 573e56d7b71SSascha Leib // count visits and page views: 574e56d7b71SSascha Leib this.data.totalVisits += 1; 575e56d7b71SSascha Leib this.data.totalPageViews += v._pageViews.length; 576e56d7b71SSascha Leib 577e56d7b71SSascha Leib // check for typical bot aspects: 578e56d7b71SSascha Leib let botScore = 0; 579e56d7b71SSascha Leib 580e56d7b71SSascha Leib if (v._type == BM_USERTYPE.KNOWN_BOT) { // known bots 581e56d7b71SSascha Leib 582e56d7b71SSascha Leib this.data.bots.known += v._pageViews.length; 583e56d7b71SSascha Leib this.groups.knownBots.push(v); 584e56d7b71SSascha Leib 585e56d7b71SSascha Leib } else if (v._type == BM_USERTYPE.KNOWN_USER) { // known users */ 586e56d7b71SSascha Leib 587e56d7b71SSascha Leib this.data.bots.users += v._pageViews.length; 588e56d7b71SSascha Leib this.groups.users.push(v); 589e56d7b71SSascha Leib 590e56d7b71SSascha Leib } else { 591e56d7b71SSascha Leib 592e56d7b71SSascha Leib // get evaluation: 593e56d7b71SSascha Leib const e = BotMon.live.data.rules.evaluate(v); 594e56d7b71SSascha Leib v._eval = e.rules; 595e56d7b71SSascha Leib v._botVal = e.val; 596e56d7b71SSascha Leib 597e56d7b71SSascha Leib if (e.isBot) { // likely bots 598e56d7b71SSascha Leib v._type = BM_USERTYPE.LIKELY_BOT; 599e56d7b71SSascha Leib this.data.bots.suspected += v._pageViews.length; 600e56d7b71SSascha Leib this.groups.suspectedBots.push(v); 601e56d7b71SSascha Leib } else { // probably humans 602e56d7b71SSascha Leib v._type = BM_USERTYPE.PROBABLY_HUMAN; 603e56d7b71SSascha Leib this.data.bots.human += v._pageViews.length; 604e56d7b71SSascha Leib this.groups.humans.push(v); 605e56d7b71SSascha Leib } 606e56d7b71SSascha Leib } 607e56d7b71SSascha Leib 608e56d7b71SSascha Leib // perform actions depending on the visitor type: 609e56d7b71SSascha Leib if (v._type == BM_USERTYPE.KNOWN_BOT ) { /* known bots only */ 610e56d7b71SSascha Leib 611e56d7b71SSascha Leib } else if (v._type == BM_USERTYPE.LIKELY_BOT) { /* probable bots only */ 612e56d7b71SSascha Leib 613e56d7b71SSascha Leib // add bot views to IP range information: 614e56d7b71SSascha Leib me.addToIpRanges(v); 615e56d7b71SSascha Leib 616e56d7b71SSascha Leib } else { /* humans only */ 617e56d7b71SSascha Leib 618e56d7b71SSascha Leib // add browser and platform statistics: 619e56d7b71SSascha Leib me.addBrowserPlatform(v); 620e56d7b71SSascha Leib 621e56d7b71SSascha Leib // add 622e56d7b71SSascha Leib v._pageViews.forEach( pv => { 623e56d7b71SSascha Leib me.addToRefererList(pv._ref); 624e56d7b71SSascha Leib me.addToPagesList(pv.pg); 625e56d7b71SSascha Leib }); 626e56d7b71SSascha Leib } 627e56d7b71SSascha Leib 628e56d7b71SSascha Leib // add to the country lists: 629e56d7b71SSascha Leib me.addToCountries(v.geo, v._country, v._type); 630e56d7b71SSascha Leib 631e56d7b71SSascha Leib }); 632e56d7b71SSascha Leib 633e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy('Done.'); 634e56d7b71SSascha Leib }, 635e56d7b71SSascha Leib 636e56d7b71SSascha Leib // get a list of known bots: 637e56d7b71SSascha Leib getTopBots: function(max) { 638e56d7b71SSascha Leib //console.info('BotMon.live.data.analytics.getTopBots('+max+')'); 639e56d7b71SSascha Leib 640e56d7b71SSascha Leib //console.log(BotMon.live.data.analytics.groups.knownBots); 641e56d7b71SSascha Leib 642e56d7b71SSascha Leib let botsList = BotMon.live.data.analytics.groups.knownBots.toSorted( (a, b) => { 643e56d7b71SSascha Leib return b._pageViews.length - a._pageViews.length; 644e56d7b71SSascha Leib }); 645e56d7b71SSascha Leib 646e56d7b71SSascha Leib const other = { 647e56d7b71SSascha Leib 'id': 'other', 648e56d7b71SSascha Leib 'name': "Others", 649e56d7b71SSascha Leib 'count': 0 650e56d7b71SSascha Leib }; 651e56d7b71SSascha Leib 652e56d7b71SSascha Leib const rList = []; 653e56d7b71SSascha Leib const max2 = ( botsList.length > max ? max-1 : botsList.length ); 654e56d7b71SSascha Leib let total = 0; // adding up the items 655e56d7b71SSascha Leib for (let i=0; i<botsList.length; i++) { 656e56d7b71SSascha Leib const it = botsList[i]; 657e56d7b71SSascha Leib if (it && it._bot) { 658e56d7b71SSascha Leib if (i < max2) { 659e56d7b71SSascha Leib rList.push({ 660e56d7b71SSascha Leib id: it._bot.id, 661e56d7b71SSascha Leib name: (it._bot.n ? it._bot.n : it._bot.id), 662e56d7b71SSascha Leib count: it._pageViews.length 663e56d7b71SSascha Leib }); 664e56d7b71SSascha Leib } else { 665e56d7b71SSascha Leib other.count += it._pageViews.length; 666e56d7b71SSascha Leib }; 667e56d7b71SSascha Leib total += it._pageViews.length; 668e56d7b71SSascha Leib } 669e56d7b71SSascha Leib }; 670e56d7b71SSascha Leib 671e56d7b71SSascha Leib // add the "other" item, if needed: 672e56d7b71SSascha Leib if (botsList.length > max2) { 673e56d7b71SSascha Leib rList.push(other); 674e56d7b71SSascha Leib }; 675e56d7b71SSascha Leib 676e56d7b71SSascha Leib rList.forEach( it => { 677e56d7b71SSascha Leib it.pct = (it.count * 100 / total); 678e56d7b71SSascha Leib }); 679e56d7b71SSascha Leib 680e56d7b71SSascha Leib return rList; 681e56d7b71SSascha Leib }, 682e56d7b71SSascha Leib 683e56d7b71SSascha Leib // most visited pages list: 684e56d7b71SSascha Leib _pagesList: [], 685e56d7b71SSascha Leib 686e56d7b71SSascha Leib /** 687e56d7b71SSascha Leib * Add a page view to the list of most visited pages. 688e56d7b71SSascha Leib * @param {string} pageId - The page ID to add to the list. 689e56d7b71SSascha Leib * @example 690e56d7b71SSascha Leib * BotMon.live.data.analytics.addToPagesList('1234567890'); 691e56d7b71SSascha Leib */ 692e56d7b71SSascha Leib addToPagesList: function(pageId) { 693e56d7b71SSascha Leib //console.log('BotMon.live.data.analytics.addToPagesList', pageId); 694e56d7b71SSascha Leib 695e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 696e56d7b71SSascha Leib 697e56d7b71SSascha Leib // already exists? 698e56d7b71SSascha Leib let pgObj = null; 699e56d7b71SSascha Leib for (let i = 0; i < me._pagesList.length; i++) { 700e56d7b71SSascha Leib if (me._pagesList[i].id == pageId) { 701e56d7b71SSascha Leib pgObj = me._pagesList[i]; 702e56d7b71SSascha Leib break; 703e56d7b71SSascha Leib } 704e56d7b71SSascha Leib } 705e56d7b71SSascha Leib 706e56d7b71SSascha Leib // if not exists, create it: 707e56d7b71SSascha Leib if (!pgObj) { 708e56d7b71SSascha Leib pgObj = { 709e56d7b71SSascha Leib id: pageId, 710e56d7b71SSascha Leib count: 1 711e56d7b71SSascha Leib }; 712e56d7b71SSascha Leib me._pagesList.push(pgObj); 713e56d7b71SSascha Leib } else { 714e56d7b71SSascha Leib pgObj.count += 1; 715e56d7b71SSascha Leib } 716e56d7b71SSascha Leib }, 717e56d7b71SSascha Leib 718e56d7b71SSascha Leib getTopPages: function(max) { 719e56d7b71SSascha Leib //console.info('BotMon.live.data.analytics.getTopPages('+max+')'); 720e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 721e56d7b71SSascha Leib return me._pagesList.toSorted( (a, b) => { 722e56d7b71SSascha Leib return b.count - a.count; 723e56d7b71SSascha Leib }).slice(0,max); 724e56d7b71SSascha Leib }, 725e56d7b71SSascha Leib 726e56d7b71SSascha Leib // Referer List: 727e56d7b71SSascha Leib _refererList: [], 728e56d7b71SSascha Leib 729e56d7b71SSascha Leib addToRefererList: function(ref) { 730e56d7b71SSascha Leib //console.log('BotMon.live.data.analytics.addToRefererList',ref); 731e56d7b71SSascha Leib 732e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 733e56d7b71SSascha Leib 734e56d7b71SSascha Leib // ignore internal references: 735e56d7b71SSascha Leib if (ref && ref.host == window.location.host) { 736e56d7b71SSascha Leib return; 737e56d7b71SSascha Leib } 738e56d7b71SSascha Leib 739e56d7b71SSascha Leib const refInfo = me.getRefererInfo(ref); 740e56d7b71SSascha Leib 741e56d7b71SSascha Leib // already exists? 742e56d7b71SSascha Leib let refObj = null; 743e56d7b71SSascha Leib for (let i = 0; i < me._refererList.length; i++) { 744e56d7b71SSascha Leib if (me._refererList[i].id == refInfo.id) { 745e56d7b71SSascha Leib refObj = me._refererList[i]; 746e56d7b71SSascha Leib break; 747e56d7b71SSascha Leib } 748e56d7b71SSascha Leib } 749e56d7b71SSascha Leib 750e56d7b71SSascha Leib // if not exists, create it: 751e56d7b71SSascha Leib if (!refObj) { 752e56d7b71SSascha Leib refObj = refInfo; 753e56d7b71SSascha Leib refObj.count = 1; 754e56d7b71SSascha Leib me._refererList.push(refObj); 755e56d7b71SSascha Leib } else { 756e56d7b71SSascha Leib refObj.count += 1; 757e56d7b71SSascha Leib } 758e56d7b71SSascha Leib }, 759e56d7b71SSascha Leib 760e56d7b71SSascha Leib getRefererInfo: function(url) { 761e56d7b71SSascha Leib //console.log('BotMon.live.data.analytics.getRefererInfo',url); 762e56d7b71SSascha Leib try { 763e56d7b71SSascha Leib url = new URL(url); 764e56d7b71SSascha Leib } catch (e) { 765e56d7b71SSascha Leib return { 766e56d7b71SSascha Leib 'id': 'null', 767e56d7b71SSascha Leib 'n': 'Invalid Referer' 768e56d7b71SSascha Leib }; 769e56d7b71SSascha Leib } 770e56d7b71SSascha Leib 771e56d7b71SSascha Leib // find the referer ID: 772e56d7b71SSascha Leib let refId = 'null'; 773e56d7b71SSascha Leib let refName = 'No Referer'; 774e56d7b71SSascha Leib if (url && url.host) { 775e56d7b71SSascha Leib const hArr = url.host.split('.'); 776e56d7b71SSascha Leib const tld = hArr[hArr.length-1]; 777e56d7b71SSascha Leib refId = ( tld == 'localhost' ? tld : hArr[hArr.length-2]); 778e56d7b71SSascha Leib refName = hArr[hArr.length-2] + '.' + tld; 779e56d7b71SSascha Leib } 780e56d7b71SSascha Leib 781e56d7b71SSascha Leib return { 782e56d7b71SSascha Leib 'id': refId, 783e56d7b71SSascha Leib 'n': refName 784e56d7b71SSascha Leib }; 785e56d7b71SSascha Leib }, 786e56d7b71SSascha Leib 787e56d7b71SSascha Leib /** 788e56d7b71SSascha Leib * Get a sorted list of the top referers. 789e56d7b71SSascha Leib * The list is sorted in descending order of count. 790e56d7b71SSascha Leib * If the array has more items than the given maximum, the rest of the items are added to an "other" item. 791e56d7b71SSascha Leib * Each item in the list has a "pct" property, which is the percentage of the total count. 792e56d7b71SSascha Leib * @param {number} max - The maximum number of items to return. 793e56d7b71SSascha Leib * @return {Array} The sorted list of top referers. 794e56d7b71SSascha Leib */ 795e56d7b71SSascha Leib getTopReferers: function(max) { 796e56d7b71SSascha Leib //console.info(('BotMon.live.data.analytics.getTopReferers(' + max + ')')); 797e56d7b71SSascha Leib 798e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 799e56d7b71SSascha Leib 800e56d7b71SSascha Leib return me._makeTopList(me._refererList, max); 801e56d7b71SSascha Leib }, 802e56d7b71SSascha Leib 803e56d7b71SSascha Leib /** 804e56d7b71SSascha Leib * Create a sorted list of top items from a given array. 805e56d7b71SSascha Leib * The list is sorted in descending order of count. 806e56d7b71SSascha Leib * If the array has more items than the given maximum, the rest of the items are added to an "other" item. 807e56d7b71SSascha Leib * Each item in the list has a "pct" property, which is the percentage of the total count. 808e56d7b71SSascha Leib * @param {Array} arr - The array to sort and truncate. 809e56d7b71SSascha Leib * @param {number} max - The maximum number of items to return. 810e56d7b71SSascha Leib * @return {Array} The sorted list of top items. 811e56d7b71SSascha Leib */ 812e56d7b71SSascha Leib _makeTopList: function(arr, max) { 813e56d7b71SSascha Leib //console.info(('BotMon.live.data.analytics._makeTopList(arr,' + max + ')')); 814e56d7b71SSascha Leib 815e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 816e56d7b71SSascha Leib 817e56d7b71SSascha Leib // sort the list: 818e56d7b71SSascha Leib arr.sort( (a,b) => { 819e56d7b71SSascha Leib return b.count - a.count; 820e56d7b71SSascha Leib }); 821e56d7b71SSascha Leib 822e56d7b71SSascha Leib const rList = []; // return array 823e56d7b71SSascha Leib const max2 = ( arr.length >= max ? max-1 : arr.length ); 824e56d7b71SSascha Leib const other = { 825e56d7b71SSascha Leib 'id': 'other', 826e56d7b71SSascha Leib 'name': "Others", 827e56d7b71SSascha Leib 'count': 0 828e56d7b71SSascha Leib }; 829e56d7b71SSascha Leib let total = 0; // adding up the items 830e56d7b71SSascha Leib for (let i=0; Math.min(max, arr.length) > i; i++) { 831e56d7b71SSascha Leib const it = arr[i]; 832e56d7b71SSascha Leib if (it) { 833e56d7b71SSascha Leib if (i < max2) { 834e56d7b71SSascha Leib const rIt = { 835e56d7b71SSascha Leib id: it.id, 836e56d7b71SSascha Leib name: (it.n ? it.n : it.id), 837e56d7b71SSascha Leib count: it.count 838e56d7b71SSascha Leib }; 839e56d7b71SSascha Leib rList.push(rIt); 840e56d7b71SSascha Leib } else { 841e56d7b71SSascha Leib other.count += it.count; 842e56d7b71SSascha Leib } 843e56d7b71SSascha Leib total += it.count; 844e56d7b71SSascha Leib } 845e56d7b71SSascha Leib } 846e56d7b71SSascha Leib 847e56d7b71SSascha Leib // add the "other" item, if needed: 848e56d7b71SSascha Leib if (arr.length > max2) { 849e56d7b71SSascha Leib rList.push(other); 850e56d7b71SSascha Leib }; 851e56d7b71SSascha Leib 852e56d7b71SSascha Leib rList.forEach( it => { 853e56d7b71SSascha Leib it.pct = (it.count * 100 / total); 854e56d7b71SSascha Leib }); 855e56d7b71SSascha Leib 856e56d7b71SSascha Leib return rList; 857e56d7b71SSascha Leib }, 858e56d7b71SSascha Leib 859e56d7b71SSascha Leib /* countries of visits */ 860e56d7b71SSascha Leib _countries: { 861e56d7b71SSascha Leib 'human': [], 862e56d7b71SSascha Leib 'bot': [] 863e56d7b71SSascha Leib }, 864e56d7b71SSascha Leib /** 865e56d7b71SSascha Leib * Adds a country code to the statistics. 866e56d7b71SSascha Leib * 867e56d7b71SSascha Leib * @param {string} iso The ISO 3166-1 alpha-2 country code. 868e56d7b71SSascha Leib */ 869e56d7b71SSascha Leib addToCountries: function(iso, name, type) { 870e56d7b71SSascha Leib 871e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 872e56d7b71SSascha Leib 873e56d7b71SSascha Leib // find the correct array: 874e56d7b71SSascha Leib let arr = null; 875e56d7b71SSascha Leib switch (type) { 876e56d7b71SSascha Leib 877e56d7b71SSascha Leib case BM_USERTYPE.KNOWN_USER: 878e56d7b71SSascha Leib case BM_USERTYPE.PROBABLY_HUMAN: 879e56d7b71SSascha Leib arr = me._countries.human; 880e56d7b71SSascha Leib break; 881e56d7b71SSascha Leib case BM_USERTYPE.LIKELY_BOT: 882e56d7b71SSascha Leib case BM_USERTYPE.KNOWN_BOT: 883e56d7b71SSascha Leib arr = me._countries.bot; 884e56d7b71SSascha Leib break; 885e56d7b71SSascha Leib default: 886e56d7b71SSascha Leib console.warn(`Unknown user type ${type} in function addToCountries.`); 887e56d7b71SSascha Leib } 888e56d7b71SSascha Leib 889e56d7b71SSascha Leib if (arr) { 890e56d7b71SSascha Leib let cRec = arr.find( it => it.id == iso); 891e56d7b71SSascha Leib if (!cRec) { 892e56d7b71SSascha Leib cRec = { 893e56d7b71SSascha Leib 'id': iso, 894e56d7b71SSascha Leib 'n': name, 895e56d7b71SSascha Leib 'count': 1 896e56d7b71SSascha Leib }; 897e56d7b71SSascha Leib arr.push(cRec); 898e56d7b71SSascha Leib } else { 899e56d7b71SSascha Leib cRec.count += 1; 900e56d7b71SSascha Leib } 901e56d7b71SSascha Leib } 902e56d7b71SSascha Leib }, 903e56d7b71SSascha Leib 904e56d7b71SSascha Leib /** 905e56d7b71SSascha Leib * Returns a list of countries with visit counts, sorted by visit count in descending order. 906e56d7b71SSascha Leib * 907e56d7b71SSascha Leib * @param {BM_USERTYPE} type array of types type of visitors to return. 908e56d7b71SSascha Leib * @param {number} max The maximum number of entries to return. 909e56d7b71SSascha Leib * @return {Array} A list of objects with properties 'iso' (ISO 3166-1 alpha-2 country code) and 'count' (visit count). 910e56d7b71SSascha Leib */ 911e56d7b71SSascha Leib getCountryList: function(type, max) { 912e56d7b71SSascha Leib 913e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 914e56d7b71SSascha Leib 915e56d7b71SSascha Leib // find the correct array: 916e56d7b71SSascha Leib let arr = null; 917e56d7b71SSascha Leib switch (type) { 918e56d7b71SSascha Leib 919e56d7b71SSascha Leib case 'human': 920e56d7b71SSascha Leib arr = me._countries.human; 921e56d7b71SSascha Leib break; 922e56d7b71SSascha Leib case 'bot': 923e56d7b71SSascha Leib arr = me._countries.bot; 924e56d7b71SSascha Leib break; 925e56d7b71SSascha Leib default: 926e56d7b71SSascha Leib console.warn(`Unknown user type ${type} in function getCountryList.`); 927e56d7b71SSascha Leib return; 928e56d7b71SSascha Leib } 929e56d7b71SSascha Leib 930e56d7b71SSascha Leib return me._makeTopList(arr, max); 931e56d7b71SSascha Leib }, 932e56d7b71SSascha Leib 933e56d7b71SSascha Leib /* browser and platform of human visitors */ 934e56d7b71SSascha Leib _browsers: [], 935e56d7b71SSascha Leib _platforms: [], 936e56d7b71SSascha Leib 937e56d7b71SSascha Leib addBrowserPlatform: function(visitor) { 938e56d7b71SSascha Leib //console.info('addBrowserPlatform', visitor); 939e56d7b71SSascha Leib 940e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 941e56d7b71SSascha Leib 942e56d7b71SSascha Leib // add to browsers list: 943e56d7b71SSascha Leib let browserRec = ( visitor._client ? visitor._client : {'id': 'unknown'}); 944e56d7b71SSascha Leib if (visitor._client) { 945e56d7b71SSascha Leib let bRec = me._browsers.find( it => it.id == browserRec.id); 946e56d7b71SSascha Leib if (!bRec) { 947e56d7b71SSascha Leib bRec = { 948e56d7b71SSascha Leib id: browserRec.id, 949e56d7b71SSascha Leib n: browserRec.n, 950e56d7b71SSascha Leib count: 1 951e56d7b71SSascha Leib }; 952e56d7b71SSascha Leib me._browsers.push(bRec); 953e56d7b71SSascha Leib } else { 954e56d7b71SSascha Leib bRec.count += 1; 955e56d7b71SSascha Leib } 956e56d7b71SSascha Leib } 957e56d7b71SSascha Leib 958e56d7b71SSascha Leib // add to platforms list: 959e56d7b71SSascha Leib let platformRec = ( visitor._platform ? visitor._platform : {'id': 'unknown'}); 960e56d7b71SSascha Leib if (visitor._platform) { 961e56d7b71SSascha Leib let pRec = me._platforms.find( it => it.id == platformRec.id); 962e56d7b71SSascha Leib if (!pRec) { 963e56d7b71SSascha Leib pRec = { 964e56d7b71SSascha Leib id: platformRec.id, 965e56d7b71SSascha Leib n: platformRec.n, 966e56d7b71SSascha Leib count: 1 967e56d7b71SSascha Leib }; 968e56d7b71SSascha Leib me._platforms.push(pRec); 969e56d7b71SSascha Leib } else { 970e56d7b71SSascha Leib pRec.count += 1; 971e56d7b71SSascha Leib } 972e56d7b71SSascha Leib } 973e56d7b71SSascha Leib 974e56d7b71SSascha Leib }, 975e56d7b71SSascha Leib 976e56d7b71SSascha Leib getTopBrowsers: function(max) { 977e56d7b71SSascha Leib 978e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 979e56d7b71SSascha Leib 980e56d7b71SSascha Leib return me._makeTopList(me._browsers, max); 981e56d7b71SSascha Leib }, 982e56d7b71SSascha Leib 983e56d7b71SSascha Leib getTopPlatforms: function(max) { 984e56d7b71SSascha Leib 985e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 986e56d7b71SSascha Leib 987e56d7b71SSascha Leib return me._makeTopList(me._platforms, max); 988e56d7b71SSascha Leib }, 989e56d7b71SSascha Leib 990e56d7b71SSascha Leib /* bounces are counted, not calculates: */ 991e56d7b71SSascha Leib getBounceCount: function(type) { 992e56d7b71SSascha Leib 993e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 994e56d7b71SSascha Leib var bounces = 0; 995e56d7b71SSascha Leib const list = me.groups[type]; 996e56d7b71SSascha Leib 997e56d7b71SSascha Leib list.forEach(it => { 998e56d7b71SSascha Leib bounces += (it._pageViews.length <= 1 ? 1 : 0); 999e56d7b71SSascha Leib }); 1000e56d7b71SSascha Leib 1001e56d7b71SSascha Leib return bounces; 1002e56d7b71SSascha Leib }, 1003e56d7b71SSascha Leib 1004e56d7b71SSascha Leib _ipOwners: [], 1005e56d7b71SSascha Leib 1006e56d7b71SSascha Leib /* adds a visit to the ip ranges arrays */ 1007e56d7b71SSascha Leib addToIpRanges: function(v) { 1008e56d7b71SSascha Leib //console.info('addToIpRanges', v.ip); 1009e56d7b71SSascha Leib 1010e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 1011e56d7b71SSascha Leib const ipRanges = BotMon.live.data.ipRanges; 1012e56d7b71SSascha Leib 10130edf1a56SSascha Leib // Number of IP address segments to look at: 10140edf1a56SSascha Leib const kIP4Segments = 1; 10150edf1a56SSascha Leib const kIP6Segments = 2; 10160edf1a56SSascha Leib 1017e56d7b71SSascha Leib let isp = 'null'; // default ISP id 1018e56d7b71SSascha Leib let name = 'Unknown'; // default ISP name 1019e56d7b71SSascha Leib 1020e56d7b71SSascha Leib // is there already a known IP range assigned? 1021e56d7b71SSascha Leib if (v._ipRange) { 1022e56d7b71SSascha Leib isp = v._ipRange.g; 1023e56d7b71SSascha Leib } 1024e56d7b71SSascha Leib 1025e56d7b71SSascha Leib let ispRec = me._ipOwners.find( it => it.id == isp); 1026e56d7b71SSascha Leib if (!ispRec) { 1027e56d7b71SSascha Leib ispRec = { 1028e56d7b71SSascha Leib 'id': isp, 1029e56d7b71SSascha Leib 'n': ipRanges.getOwner( isp ) || "Unknown", 1030e56d7b71SSascha Leib 'count': v._pageViews.length 1031e56d7b71SSascha Leib }; 1032e56d7b71SSascha Leib me._ipOwners.push(ispRec); 1033e56d7b71SSascha Leib } else { 1034e56d7b71SSascha Leib ispRec.count += v._pageViews.length; 1035e56d7b71SSascha Leib } 1036e56d7b71SSascha Leib }, 1037e56d7b71SSascha Leib 1038e56d7b71SSascha Leib getTopBotISPs: function(max) { 1039e56d7b71SSascha Leib 1040e56d7b71SSascha Leib const me = BotMon.live.data.analytics; 1041e56d7b71SSascha Leib 1042e56d7b71SSascha Leib return me._makeTopList(me._ipOwners, max); 1043e56d7b71SSascha Leib }, 1044e56d7b71SSascha Leib }, 1045e56d7b71SSascha Leib 1046e56d7b71SSascha Leib // information on "known bots": 1047e56d7b71SSascha Leib bots: { 1048e56d7b71SSascha Leib // loads the list of known bots from a JSON file: 1049e56d7b71SSascha Leib init: async function() { 1050e56d7b71SSascha Leib //console.info('BotMon.live.data.bots.init()'); 1051e56d7b71SSascha Leib 1052e56d7b71SSascha Leib // Load the list of known bots: 1053e56d7b71SSascha Leib BotMon.live.gui.status.showBusy("Loading known bots …"); 1054e56d7b71SSascha Leib const url = BotMon._baseDir + 'config/known-bots.json'; 1055e56d7b71SSascha Leib try { 1056e56d7b71SSascha Leib const response = await fetch(url); 1057e56d7b71SSascha Leib if (!response.ok) { 1058e56d7b71SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 1059e56d7b71SSascha Leib } 1060e56d7b71SSascha Leib 1061e56d7b71SSascha Leib this._list = await response.json(); 1062e56d7b71SSascha Leib this._ready = true; 1063e56d7b71SSascha Leib 1064e56d7b71SSascha Leib } catch (error) { 1065e56d7b71SSascha Leib BotMon.live.gui.status.setError("Error while loading the known bots file:", error.message); 1066e56d7b71SSascha Leib } finally { 1067e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1068e56d7b71SSascha Leib BotMon.live.data._dispatch('bots') 1069e56d7b71SSascha Leib } 1070e56d7b71SSascha Leib }, 1071e56d7b71SSascha Leib 1072e56d7b71SSascha Leib // returns bot info if the clientId matches a known bot, null otherwise: 1073e56d7b71SSascha Leib match: function(agent) { 1074e56d7b71SSascha Leib //console.info('BotMon.live.data.bots.match(',agent,')'); 1075e56d7b71SSascha Leib 1076e56d7b71SSascha Leib const BotList = BotMon.live.data.bots._list; 1077e56d7b71SSascha Leib 1078e56d7b71SSascha Leib // default is: not found! 1079e56d7b71SSascha Leib let botInfo = null; 1080e56d7b71SSascha Leib 1081e56d7b71SSascha Leib if (!agent) return null; 1082e56d7b71SSascha Leib 1083e56d7b71SSascha Leib // check for known bots: 1084e56d7b71SSascha Leib BotList.find(bot => { 1085e56d7b71SSascha Leib let r = false; 1086e56d7b71SSascha Leib for (let j=0; j<bot.rx.length; j++) { 1087e56d7b71SSascha Leib const rxr = agent.match(new RegExp(bot.rx[j])); 1088e56d7b71SSascha Leib if (rxr) { 1089e56d7b71SSascha Leib botInfo = { 1090e56d7b71SSascha Leib n : bot.n, 1091e56d7b71SSascha Leib id: bot.id, 1092e56d7b71SSascha Leib geo: (bot.geo ? bot.geo : null), 1093e56d7b71SSascha Leib url: bot.url, 1094e56d7b71SSascha Leib v: (rxr.length > 1 ? rxr[1] : -1) 1095e56d7b71SSascha Leib }; 1096e56d7b71SSascha Leib r = true; 1097e56d7b71SSascha Leib break; 1098e56d7b71SSascha Leib }; 1099e56d7b71SSascha Leib }; 1100e56d7b71SSascha Leib return r; 1101e56d7b71SSascha Leib }); 1102e56d7b71SSascha Leib 1103e56d7b71SSascha Leib // check for unknown bots: 1104e56d7b71SSascha Leib if (!botInfo) { 1105e56d7b71SSascha Leib const botmatch = agent.match(/([\s\d\w\-]*bot|[\s\d\w\-]*crawler|[\s\d\w\-]*spider)[\/\s;\),\\.$]/i); 1106e56d7b71SSascha Leib if(botmatch) { 1107e56d7b71SSascha Leib botInfo = {'id': ( botmatch[1] || "other_" ), 'n': "Other" + ( botmatch[1] ? " (" + botmatch[1] + ")" : "" ) , "bot": botmatch[1] }; 1108e56d7b71SSascha Leib } 1109e56d7b71SSascha Leib } 1110e56d7b71SSascha Leib 1111e56d7b71SSascha Leib //console.log("botInfo:", botInfo); 1112e56d7b71SSascha Leib return botInfo; 1113e56d7b71SSascha Leib }, 1114e56d7b71SSascha Leib 1115e56d7b71SSascha Leib 1116e56d7b71SSascha Leib // indicates if the list is loaded and ready to use: 1117e56d7b71SSascha Leib _ready: false, 1118e56d7b71SSascha Leib 1119e56d7b71SSascha Leib // the actual bot list is stored here: 1120e56d7b71SSascha Leib _list: [] 1121e56d7b71SSascha Leib }, 1122e56d7b71SSascha Leib 1123e56d7b71SSascha Leib // information on known clients (browsers): 1124e56d7b71SSascha Leib clients: { 1125e56d7b71SSascha Leib // loads the list of known clients from a JSON file: 1126e56d7b71SSascha Leib init: async function() { 1127e56d7b71SSascha Leib //console.info('BotMon.live.data.clients.init()'); 1128e56d7b71SSascha Leib 1129e56d7b71SSascha Leib // Load the list of known bots: 1130e56d7b71SSascha Leib BotMon.live.gui.status.showBusy("Loading known clients"); 1131e56d7b71SSascha Leib const url = BotMon._baseDir + 'config/known-clients.json'; 1132e56d7b71SSascha Leib try { 1133e56d7b71SSascha Leib const response = await fetch(url); 1134e56d7b71SSascha Leib if (!response.ok) { 1135e56d7b71SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 1136e56d7b71SSascha Leib } 1137e56d7b71SSascha Leib 1138e56d7b71SSascha Leib BotMon.live.data.clients._list = await response.json(); 1139e56d7b71SSascha Leib BotMon.live.data.clients._ready = true; 1140e56d7b71SSascha Leib 1141e56d7b71SSascha Leib } catch (error) { 1142e56d7b71SSascha Leib BotMon.live.gui.status.setError("Error while loading the known clients file: " + error.message); 1143e56d7b71SSascha Leib } finally { 1144e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1145e56d7b71SSascha Leib BotMon.live.data._dispatch('clients') 1146e56d7b71SSascha Leib } 1147e56d7b71SSascha Leib }, 1148e56d7b71SSascha Leib 1149e56d7b71SSascha Leib // returns bot info if the user-agent matches a known bot, null otherwise: 1150e56d7b71SSascha Leib match: function(agent) { 1151e56d7b71SSascha Leib //console.info('BotMon.live.data.clients.match(',agent,')'); 1152e56d7b71SSascha Leib 1153e56d7b71SSascha Leib let match = {"n": "Unknown", "v": -1, "id": 'null'}; 1154e56d7b71SSascha Leib 1155e56d7b71SSascha Leib if (agent) { 1156e56d7b71SSascha Leib BotMon.live.data.clients._list.find(client => { 1157e56d7b71SSascha Leib let r = false; 1158e56d7b71SSascha Leib for (let j=0; j<client.rx.length; j++) { 1159e56d7b71SSascha Leib const rxr = agent.match(new RegExp(client.rx[j])); 1160e56d7b71SSascha Leib if (rxr) { 1161e56d7b71SSascha Leib match.n = client.n; 1162e56d7b71SSascha Leib match.v = (rxr.length > 1 ? rxr[1] : -1); 1163e56d7b71SSascha Leib match.id = client.id || null; 1164e56d7b71SSascha Leib r = true; 1165e56d7b71SSascha Leib break; 1166e56d7b71SSascha Leib } 1167e56d7b71SSascha Leib } 1168e56d7b71SSascha Leib return r; 1169e56d7b71SSascha Leib }); 1170e56d7b71SSascha Leib } 1171e56d7b71SSascha Leib 1172e56d7b71SSascha Leib //console.log(match) 1173e56d7b71SSascha Leib return match; 1174e56d7b71SSascha Leib }, 1175e56d7b71SSascha Leib 1176e56d7b71SSascha Leib // return the browser name for a browser ID: 1177e56d7b71SSascha Leib getName: function(id) { 1178e56d7b71SSascha Leib const it = BotMon.live.data.clients._list.find(client => client.id == id); 1179e56d7b71SSascha Leib return ( it && it.n ? it.n : "Unknown"); //it.n; 1180e56d7b71SSascha Leib }, 1181e56d7b71SSascha Leib 1182e56d7b71SSascha Leib // indicates if the list is loaded and ready to use: 1183e56d7b71SSascha Leib _ready: false, 1184e56d7b71SSascha Leib 1185e56d7b71SSascha Leib // the actual bot list is stored here: 1186e56d7b71SSascha Leib _list: [] 1187e56d7b71SSascha Leib 1188e56d7b71SSascha Leib }, 1189e56d7b71SSascha Leib 1190e56d7b71SSascha Leib // information on known platforms (operating systems): 1191e56d7b71SSascha Leib platforms: { 1192e56d7b71SSascha Leib // loads the list of known platforms from a JSON file: 1193e56d7b71SSascha Leib init: async function() { 1194e56d7b71SSascha Leib //console.info('BotMon.live.data.platforms.init()'); 1195e56d7b71SSascha Leib 1196e56d7b71SSascha Leib // Load the list of known bots: 1197e56d7b71SSascha Leib BotMon.live.gui.status.showBusy("Loading known platforms"); 1198e56d7b71SSascha Leib const url = BotMon._baseDir + 'config/known-platforms.json'; 1199e56d7b71SSascha Leib try { 1200e56d7b71SSascha Leib const response = await fetch(url); 1201e56d7b71SSascha Leib if (!response.ok) { 1202e56d7b71SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 1203e56d7b71SSascha Leib } 1204e56d7b71SSascha Leib 1205e56d7b71SSascha Leib BotMon.live.data.platforms._list = await response.json(); 1206e56d7b71SSascha Leib BotMon.live.data.platforms._ready = true; 1207e56d7b71SSascha Leib 1208e56d7b71SSascha Leib } catch (error) { 1209e56d7b71SSascha Leib BotMon.live.gui.status.setError("Error while loading the known platforms file: " + error.message); 1210e56d7b71SSascha Leib } finally { 1211e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1212e56d7b71SSascha Leib BotMon.live.data._dispatch('platforms') 1213e56d7b71SSascha Leib } 1214e56d7b71SSascha Leib }, 1215e56d7b71SSascha Leib 1216e56d7b71SSascha Leib // returns bot info if the browser id matches a known platform: 1217e56d7b71SSascha Leib match: function(cid) { 1218e56d7b71SSascha Leib //console.info('BotMon.live.data.platforms.match(',cid,')'); 1219e56d7b71SSascha Leib 1220e56d7b71SSascha Leib let match = {"n": "Unknown", "id": 'null'}; 1221e56d7b71SSascha Leib 1222e56d7b71SSascha Leib if (cid) { 1223e56d7b71SSascha Leib BotMon.live.data.platforms._list.find(platform => { 1224e56d7b71SSascha Leib let r = false; 1225e56d7b71SSascha Leib for (let j=0; j<platform.rx.length; j++) { 1226e56d7b71SSascha Leib const rxr = cid.match(new RegExp(platform.rx[j])); 1227e56d7b71SSascha Leib if (rxr) { 1228e56d7b71SSascha Leib match.n = platform.n; 1229e56d7b71SSascha Leib match.v = (rxr.length > 1 ? rxr[1] : -1); 1230e56d7b71SSascha Leib match.id = platform.id || null; 1231e56d7b71SSascha Leib r = true; 1232e56d7b71SSascha Leib break; 1233e56d7b71SSascha Leib } 1234e56d7b71SSascha Leib } 1235e56d7b71SSascha Leib return r; 1236e56d7b71SSascha Leib }); 1237e56d7b71SSascha Leib } 1238e56d7b71SSascha Leib 1239e56d7b71SSascha Leib return match; 1240e56d7b71SSascha Leib }, 1241e56d7b71SSascha Leib 1242e56d7b71SSascha Leib // return the platform name for a given ID: 1243e56d7b71SSascha Leib getName: function(id) { 1244e56d7b71SSascha Leib const it = BotMon.live.data.platforms._list.find( pf => pf.id == id); 1245e56d7b71SSascha Leib return ( it ? it.n : 'Unknown' ); 1246e56d7b71SSascha Leib }, 1247e56d7b71SSascha Leib 1248e56d7b71SSascha Leib 1249e56d7b71SSascha Leib // indicates if the list is loaded and ready to use: 1250e56d7b71SSascha Leib _ready: false, 1251e56d7b71SSascha Leib 1252e56d7b71SSascha Leib // the actual bot list is stored here: 1253e56d7b71SSascha Leib _list: [] 1254e56d7b71SSascha Leib 1255e56d7b71SSascha Leib }, 1256e56d7b71SSascha Leib 1257e56d7b71SSascha Leib // storage and functions for the known bot IP-Ranges: 1258e56d7b71SSascha Leib ipRanges: { 1259e56d7b71SSascha Leib 1260e56d7b71SSascha Leib init: function() { 1261e56d7b71SSascha Leib //console.log('BotMon.live.data.ipRanges.init()'); 1262e56d7b71SSascha Leib // #TODO: Load from separate IP-Ranges file 1263e56d7b71SSascha Leib // load the rules file: 1264e56d7b71SSascha Leib const me = BotMon.live.data; 1265e56d7b71SSascha Leib 1266e56d7b71SSascha Leib try { 1267e56d7b71SSascha Leib BotMon.live.data._loadSettingsFile(['user-ipranges', 'known-ipranges'], 1268e56d7b71SSascha Leib (json) => { 1269e56d7b71SSascha Leib 1270e56d7b71SSascha Leib // groups can be just saved in the data structure: 1271e56d7b71SSascha Leib if (json.groups && json.groups.constructor.name == 'Array') { 1272e56d7b71SSascha Leib me.ipRanges._groups = json.groups; 1273e56d7b71SSascha Leib } 1274e56d7b71SSascha Leib 1275e56d7b71SSascha Leib // groups can be just saved in the data structure: 1276e56d7b71SSascha Leib if (json.ranges && json.ranges.constructor.name == 'Array') { 1277e56d7b71SSascha Leib json.ranges.forEach(range => { 1278e56d7b71SSascha Leib me.ipRanges.add(range); 1279e56d7b71SSascha Leib }) 1280e56d7b71SSascha Leib } 1281e56d7b71SSascha Leib 1282e56d7b71SSascha Leib // finished loading 1283e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1284e56d7b71SSascha Leib BotMon.live.data._dispatch('ipranges') 1285e56d7b71SSascha Leib }); 1286e56d7b71SSascha Leib } catch (error) { 1287e56d7b71SSascha Leib BotMon.live.gui.status.setError("Error while loading the config file: " + error.message); 1288e56d7b71SSascha Leib } 1289e56d7b71SSascha Leib }, 1290e56d7b71SSascha Leib 1291e56d7b71SSascha Leib // the actual bot list is stored here: 1292e56d7b71SSascha Leib _list: [], 1293e56d7b71SSascha Leib _groups: [], 1294e56d7b71SSascha Leib 1295e56d7b71SSascha Leib add: function(data) { 1296e56d7b71SSascha Leib //console.log('BotMon.live.data.ipRanges.add(',data,')'); 1297e56d7b71SSascha Leib 1298e56d7b71SSascha Leib const me = BotMon.live.data.ipRanges; 1299e56d7b71SSascha Leib 1300e56d7b71SSascha Leib // convert IP address to easier comparable form: 1301e56d7b71SSascha Leib const ip2Num = BotMon.t._ip2Num; 1302e56d7b71SSascha Leib 1303e56d7b71SSascha Leib let item = { 1304e56d7b71SSascha Leib 'cidr': data.from.replaceAll(/::+/g, '::') + '/' + ( data.m ? data.m : '??' ), 1305e56d7b71SSascha Leib 'from': ip2Num(data.from), 1306e56d7b71SSascha Leib 'to': ip2Num(data.to), 1307e56d7b71SSascha Leib 'm': data.m, 1308e56d7b71SSascha Leib 'g': data.g 1309e56d7b71SSascha Leib }; 1310e56d7b71SSascha Leib me._list.push(item); 1311e56d7b71SSascha Leib 1312e56d7b71SSascha Leib }, 1313e56d7b71SSascha Leib 1314e56d7b71SSascha Leib getOwner: function(gid) { 1315e56d7b71SSascha Leib 1316e56d7b71SSascha Leib const me = BotMon.live.data.ipRanges; 1317e56d7b71SSascha Leib 1318e56d7b71SSascha Leib for (let i=0; i < me._groups.length; i++) { 1319e56d7b71SSascha Leib const it = me._groups[i]; 1320e56d7b71SSascha Leib if (it.id == gid) { 1321e56d7b71SSascha Leib return it.name; 1322e56d7b71SSascha Leib } 1323e56d7b71SSascha Leib } 1324e56d7b71SSascha Leib return null; 1325e56d7b71SSascha Leib }, 1326e56d7b71SSascha Leib 1327e56d7b71SSascha Leib match: function(ip) { 1328e56d7b71SSascha Leib //console.log('BotMon.live.data.ipRanges.match(',ip,')'); 1329e56d7b71SSascha Leib 1330e56d7b71SSascha Leib const me = BotMon.live.data.ipRanges; 1331e56d7b71SSascha Leib 1332e56d7b71SSascha Leib // convert IP address to easier comparable form: 1333e56d7b71SSascha Leib const ipNum = BotMon.t._ip2Num(ip); 1334e56d7b71SSascha Leib 1335e56d7b71SSascha Leib for (let i=0; i < me._list.length; i++) { 1336e56d7b71SSascha Leib const ipRange = me._list[i]; 1337e56d7b71SSascha Leib 1338e56d7b71SSascha Leib if (ipNum >= ipRange.from && ipNum <= ipRange.to) { 1339e56d7b71SSascha Leib return ipRange; 1340e56d7b71SSascha Leib } 1341e56d7b71SSascha Leib 1342e56d7b71SSascha Leib }; 1343e56d7b71SSascha Leib return null; 1344e56d7b71SSascha Leib } 1345e56d7b71SSascha Leib }, 1346e56d7b71SSascha Leib 1347e56d7b71SSascha Leib // storage for the rules and related functions 1348e56d7b71SSascha Leib rules: { 1349e56d7b71SSascha Leib 1350e56d7b71SSascha Leib /** 1351e56d7b71SSascha Leib * Initializes the rules data. 1352e56d7b71SSascha Leib * 1353e56d7b71SSascha Leib * Loads the default config file and the user config file (if present). 1354e56d7b71SSascha Leib * The default config file is used if the user config file does not have a certain setting. 1355e56d7b71SSascha Leib * The user config file can override settings from the default config file. 1356e56d7b71SSascha Leib * 1357e56d7b71SSascha Leib * The rules are loaded from the `rules` property of the config files. 1358e56d7b71SSascha Leib * The IP ranges are loaded from the `ipRanges` property of the config files. 1359e56d7b71SSascha Leib * 1360e56d7b71SSascha Leib * If an error occurs while loading the config file, it is displayed in the status bar. 1361e56d7b71SSascha Leib * After the config file is loaded, the status bar is hidden. 1362e56d7b71SSascha Leib */ 1363e56d7b71SSascha Leib init: async function() { 1364e56d7b71SSascha Leib //console.info('BotMon.live.data.rules.init()'); 1365e56d7b71SSascha Leib 1366e56d7b71SSascha Leib // Load the list of known bots: 1367e56d7b71SSascha Leib BotMon.live.gui.status.showBusy("Loading list of rules …"); 1368e56d7b71SSascha Leib 1369e56d7b71SSascha Leib // load the rules file: 1370e56d7b71SSascha Leib const me = BotMon.live.data; 1371e56d7b71SSascha Leib 1372e56d7b71SSascha Leib try { 1373e56d7b71SSascha Leib BotMon.live.data._loadSettingsFile(['user-config', 'default-config'], 1374e56d7b71SSascha Leib (json) => { 1375e56d7b71SSascha Leib 1376e56d7b71SSascha Leib // override the threshold? 1377e56d7b71SSascha Leib if (json.threshold) me._threshold = json.threshold; 1378e56d7b71SSascha Leib 1379e56d7b71SSascha Leib // set the rules list: 1380e56d7b71SSascha Leib if (json.rules && json.rules.constructor.name == 'Array') { 1381e56d7b71SSascha Leib me.rules._rulesList = json.rules; 1382e56d7b71SSascha Leib } 1383e56d7b71SSascha Leib 1384e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1385e56d7b71SSascha Leib BotMon.live.data._dispatch('rules') 1386e56d7b71SSascha Leib } 1387e56d7b71SSascha Leib ); 1388e56d7b71SSascha Leib } catch (error) { 1389e56d7b71SSascha Leib BotMon.live.gui.status.setError("Error while loading the config file: " + error.message); 1390e56d7b71SSascha Leib } 1391e56d7b71SSascha Leib }, 1392e56d7b71SSascha Leib 1393e56d7b71SSascha Leib _rulesList: [], // list of rules to find out if a visitor is a bot 1394e56d7b71SSascha Leib _threshold: 100, // above this, it is considered a bot. 1395e56d7b71SSascha Leib 1396e56d7b71SSascha Leib // returns a descriptive text for a rule id 1397e56d7b71SSascha Leib getRuleInfo: function(ruleId) { 1398e56d7b71SSascha Leib // console.info('getRuleInfo', ruleId); 1399e56d7b71SSascha Leib 1400e56d7b71SSascha Leib // shortcut for neater code: 1401e56d7b71SSascha Leib const me = BotMon.live.data.rules; 1402e56d7b71SSascha Leib 1403e56d7b71SSascha Leib for (let i=0; i<me._rulesList.length; i++) { 1404e56d7b71SSascha Leib const rule = me._rulesList[i]; 1405e56d7b71SSascha Leib if (rule.id == ruleId) { 1406e56d7b71SSascha Leib return rule; 1407e56d7b71SSascha Leib } 1408e56d7b71SSascha Leib } 1409e56d7b71SSascha Leib return null; 1410e56d7b71SSascha Leib 1411e56d7b71SSascha Leib }, 1412e56d7b71SSascha Leib 1413e56d7b71SSascha Leib // evaluate a visitor for lkikelihood of being a bot 1414e56d7b71SSascha Leib evaluate: function(visitor) { 1415e56d7b71SSascha Leib 1416e56d7b71SSascha Leib // shortcut for neater code: 1417e56d7b71SSascha Leib const me = BotMon.live.data.rules; 1418e56d7b71SSascha Leib 1419e56d7b71SSascha Leib let r = { // evaluation result 1420e56d7b71SSascha Leib 'val': 0, 1421e56d7b71SSascha Leib 'rules': [], 1422e56d7b71SSascha Leib 'isBot': false 1423e56d7b71SSascha Leib }; 1424e56d7b71SSascha Leib 1425e56d7b71SSascha Leib for (let i=0; i<me._rulesList.length; i++) { 1426e56d7b71SSascha Leib const rule = me._rulesList[i]; 1427e56d7b71SSascha Leib const params = ( rule.params ? rule.params : [] ); 1428e56d7b71SSascha Leib 1429e56d7b71SSascha Leib if (rule.func) { // rule is calling a function 1430e56d7b71SSascha Leib if (me.func[rule.func]) { 1431e56d7b71SSascha Leib if(me.func[rule.func](visitor, ...params)) { 1432e56d7b71SSascha Leib r.val += rule.bot; 1433e56d7b71SSascha Leib r.rules.push(rule.id) 1434e56d7b71SSascha Leib } 1435e56d7b71SSascha Leib } else { 1436e56d7b71SSascha Leib //console.warn("Unknown rule function: “${rule.func}”. Ignoring rule.") 1437e56d7b71SSascha Leib } 1438e56d7b71SSascha Leib } 1439e56d7b71SSascha Leib } 1440e56d7b71SSascha Leib 1441e56d7b71SSascha Leib // is a bot? 1442e56d7b71SSascha Leib r.isBot = (r.val >= me._threshold); 1443e56d7b71SSascha Leib 1444e56d7b71SSascha Leib return r; 1445e56d7b71SSascha Leib }, 1446e56d7b71SSascha Leib 1447e56d7b71SSascha Leib // list of functions that can be called by the rules list to evaluate a visitor: 1448e56d7b71SSascha Leib func: { 1449e56d7b71SSascha Leib 1450e56d7b71SSascha Leib // check if client is on the list passed as parameter: 1451e56d7b71SSascha Leib matchesClient: function(visitor, ...clients) { 1452e56d7b71SSascha Leib 1453e56d7b71SSascha Leib const clientId = ( visitor._client ? visitor._client.id : ''); 1454e56d7b71SSascha Leib return clients.includes(clientId); 1455e56d7b71SSascha Leib }, 1456e56d7b71SSascha Leib 1457e56d7b71SSascha Leib // check if OS/Platform is one of the obsolete ones: 1458e56d7b71SSascha Leib matchesPlatform: function(visitor, ...platforms) { 1459e56d7b71SSascha Leib 1460e56d7b71SSascha Leib const pId = ( visitor._platform ? visitor._platform.id : ''); 1461e56d7b71SSascha Leib 1462e56d7b71SSascha Leib if (visitor._platform.id == null) console.log(visitor._platform); 1463e56d7b71SSascha Leib 1464e56d7b71SSascha Leib return platforms.includes(pId); 1465e56d7b71SSascha Leib }, 1466e56d7b71SSascha Leib 1467e56d7b71SSascha Leib // are there at lest num pages loaded? 1468e56d7b71SSascha Leib smallPageCount: function(visitor, num) { 1469e56d7b71SSascha Leib return (visitor._pageViews.length <= Number(num)); 1470e56d7b71SSascha Leib }, 1471e56d7b71SSascha Leib 1472e56d7b71SSascha Leib // There was no entry in a specific log file for this visitor: 1473e56d7b71SSascha Leib // note that this will also trigger the "noJavaScript" rule: 1474e56d7b71SSascha Leib noRecord: function(visitor, type) { 1475e56d7b71SSascha Leib return !visitor._seenBy.includes(type); 1476e56d7b71SSascha Leib }, 1477e56d7b71SSascha Leib 1478e56d7b71SSascha Leib // there are no referrers in any of the page visits: 1479e56d7b71SSascha Leib noReferrer: function(visitor) { 1480e56d7b71SSascha Leib 1481e56d7b71SSascha Leib let r = false; // return value 1482e56d7b71SSascha Leib for (let i = 0; i < visitor._pageViews.length; i++) { 1483e56d7b71SSascha Leib if (!visitor._pageViews[i]._ref) { 1484e56d7b71SSascha Leib r = true; 1485e56d7b71SSascha Leib break; 1486e56d7b71SSascha Leib } 1487e56d7b71SSascha Leib } 1488e56d7b71SSascha Leib return r; 1489e56d7b71SSascha Leib }, 1490e56d7b71SSascha Leib 1491e56d7b71SSascha Leib // test for specific client identifiers: 1492e56d7b71SSascha Leib /*matchesClients: function(visitor, ...list) { 1493e56d7b71SSascha Leib 1494e56d7b71SSascha Leib for (let i=0; i<list.length; i++) { 1495e56d7b71SSascha Leib if (visitor._client.id == list[i]) { 1496e56d7b71SSascha Leib return true 1497e56d7b71SSascha Leib } 1498e56d7b71SSascha Leib }; 1499e56d7b71SSascha Leib return false; 1500e56d7b71SSascha Leib },*/ 1501e56d7b71SSascha Leib 1502e56d7b71SSascha Leib // unusual combinations of Platform and Client: 1503e56d7b71SSascha Leib combinationTest: function(visitor, ...combinations) { 1504e56d7b71SSascha Leib 1505e56d7b71SSascha Leib for (let i=0; i<combinations.length; i++) { 1506e56d7b71SSascha Leib 1507e56d7b71SSascha Leib if (visitor._platform.id == combinations[i][0] 1508e56d7b71SSascha Leib && visitor._client.id == combinations[i][1]) { 1509e56d7b71SSascha Leib return true 1510e56d7b71SSascha Leib } 1511e56d7b71SSascha Leib }; 1512e56d7b71SSascha Leib 1513e56d7b71SSascha Leib return false; 1514e56d7b71SSascha Leib }, 1515e56d7b71SSascha Leib 1516e56d7b71SSascha Leib // is the IP address from a known bot network? 1517e56d7b71SSascha Leib fromKnownBotIP: function(visitor) { 1518e56d7b71SSascha Leib //console.info('fromKnownBotIP()', visitor.ip); 1519e56d7b71SSascha Leib 1520e56d7b71SSascha Leib const ipInfo = BotMon.live.data.ipRanges.match(visitor.ip); 1521e56d7b71SSascha Leib 1522e56d7b71SSascha Leib if (ipInfo) { 1523e56d7b71SSascha Leib visitor._ipInKnownBotRange = true; 1524e56d7b71SSascha Leib visitor._ipRange = ipInfo; 1525e56d7b71SSascha Leib } 1526e56d7b71SSascha Leib 1527e56d7b71SSascha Leib return (ipInfo !== null); 1528e56d7b71SSascha Leib }, 1529e56d7b71SSascha Leib 1530e56d7b71SSascha Leib // is the page language mentioned in the client's accepted languages? 1531e56d7b71SSascha Leib // the parameter holds an array of exceptions, i.e. page languages that should be ignored. 1532e56d7b71SSascha Leib matchLang: function(visitor, ...exceptions) { 1533e56d7b71SSascha Leib 1534e56d7b71SSascha Leib if (visitor.lang && visitor.accept && exceptions.indexOf(visitor.lang) < 0) { 1535e56d7b71SSascha Leib return (visitor.accept.split(',').indexOf(visitor.lang) < 0); 1536e56d7b71SSascha Leib } 1537e56d7b71SSascha Leib return false; 1538e56d7b71SSascha Leib }, 1539e56d7b71SSascha Leib 1540e56d7b71SSascha Leib // the "Accept language" header contains certain entries: 1541e56d7b71SSascha Leib clientAccepts: function(visitor, ...languages) { 1542e56d7b71SSascha Leib //console.info('clientAccepts', visitor.accept, languages); 1543e56d7b71SSascha Leib 1544e56d7b71SSascha Leib if (visitor.accept && languages) {; 1545e56d7b71SSascha Leib return ( visitor.accept.split(',').filter(lang => languages.includes(lang)).length > 0 ); 1546e56d7b71SSascha Leib } 1547e56d7b71SSascha Leib return false; 1548e56d7b71SSascha Leib }, 1549e56d7b71SSascha Leib 1550e56d7b71SSascha Leib // Is there an accept-language field defined at all? 1551e56d7b71SSascha Leib noAcceptLang: function(visitor) { 1552e56d7b71SSascha Leib 1553e56d7b71SSascha Leib if (!visitor.accept || visitor.accept.length <= 0) { // no accept-languages header 1554e56d7b71SSascha Leib return true; 1555e56d7b71SSascha Leib } 1556e56d7b71SSascha Leib // TODO: parametrize this! 1557e56d7b71SSascha Leib return false; 1558e56d7b71SSascha Leib }, 1559e56d7b71SSascha Leib // At least x page views were recorded, but they come within less than y seconds 1560e56d7b71SSascha Leib loadSpeed: function(visitor, minItems, maxTime) { 1561e56d7b71SSascha Leib 1562e56d7b71SSascha Leib if (visitor._pageViews.length >= minItems) { 1563e56d7b71SSascha Leib //console.log('loadSpeed', visitor._pageViews.length, minItems, maxTime); 1564e56d7b71SSascha Leib 1565e56d7b71SSascha Leib const pvArr = visitor._pageViews.map(pv => pv._lastSeen).sort(); 1566e56d7b71SSascha Leib 1567e56d7b71SSascha Leib let totalTime = 0; 1568e56d7b71SSascha Leib for (let i=1; i < pvArr.length; i++) { 1569e56d7b71SSascha Leib totalTime += (pvArr[i] - pvArr[i-1]); 1570e56d7b71SSascha Leib } 1571e56d7b71SSascha Leib 1572e56d7b71SSascha Leib //console.log(' ', totalTime , Math.round(totalTime / (pvArr.length * 1000)), (( totalTime / pvArr.length ) <= maxTime * 1000), visitor.ip); 1573e56d7b71SSascha Leib 1574e56d7b71SSascha Leib return (( totalTime / pvArr.length ) <= maxTime * 1000); 1575e56d7b71SSascha Leib } 1576e56d7b71SSascha Leib }, 1577e56d7b71SSascha Leib 1578e56d7b71SSascha Leib // Country code matches one of those in the list: 1579e56d7b71SSascha Leib matchesCountry: function(visitor, ...countries) { 1580e56d7b71SSascha Leib 1581e56d7b71SSascha Leib // ingore if geoloc is not set or unknown: 1582e56d7b71SSascha Leib if (visitor.geo) { 1583e56d7b71SSascha Leib return (countries.indexOf(visitor.geo) >= 0); 1584e56d7b71SSascha Leib } 1585e56d7b71SSascha Leib return false; 1586e56d7b71SSascha Leib }, 1587e56d7b71SSascha Leib 1588e56d7b71SSascha Leib // Country does not match one of the given codes. 1589e56d7b71SSascha Leib notFromCountry: function(visitor, ...countries) { 1590e56d7b71SSascha Leib 1591e56d7b71SSascha Leib // ingore if geoloc is not set or unknown: 1592e56d7b71SSascha Leib if (visitor.geo && visitor.geo !== 'ZZ') { 1593e56d7b71SSascha Leib return (countries.indexOf(visitor.geo) < 0); 1594e56d7b71SSascha Leib } 1595e56d7b71SSascha Leib return false; 1596e56d7b71SSascha Leib } 1597e56d7b71SSascha Leib } 1598e56d7b71SSascha Leib }, 1599e56d7b71SSascha Leib 1600e56d7b71SSascha Leib /** 1601e56d7b71SSascha Leib * Loads a settings file from the specified list of filenames. 1602e56d7b71SSascha Leib * If the file is successfully loaded, it will call the callback function 1603e56d7b71SSascha Leib * with the loaded JSON data. 1604e56d7b71SSascha Leib * If no file can be loaded, it will display an error message. 1605e56d7b71SSascha Leib * 1606e56d7b71SSascha Leib * @param {string[]} fns - list of filenames to load 1607e56d7b71SSascha Leib * @param {function} callback - function to call with the loaded JSON data 1608e56d7b71SSascha Leib */ 1609e56d7b71SSascha Leib _loadSettingsFile: async function(fns, callback) { 1610e56d7b71SSascha Leib //console.info('BotMon.live.data._loadSettingsFile()', fns); 1611e56d7b71SSascha Leib 1612e56d7b71SSascha Leib const kJsonExt = '.json'; 1613e56d7b71SSascha Leib let loaded = false; // if successfully loaded file 1614e56d7b71SSascha Leib 1615e56d7b71SSascha Leib for (let i=0; i<fns.length; i++) { 1616e56d7b71SSascha Leib const filename = fns[i] +kJsonExt; 1617e56d7b71SSascha Leib try { 1618e56d7b71SSascha Leib const response = await fetch(DOKU_BASE + 'lib/plugins/botmon/config/' + filename); 1619e56d7b71SSascha Leib if (!response.ok) { 1620e56d7b71SSascha Leib continue; 1621e56d7b71SSascha Leib } else { 1622e56d7b71SSascha Leib loaded = true; 1623e56d7b71SSascha Leib } 1624e56d7b71SSascha Leib const json = await response.json(); 1625e56d7b71SSascha Leib if (callback && typeof callback === 'function') { 1626e56d7b71SSascha Leib callback(json); 1627e56d7b71SSascha Leib } 1628e56d7b71SSascha Leib break; 1629e56d7b71SSascha Leib } catch (e) { 1630e56d7b71SSascha Leib BotMon.live.gui.status.setError("Error while loading the config file: " + filename); 1631e56d7b71SSascha Leib } 1632e56d7b71SSascha Leib } 1633e56d7b71SSascha Leib 1634e56d7b71SSascha Leib if (!loaded) { 1635e56d7b71SSascha Leib BotMon.live.gui.status.setError("Could not load a config file."); 1636e56d7b71SSascha Leib } 1637e56d7b71SSascha Leib }, 1638e56d7b71SSascha Leib 1639e56d7b71SSascha Leib /** 1640e56d7b71SSascha Leib * Loads a log file (server, page load, or ticker) and parses it. 1641e56d7b71SSascha Leib * @param {String} type - the type of the log file to load (srv, log, or tck) 1642e56d7b71SSascha Leib * @param {Function} [onLoaded] - an optional callback function to call after loading is finished. 1643e56d7b71SSascha Leib */ 1644e56d7b71SSascha Leib loadLogFile: async function(type, onLoaded = undefined) { 1645e56d7b71SSascha Leib //console.info('BotMon.live.data.loadLogFile(',type,')'); 1646e56d7b71SSascha Leib 1647e56d7b71SSascha Leib let typeName = ''; 1648e56d7b71SSascha Leib let columns = []; 1649e56d7b71SSascha Leib 1650e56d7b71SSascha Leib switch (type) { 1651e56d7b71SSascha Leib case "srv": 1652e56d7b71SSascha Leib typeName = "Server"; 1653e56d7b71SSascha Leib columns = ['ts','ip','pg','id','typ','usr','agent','ref','lang','accept','geo']; 1654e56d7b71SSascha Leib break; 1655e56d7b71SSascha Leib case "log": 1656e56d7b71SSascha Leib typeName = "Page load"; 1657e56d7b71SSascha Leib columns = ['ts','ip','pg','id','usr','lt','ref','agent']; 1658e56d7b71SSascha Leib break; 1659e56d7b71SSascha Leib case "tck": 1660e56d7b71SSascha Leib typeName = "Ticker"; 1661e56d7b71SSascha Leib columns = ['ts','ip','pg','id','agent']; 1662e56d7b71SSascha Leib break; 1663e56d7b71SSascha Leib default: 1664e56d7b71SSascha Leib console.warn(`Unknown log type ${type}.`); 1665e56d7b71SSascha Leib return; 1666e56d7b71SSascha Leib } 1667e56d7b71SSascha Leib 1668e56d7b71SSascha Leib // Show the busy indicator and set the visible status: 1669e56d7b71SSascha Leib BotMon.live.gui.status.showBusy(`Loading ${typeName} log file …`); 1670e56d7b71SSascha Leib 1671e56d7b71SSascha Leib // compose the URL from which to load: 1672e56d7b71SSascha Leib const url = BotMon._baseDir + `logs/${BotMon._datestr}.${type}.txt`; 1673e56d7b71SSascha Leib //console.log("Loading:",url); 1674e56d7b71SSascha Leib 1675e56d7b71SSascha Leib // fetch the data: 1676e56d7b71SSascha Leib try { 1677e56d7b71SSascha Leib const response = await fetch(url); 1678e56d7b71SSascha Leib if (!response.ok) { 1679e56d7b71SSascha Leib 1680e56d7b71SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 1681e56d7b71SSascha Leib 1682e56d7b71SSascha Leib } else { 1683e56d7b71SSascha Leib 1684e56d7b71SSascha Leib // parse the data: 1685e56d7b71SSascha Leib const logtxt = await response.text(); 1686e56d7b71SSascha Leib if (logtxt.length <= 0) { 1687e56d7b71SSascha Leib throw new Error(`Empty log file ${url}.`); 1688e56d7b71SSascha Leib } 1689e56d7b71SSascha Leib 1690e56d7b71SSascha Leib logtxt.split('\n').forEach((line) => { 1691e56d7b71SSascha Leib if (line.trim() === '') return; // skip empty lines 1692e56d7b71SSascha Leib const cols = line.split('\t'); 1693e56d7b71SSascha Leib 1694e56d7b71SSascha Leib // assign the columns to an object: 1695e56d7b71SSascha Leib const data = {}; 1696e56d7b71SSascha Leib cols.forEach( (colVal,i) => { 16970edf1a56SSascha Leib const colName = columns[i] || `col${i}`; 1698e56d7b71SSascha Leib const colValue = (colName == 'ts' ? new Date(colVal) : colVal.trim()); 1699e56d7b71SSascha Leib data[colName] = colValue; 1700e56d7b71SSascha Leib }); 1701e56d7b71SSascha Leib 1702e56d7b71SSascha Leib // register the visit in the model: 1703e56d7b71SSascha Leib switch(type) { 1704e56d7b71SSascha Leib case BM_LOGTYPE.SERVER: 1705e56d7b71SSascha Leib BotMon.live.data.model.registerVisit(data, type); 1706e56d7b71SSascha Leib break; 1707e56d7b71SSascha Leib case BM_LOGTYPE.CLIENT: 1708e56d7b71SSascha Leib data.typ = 'js'; 1709e56d7b71SSascha Leib BotMon.live.data.model.updateVisit(data); 1710e56d7b71SSascha Leib break; 1711e56d7b71SSascha Leib case BM_LOGTYPE.TICKER: 1712e56d7b71SSascha Leib data.typ = 'js'; 1713e56d7b71SSascha Leib BotMon.live.data.model.updateTicks(data); 1714e56d7b71SSascha Leib break; 1715e56d7b71SSascha Leib default: 1716e56d7b71SSascha Leib console.warn(`Unknown log type ${type}.`); 1717e56d7b71SSascha Leib return; 1718e56d7b71SSascha Leib } 1719e56d7b71SSascha Leib }); 1720e56d7b71SSascha Leib } 1721e56d7b71SSascha Leib 1722e56d7b71SSascha Leib } catch (error) { 1723e56d7b71SSascha Leib BotMon.live.gui.status.setError(`Error while loading the ${typeName} log file: ${error.message} – data may be incomplete.`); 1724e56d7b71SSascha Leib } finally { 1725e56d7b71SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1726e56d7b71SSascha Leib if (onLoaded) { 1727e56d7b71SSascha Leib onLoaded(); // callback after loading is finished. 1728e56d7b71SSascha Leib } 1729e56d7b71SSascha Leib } 1730e56d7b71SSascha Leib } 1731e56d7b71SSascha Leib }, 1732e56d7b71SSascha Leib 1733e56d7b71SSascha Leib gui: { 1734e56d7b71SSascha Leib init: function() { 1735*e2bc8f6fSSascha Leib //console.log('BotMon.live.gui.init()'); 1736*e2bc8f6fSSascha Leib 17370edf1a56SSascha Leib // init sub-objects: 17380edf1a56SSascha Leib BotMon.t._callInit(this); 1739e56d7b71SSascha Leib }, 1740e56d7b71SSascha Leib 1741*e2bc8f6fSSascha Leib tabs: { 1742*e2bc8f6fSSascha Leib init: function() { 1743*e2bc8f6fSSascha Leib //console.log('BotMon.live.gui.tabs.init()'); 1744*e2bc8f6fSSascha Leib 1745*e2bc8f6fSSascha Leib /* find and add all existing tabs */ 1746*e2bc8f6fSSascha Leib document.querySelectorAll('#botmon__admin *[role=tablist]') 1747*e2bc8f6fSSascha Leib .forEach((tablist) => { 1748*e2bc8f6fSSascha Leib tablist.querySelectorAll('*[role=tab]') 1749*e2bc8f6fSSascha Leib .forEach( t => t.addEventListener('click', this._onTabClick) ) 1750*e2bc8f6fSSascha Leib }); 1751*e2bc8f6fSSascha Leib }, 1752*e2bc8f6fSSascha Leib 1753*e2bc8f6fSSascha Leib /* callback for tab click */ 1754*e2bc8f6fSSascha Leib _onTabClick: function(e) { 1755*e2bc8f6fSSascha Leib //console.log('BotMon.live.gui.tabs._onTabClick()'); 1756*e2bc8f6fSSascha Leib 1757*e2bc8f6fSSascha Leib /* reusable constants: */ 1758*e2bc8f6fSSascha Leib const kAriaSelected = 'aria-selected'; 1759*e2bc8f6fSSascha Leib const kAriaControls = 'aria-controls'; 1760*e2bc8f6fSSascha Leib const kTrue = 'true'; 1761*e2bc8f6fSSascha Leib const kFalse = 'false'; 1762*e2bc8f6fSSascha Leib const kHidden = 'hidden'; 1763*e2bc8f6fSSascha Leib 1764*e2bc8f6fSSascha Leib /* cancel default action */ 1765*e2bc8f6fSSascha Leib e.preventDefault(); 1766*e2bc8f6fSSascha Leib 1767*e2bc8f6fSSascha Leib /* if the active tab is clicked, do nothing: */ 1768*e2bc8f6fSSascha Leib let selState = this.getAttribute(kAriaSelected); 1769*e2bc8f6fSSascha Leib if ( selState && selState == kTrue ) { 1770*e2bc8f6fSSascha Leib return; 1771*e2bc8f6fSSascha Leib } 1772*e2bc8f6fSSascha Leib 1773*e2bc8f6fSSascha Leib /* find the active tab element: */ 1774*e2bc8f6fSSascha Leib var aItem = null; 1775*e2bc8f6fSSascha Leib let tablist = this.parentNode; 1776*e2bc8f6fSSascha Leib while (tablist.getAttribute('role') !== 'tablist') { 1777*e2bc8f6fSSascha Leib tablist = tablist.parentNode; 1778*e2bc8f6fSSascha Leib } 1779*e2bc8f6fSSascha Leib 1780*e2bc8f6fSSascha Leib if (tablist.getAttribute('role') == 'tablist') { 1781*e2bc8f6fSSascha Leib let lis = tablist.querySelectorAll('*[role=tab]'); 1782*e2bc8f6fSSascha Leib lis.forEach( (it) => { 1783*e2bc8f6fSSascha Leib let selected = it.getAttribute(kAriaSelected); 1784*e2bc8f6fSSascha Leib if ( selected && selected == kTrue ) { 1785*e2bc8f6fSSascha Leib aItem = it; 1786*e2bc8f6fSSascha Leib } 1787*e2bc8f6fSSascha Leib }); 1788*e2bc8f6fSSascha Leib } 1789*e2bc8f6fSSascha Leib 1790*e2bc8f6fSSascha Leib /* swap the active states: */ 1791*e2bc8f6fSSascha Leib this.setAttribute(kAriaSelected, kTrue); 1792*e2bc8f6fSSascha Leib if (aItem) { 1793*e2bc8f6fSSascha Leib aItem.setAttribute(kAriaSelected, kFalse); 1794*e2bc8f6fSSascha Leib let aId = aItem.getAttribute(kAriaControls); 1795*e2bc8f6fSSascha Leib let aObj = document.getElementById(aId); 1796*e2bc8f6fSSascha Leib if (aObj) aObj.hidden = true; 1797*e2bc8f6fSSascha Leib } 1798*e2bc8f6fSSascha Leib 1799*e2bc8f6fSSascha Leib /* show the new panel: */ 1800*e2bc8f6fSSascha Leib let nId = this.getAttribute(kAriaControls); 1801*e2bc8f6fSSascha Leib let nObj = document.getElementById(nId); 1802*e2bc8f6fSSascha Leib if (nObj) nObj.hidden = false; 1803*e2bc8f6fSSascha Leib } 1804*e2bc8f6fSSascha Leib }, 18050edf1a56SSascha Leib 1806e56d7b71SSascha Leib /* The Overview / web metrics section of the live tab */ 1807e56d7b71SSascha Leib overview: { 1808e56d7b71SSascha Leib /** 1809e56d7b71SSascha Leib * Populates the overview part of the today tab with the analytics data. 1810e56d7b71SSascha Leib * 1811e56d7b71SSascha Leib * @method make 1812e56d7b71SSascha Leib * @memberof BotMon.live.gui.overview 1813e56d7b71SSascha Leib */ 1814e56d7b71SSascha Leib make: function() { 1815e56d7b71SSascha Leib 1816e56d7b71SSascha Leib const data = BotMon.live.data.analytics.data; 1817e56d7b71SSascha Leib 1818e56d7b71SSascha Leib const maxItemsPerList = 5; // how many list items to show? 1819e56d7b71SSascha Leib 1820e56d7b71SSascha Leib // shortcut for neater code: 1821e56d7b71SSascha Leib const makeElement = BotMon.t._makeElement; 1822e56d7b71SSascha Leib 1823e56d7b71SSascha Leib const botsVsHumans = document.getElementById('botmon__today__botsvshumans'); 1824e56d7b71SSascha Leib if (botsVsHumans) { 1825e56d7b71SSascha Leib botsVsHumans.appendChild(makeElement('dt', {}, "Page views")); 1826e56d7b71SSascha Leib 1827e56d7b71SSascha Leib for (let i = 0; i <= 5; i++) { 1828e56d7b71SSascha Leib const dd = makeElement('dd'); 1829e56d7b71SSascha Leib let title = ''; 1830e56d7b71SSascha Leib let value = ''; 1831e56d7b71SSascha Leib switch(i) { 1832e56d7b71SSascha Leib case 0: 1833e56d7b71SSascha Leib title = "Known bots:"; 1834e56d7b71SSascha Leib value = data.bots.known; 1835e56d7b71SSascha Leib break; 1836e56d7b71SSascha Leib case 1: 1837e56d7b71SSascha Leib title = "Suspected bots:"; 1838e56d7b71SSascha Leib value = data.bots.suspected; 1839e56d7b71SSascha Leib break; 1840e56d7b71SSascha Leib case 2: 1841e56d7b71SSascha Leib title = "Probably humans:"; 1842e56d7b71SSascha Leib value = data.bots.human; 1843e56d7b71SSascha Leib break; 1844e56d7b71SSascha Leib case 3: 1845e56d7b71SSascha Leib title = "Registered users:"; 1846e56d7b71SSascha Leib value = data.bots.users; 1847e56d7b71SSascha Leib break; 1848e56d7b71SSascha Leib case 4: 1849e56d7b71SSascha Leib title = "Total:"; 1850e56d7b71SSascha Leib value = data.totalPageViews; 1851e56d7b71SSascha Leib break; 1852e56d7b71SSascha Leib case 5: 1853e56d7b71SSascha Leib title = "Bots-humans ratio:"; 1854e56d7b71SSascha Leib value = BotMon.t._getRatio(data.bots.suspected + data.bots.known, data.bots.users + data.bots.human, 100); 1855e56d7b71SSascha Leib break; 1856e56d7b71SSascha Leib default: 1857e56d7b71SSascha Leib console.warn(`Unknown list type ${i}.`); 1858e56d7b71SSascha Leib } 1859e56d7b71SSascha Leib dd.appendChild(makeElement('span', {}, title)); 1860e56d7b71SSascha Leib dd.appendChild(makeElement('strong', {}, value)); 1861e56d7b71SSascha Leib botsVsHumans.appendChild(dd); 1862e56d7b71SSascha Leib } 1863e56d7b71SSascha Leib } 1864e56d7b71SSascha Leib 1865e56d7b71SSascha Leib // update known bots list: 1866e56d7b71SSascha Leib const botElement = document.getElementById('botmon__botslist'); /* Known bots */ 1867e56d7b71SSascha Leib if (botElement) { 1868e56d7b71SSascha Leib botElement.appendChild(makeElement('dt', {}, `Top known bots`)); 1869e56d7b71SSascha Leib 1870e56d7b71SSascha Leib let botList = BotMon.live.data.analytics.getTopBots(maxItemsPerList); 1871e56d7b71SSascha Leib botList.forEach( (botInfo) => { 1872e56d7b71SSascha Leib const bli = makeElement('dd'); 1873e56d7b71SSascha Leib bli.appendChild(makeElement('span', {'class': 'has_icon bot bot_' + botInfo.id }, botInfo.name)); 1874e56d7b71SSascha Leib bli.appendChild(makeElement('span', {'class': 'count' }, botInfo.count)); 1875e56d7b71SSascha Leib botElement.append(bli) 1876e56d7b71SSascha Leib }); 1877e56d7b71SSascha Leib } 1878e56d7b71SSascha Leib 1879e56d7b71SSascha Leib // update the suspected bot IP ranges list: 1880e56d7b71SSascha Leib const botIps = document.getElementById('botmon__botips'); 1881e56d7b71SSascha Leib if (botIps) { 1882e56d7b71SSascha Leib botIps.appendChild(makeElement('dt', {}, "Top bot ISPs")); 1883e56d7b71SSascha Leib 1884e56d7b71SSascha Leib const ispList = BotMon.live.data.analytics.getTopBotISPs(5); 1885e56d7b71SSascha Leib console.log(ispList); 1886e56d7b71SSascha Leib ispList.forEach( (netInfo) => { 1887e56d7b71SSascha Leib const li = makeElement('dd'); 1888e56d7b71SSascha Leib li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip_' + netInfo.id }, netInfo.name)); 1889e56d7b71SSascha Leib li.appendChild(makeElement('span', {'class': 'count' }, netInfo.count)); 1890e56d7b71SSascha Leib botIps.append(li) 1891e56d7b71SSascha Leib }); 1892e56d7b71SSascha Leib } 1893e56d7b71SSascha Leib 1894e56d7b71SSascha Leib // update the top bot countries list: 1895e56d7b71SSascha Leib const botCountries = document.getElementById('botmon__botcountries'); 1896e56d7b71SSascha Leib if (botCountries) { 1897e56d7b71SSascha Leib botCountries.appendChild(makeElement('dt', {}, `Top bot Countries`)); 1898e56d7b71SSascha Leib const countryList = BotMon.live.data.analytics.getCountryList('bot', 5); 1899e56d7b71SSascha Leib countryList.forEach( (cInfo) => { 1900e56d7b71SSascha Leib const cLi = makeElement('dd'); 1901e56d7b71SSascha Leib cLi.appendChild(makeElement('span', {'class': 'has_icon country ctry_' + cInfo.id.toLowerCase() }, cInfo.name)); 1902e56d7b71SSascha Leib cLi.appendChild(makeElement('span', {'class': 'count' }, cInfo.count)); 1903e56d7b71SSascha Leib botCountries.appendChild(cLi); 1904e56d7b71SSascha Leib }); 1905e56d7b71SSascha Leib } 1906e56d7b71SSascha Leib 1907e56d7b71SSascha Leib // update the webmetrics overview: 1908e56d7b71SSascha Leib const wmoverview = document.getElementById('botmon__today__wm_overview'); 1909e56d7b71SSascha Leib if (wmoverview) { 1910e56d7b71SSascha Leib 1911e56d7b71SSascha Leib const humanVisits = BotMon.live.data.analytics.groups.users.length + BotMon.live.data.analytics.groups.humans.length; 1912e56d7b71SSascha Leib const bounceRate = Math.round(100 * (BotMon.live.data.analytics.getBounceCount('users') + BotMon.live.data.analytics.getBounceCount('humans')) / humanVisits); 1913e56d7b71SSascha Leib 1914e56d7b71SSascha Leib wmoverview.appendChild(makeElement('dt', {}, "Humans’ metrics")); 1915e56d7b71SSascha Leib for (let i = 0; i <= 4; i++) { 1916e56d7b71SSascha Leib const dd = makeElement('dd'); 1917e56d7b71SSascha Leib let title = ''; 1918e56d7b71SSascha Leib let value = ''; 1919e56d7b71SSascha Leib switch(i) { 1920e56d7b71SSascha Leib case 0: 1921e56d7b71SSascha Leib title = "Page views by registered users:"; 1922e56d7b71SSascha Leib value = data.bots.users; 1923e56d7b71SSascha Leib break; 1924e56d7b71SSascha Leib case 1: 1925e56d7b71SSascha Leib title = "Page views by “probably humans”:"; 1926e56d7b71SSascha Leib value = data.bots.human; 1927e56d7b71SSascha Leib break; 1928e56d7b71SSascha Leib case 2: 1929e56d7b71SSascha Leib title = "Total human page views:"; 1930e56d7b71SSascha Leib value = data.bots.users + data.bots.human; 1931e56d7b71SSascha Leib break; 1932e56d7b71SSascha Leib case 3: 1933e56d7b71SSascha Leib title = "Total human visits:"; 1934e56d7b71SSascha Leib value = humanVisits; 1935e56d7b71SSascha Leib break; 1936e56d7b71SSascha Leib case 4: 1937e56d7b71SSascha Leib title = "Humans’ bounce rate:"; 1938e56d7b71SSascha Leib value = bounceRate + '%'; 1939e56d7b71SSascha Leib break; 1940e56d7b71SSascha Leib default: 1941e56d7b71SSascha Leib console.warn(`Unknown list type ${i}.`); 1942e56d7b71SSascha Leib } 1943e56d7b71SSascha Leib dd.appendChild(makeElement('span', {}, title)); 1944e56d7b71SSascha Leib dd.appendChild(makeElement('strong', {}, value)); 1945e56d7b71SSascha Leib wmoverview.appendChild(dd); 1946e56d7b71SSascha Leib } 1947e56d7b71SSascha Leib } 1948e56d7b71SSascha Leib 1949e56d7b71SSascha Leib // update the webmetrics clients list: 1950e56d7b71SSascha Leib const wmclients = document.getElementById('botmon__today__wm_clients'); 1951e56d7b71SSascha Leib if (wmclients) { 1952e56d7b71SSascha Leib 1953e56d7b71SSascha Leib wmclients.appendChild(makeElement('dt', {}, "Browsers")); 1954e56d7b71SSascha Leib 1955e56d7b71SSascha Leib const clientList = BotMon.live.data.analytics.getTopBrowsers(maxItemsPerList); 1956e56d7b71SSascha Leib if (clientList) { 1957e56d7b71SSascha Leib clientList.forEach( (cInfo) => { 1958e56d7b71SSascha Leib const cDd = makeElement('dd'); 1959e56d7b71SSascha Leib cDd.appendChild(makeElement('span', {'class': 'has_icon client cl_' + cInfo.id }, ( cInfo.name ? cInfo.name : cInfo.id))); 1960e56d7b71SSascha Leib cDd.appendChild(makeElement('span', { 1961e56d7b71SSascha Leib 'class': 'count', 1962e56d7b71SSascha Leib 'title': cInfo.count + " page views" 1963e56d7b71SSascha Leib }, cInfo.pct.toFixed(1) + '%')); 1964e56d7b71SSascha Leib wmclients.appendChild(cDd); 1965e56d7b71SSascha Leib }); 1966e56d7b71SSascha Leib } 1967e56d7b71SSascha Leib } 1968e56d7b71SSascha Leib 1969e56d7b71SSascha Leib // update the webmetrics platforms list: 1970e56d7b71SSascha Leib const wmplatforms = document.getElementById('botmon__today__wm_platforms'); 1971e56d7b71SSascha Leib if (wmplatforms) { 1972e56d7b71SSascha Leib 1973e56d7b71SSascha Leib wmplatforms.appendChild(makeElement('dt', {}, "Platforms")); 1974e56d7b71SSascha Leib 1975e56d7b71SSascha Leib const pfList = BotMon.live.data.analytics.getTopPlatforms(maxItemsPerList); 1976e56d7b71SSascha Leib if (pfList) { 1977e56d7b71SSascha Leib pfList.forEach( (pInfo) => { 1978e56d7b71SSascha Leib const pDd = makeElement('dd'); 1979e56d7b71SSascha Leib pDd.appendChild(makeElement('span', {'class': 'has_icon platform pf_' + pInfo.id }, ( pInfo.name ? pInfo.name : pInfo.id))); 1980e56d7b71SSascha Leib pDd.appendChild(makeElement('span', { 1981e56d7b71SSascha Leib 'class': 'count', 1982e56d7b71SSascha Leib 'title': pInfo.count + " page views" 1983e56d7b71SSascha Leib }, pInfo.pct.toFixed(1) + '%')); 1984e56d7b71SSascha Leib wmplatforms.appendChild(pDd); 1985e56d7b71SSascha Leib }); 1986e56d7b71SSascha Leib } 1987e56d7b71SSascha Leib } 1988e56d7b71SSascha Leib 1989e56d7b71SSascha Leib // update the top bot countries list: 1990e56d7b71SSascha Leib const usrCountries = document.getElementById('botmon__today__wm_countries'); 1991e56d7b71SSascha Leib if (usrCountries) { 1992e56d7b71SSascha Leib usrCountries.appendChild(makeElement('dt', {}, `Top visitor Countries:`)); 1993e56d7b71SSascha Leib const usrCtryList = BotMon.live.data.analytics.getCountryList('human', 5); 1994e56d7b71SSascha Leib usrCtryList.forEach( (cInfo) => { 1995e56d7b71SSascha Leib const cLi = makeElement('dd'); 1996e56d7b71SSascha Leib cLi.appendChild(makeElement('span', {'class': 'has_icon country ctry_' + cInfo.id.toLowerCase() }, cInfo.name)); 1997e56d7b71SSascha Leib cLi.appendChild(makeElement('span', {'class': 'count' }, cInfo.count)); 1998e56d7b71SSascha Leib usrCountries.appendChild(cLi); 1999e56d7b71SSascha Leib }); 2000e56d7b71SSascha Leib } 2001e56d7b71SSascha Leib 2002e56d7b71SSascha Leib // update the top pages; 2003e56d7b71SSascha Leib const wmpages = document.getElementById('botmon__today__wm_pages'); 2004e56d7b71SSascha Leib if (wmpages) { 2005e56d7b71SSascha Leib 2006e56d7b71SSascha Leib wmpages.appendChild(makeElement('dt', {}, "Top pages")); 2007e56d7b71SSascha Leib 2008e56d7b71SSascha Leib const pgList = BotMon.live.data.analytics.getTopPages(maxItemsPerList); 2009e56d7b71SSascha Leib if (pgList) { 2010e56d7b71SSascha Leib pgList.forEach( (pgInfo) => { 2011e56d7b71SSascha Leib const pgDd = makeElement('dd'); 2012e56d7b71SSascha Leib pgDd.appendChild(makeElement('a', { 2013e56d7b71SSascha Leib 'class': 'page_icon', 2014e56d7b71SSascha Leib 'href': DOKU_BASE + 'doku.php?id=' + encodeURIComponent(pgInfo.id), 2015e56d7b71SSascha Leib 'target': 'preview', 2016e56d7b71SSascha Leib 'title': "PageID: " + pgInfo.id 2017e56d7b71SSascha Leib }, pgInfo.id)); 2018e56d7b71SSascha Leib pgDd.appendChild(makeElement('span', { 2019e56d7b71SSascha Leib 'class': 'count', 2020e56d7b71SSascha Leib 'title': pgInfo.count + " page views" 2021e56d7b71SSascha Leib }, pgInfo.count)); 2022e56d7b71SSascha Leib wmpages.appendChild(pgDd); 2023e56d7b71SSascha Leib }); 2024e56d7b71SSascha Leib } 2025e56d7b71SSascha Leib } 2026e56d7b71SSascha Leib 2027e56d7b71SSascha Leib // update the top referrers; 2028e56d7b71SSascha Leib const wmreferers = document.getElementById('botmon__today__wm_referers'); 2029e56d7b71SSascha Leib if (wmreferers) { 2030e56d7b71SSascha Leib 2031e56d7b71SSascha Leib wmreferers.appendChild(makeElement('dt', {}, "Referers")); 2032e56d7b71SSascha Leib 2033e56d7b71SSascha Leib const refList = BotMon.live.data.analytics.getTopReferers(maxItemsPerList); 2034e56d7b71SSascha Leib if (refList) { 2035e56d7b71SSascha Leib refList.forEach( (rInfo) => { 2036e56d7b71SSascha Leib const rDd = makeElement('dd'); 2037e56d7b71SSascha Leib rDd.appendChild(makeElement('span', {'class': 'has_icon referer ref_' + rInfo.id }, rInfo.name)); 2038e56d7b71SSascha Leib rDd.appendChild(makeElement('span', { 2039e56d7b71SSascha Leib 'class': 'count', 2040e56d7b71SSascha Leib 'title': rInfo.count + " references" 2041e56d7b71SSascha Leib }, rInfo.pct.toFixed(1) + '%')); 2042e56d7b71SSascha Leib wmreferers.appendChild(rDd); 2043e56d7b71SSascha Leib }); 2044e56d7b71SSascha Leib } 2045e56d7b71SSascha Leib } 2046e56d7b71SSascha Leib } 2047e56d7b71SSascha Leib }, 2048e56d7b71SSascha Leib 2049e56d7b71SSascha Leib status: { 2050e56d7b71SSascha Leib setText: function(txt) { 2051e56d7b71SSascha Leib const el = document.getElementById('botmon__today__status'); 2052e56d7b71SSascha Leib if (el && BotMon.live.gui.status._errorCount <= 0) { 2053e56d7b71SSascha Leib el.innerText = txt; 2054e56d7b71SSascha Leib } 2055e56d7b71SSascha Leib }, 2056e56d7b71SSascha Leib 2057e56d7b71SSascha Leib setTitle: function(html) { 2058e56d7b71SSascha Leib const el = document.getElementById('botmon__today__title'); 2059e56d7b71SSascha Leib if (el) { 2060e56d7b71SSascha Leib el.innerHTML = html; 2061e56d7b71SSascha Leib } 2062e56d7b71SSascha Leib }, 2063e56d7b71SSascha Leib 2064e56d7b71SSascha Leib setError: function(txt) { 2065e56d7b71SSascha Leib console.error(txt); 2066e56d7b71SSascha Leib BotMon.live.gui.status._errorCount += 1; 2067e56d7b71SSascha Leib const el = document.getElementById('botmon__today__status'); 2068e56d7b71SSascha Leib if (el) { 2069e56d7b71SSascha Leib el.innerText = "Data may be incomplete."; 2070e56d7b71SSascha Leib el.classList.add('error'); 2071e56d7b71SSascha Leib } 2072e56d7b71SSascha Leib }, 2073e56d7b71SSascha Leib _errorCount: 0, 2074e56d7b71SSascha Leib 2075e56d7b71SSascha Leib showBusy: function(txt = null) { 2076e56d7b71SSascha Leib BotMon.live.gui.status._busyCount += 1; 2077e56d7b71SSascha Leib const el = document.getElementById('botmon__today__busy'); 2078e56d7b71SSascha Leib if (el) { 2079e56d7b71SSascha Leib el.style.display = 'inline-block'; 2080e56d7b71SSascha Leib } 2081e56d7b71SSascha Leib if (txt) BotMon.live.gui.status.setText(txt); 2082e56d7b71SSascha Leib }, 2083e56d7b71SSascha Leib _busyCount: 0, 2084e56d7b71SSascha Leib 2085e56d7b71SSascha Leib hideBusy: function(txt = null) { 2086e56d7b71SSascha Leib const el = document.getElementById('botmon__today__busy'); 2087e56d7b71SSascha Leib BotMon.live.gui.status._busyCount -= 1; 2088e56d7b71SSascha Leib if (BotMon.live.gui.status._busyCount <= 0) { 2089e56d7b71SSascha Leib if (el) el.style.display = 'none'; 2090e56d7b71SSascha Leib if (txt) BotMon.live.gui.status.setText(txt); 2091e56d7b71SSascha Leib } 2092e56d7b71SSascha Leib } 2093e56d7b71SSascha Leib }, 2094e56d7b71SSascha Leib 2095e56d7b71SSascha Leib lists: { 2096e56d7b71SSascha Leib init: function() { 2097e56d7b71SSascha Leib 2098e56d7b71SSascha Leib // function shortcut: 2099e56d7b71SSascha Leib const makeElement = BotMon.t._makeElement; 2100e56d7b71SSascha Leib 2101e56d7b71SSascha Leib const parent = document.getElementById('botmon__today__visitorlists'); 2102e56d7b71SSascha Leib if (parent) { 2103e56d7b71SSascha Leib 2104e56d7b71SSascha Leib for (let i=0; i < 4; i++) { 2105e56d7b71SSascha Leib 2106e56d7b71SSascha Leib // change the id and title by number: 2107e56d7b71SSascha Leib let listTitle = ''; 2108e56d7b71SSascha Leib let listId = ''; 2109e56d7b71SSascha Leib let infolink = null; 2110e56d7b71SSascha Leib switch (i) { 2111e56d7b71SSascha Leib case 0: 2112e56d7b71SSascha Leib listTitle = "Registered users"; 2113e56d7b71SSascha Leib listId = 'users'; 2114e56d7b71SSascha Leib break; 2115e56d7b71SSascha Leib case 1: 2116e56d7b71SSascha Leib listTitle = "Probably humans"; 2117e56d7b71SSascha Leib listId = 'humans'; 2118e56d7b71SSascha Leib break; 2119e56d7b71SSascha Leib case 2: 2120e56d7b71SSascha Leib listTitle = "Suspected bots"; 2121e56d7b71SSascha Leib listId = 'suspectedBots'; 2122e56d7b71SSascha Leib infolink = 'https://leib.be/sascha/projects/dokuwiki/botmon/info/suspected_bots'; 2123e56d7b71SSascha Leib break; 2124e56d7b71SSascha Leib case 3: 2125e56d7b71SSascha Leib listTitle = "Known bots"; 2126e56d7b71SSascha Leib listId = 'knownBots'; 2127e56d7b71SSascha Leib infolink = 'https://leib.be/sascha/projects/dokuwiki/botmon/info/known_bots'; 2128e56d7b71SSascha Leib break; 2129e56d7b71SSascha Leib default: 2130e56d7b71SSascha Leib console.warn('Unknown list number.'); 2131e56d7b71SSascha Leib } 2132e56d7b71SSascha Leib 2133e56d7b71SSascha Leib const details = makeElement('details', { 2134e56d7b71SSascha Leib 'data-group': listId, 2135e56d7b71SSascha Leib 'data-loaded': false 2136e56d7b71SSascha Leib }); 2137e56d7b71SSascha Leib const title = details.appendChild(makeElement('summary')); 2138e56d7b71SSascha Leib title.appendChild(makeElement('span', {'class': 'title'}, listTitle)); 2139e56d7b71SSascha Leib if (infolink) { 2140e56d7b71SSascha Leib title.appendChild(makeElement('a', { 2141e56d7b71SSascha Leib 'class': 'ext_info', 2142e56d7b71SSascha Leib 'target': '_blank', 2143e56d7b71SSascha Leib 'href': infolink, 2144e56d7b71SSascha Leib 'title': "More information" 2145e56d7b71SSascha Leib }, "Info")); 2146e56d7b71SSascha Leib } 2147e56d7b71SSascha Leib details.addEventListener("toggle", this._onDetailsToggle); 2148e56d7b71SSascha Leib 2149e56d7b71SSascha Leib parent.appendChild(details); 2150e56d7b71SSascha Leib 2151e56d7b71SSascha Leib } 2152e56d7b71SSascha Leib } 2153e56d7b71SSascha Leib }, 2154e56d7b71SSascha Leib 2155e56d7b71SSascha Leib _onDetailsToggle: function(e) { 2156e56d7b71SSascha Leib //console.info('BotMon.live.gui.lists._onDetailsToggle()'); 2157e56d7b71SSascha Leib 2158e56d7b71SSascha Leib const target = e.target; 2159e56d7b71SSascha Leib 2160e56d7b71SSascha Leib if (target.getAttribute('data-loaded') == 'false') { // only if not loaded yet 2161e56d7b71SSascha Leib target.setAttribute('data-loaded', 'loading'); 2162e56d7b71SSascha Leib 2163e56d7b71SSascha Leib const fillType = target.getAttribute('data-group'); 2164e56d7b71SSascha Leib const fillList = BotMon.live.data.analytics.groups[fillType]; 2165e56d7b71SSascha Leib if (fillList && fillList.length > 0) { 2166e56d7b71SSascha Leib 2167e56d7b71SSascha Leib const ul = BotMon.t._makeElement('ul'); 2168e56d7b71SSascha Leib 2169e56d7b71SSascha Leib fillList.forEach( (it) => { 2170e56d7b71SSascha Leib ul.appendChild(BotMon.live.gui.lists._makeVisitorItem(it, fillType)); 2171e56d7b71SSascha Leib }); 2172e56d7b71SSascha Leib 2173e56d7b71SSascha Leib target.appendChild(ul); 2174e56d7b71SSascha Leib target.setAttribute('data-loaded', 'true'); 2175e56d7b71SSascha Leib } else { 2176e56d7b71SSascha Leib target.setAttribute('data-loaded', 'false'); 2177e56d7b71SSascha Leib } 2178e56d7b71SSascha Leib 2179e56d7b71SSascha Leib } 2180e56d7b71SSascha Leib }, 2181e56d7b71SSascha Leib 2182e56d7b71SSascha Leib _makeVisitorItem: function(data, type) { 2183e56d7b71SSascha Leib 2184e56d7b71SSascha Leib // shortcut for neater code: 2185e56d7b71SSascha Leib const make = BotMon.t._makeElement; 2186e56d7b71SSascha Leib 2187e56d7b71SSascha Leib let ipType = ( data.ip.indexOf(':') >= 0 ? '6' : '4' ); 2188e56d7b71SSascha Leib if (data.ip == '127.0.0.1' || data.ip == '::1' ) ipType = '0'; 2189e56d7b71SSascha Leib 2190e56d7b71SSascha Leib const platformName = (data._platform ? data._platform.n : 'Unknown'); 2191e56d7b71SSascha Leib const clientName = (data._client ? data._client.n: 'Unknown'); 2192e56d7b71SSascha Leib 2193e56d7b71SSascha Leib const sumClass = ( !data._seenBy || data._seenBy.indexOf(BM_LOGTYPE.SERVER) < 0 ? 'noServer' : 'hasServer'); 2194e56d7b71SSascha Leib 2195e56d7b71SSascha Leib const li = make('li'); // root list item 2196e56d7b71SSascha Leib const details = make('details'); 2197e56d7b71SSascha Leib const summary = make('summary', { 2198e56d7b71SSascha Leib 'class': sumClass 2199e56d7b71SSascha Leib }); 2200e56d7b71SSascha Leib details.appendChild(summary); 2201e56d7b71SSascha Leib 2202e56d7b71SSascha Leib const span1 = make('span'); /* left-hand group */ 2203e56d7b71SSascha Leib 2204e56d7b71SSascha Leib if (data._type !== BM_USERTYPE.KNOWN_BOT) { /* No platform/client for bots */ 2205e56d7b71SSascha Leib span1.appendChild(make('span', { /* Platform */ 2206e56d7b71SSascha Leib 'class': 'icon_only platform pf_' + (data._platform ? data._platform.id : 'unknown'), 2207e56d7b71SSascha Leib 'title': "Platform: " + platformName 2208e56d7b71SSascha Leib }, platformName)); 2209e56d7b71SSascha Leib 2210e56d7b71SSascha Leib span1.appendChild(make('span', { /* Client */ 2211e56d7b71SSascha Leib 'class': 'icon_only client client cl_' + (data._client ? data._client.id : 'unknown'), 2212e56d7b71SSascha Leib 'title': "Client: " + clientName 2213e56d7b71SSascha Leib }, clientName)); 2214e56d7b71SSascha Leib } 2215e56d7b71SSascha Leib 2216e56d7b71SSascha Leib // identifier: 2217e56d7b71SSascha Leib if (data._type == BM_USERTYPE.KNOWN_BOT) { /* Bot only */ 2218e56d7b71SSascha Leib 2219e56d7b71SSascha Leib const botName = ( data._bot && data._bot.n ? data._bot.n : "Unknown"); 2220e56d7b71SSascha Leib span1.appendChild(make('span', { /* Bot */ 2221e56d7b71SSascha Leib 'class': 'has_icon bot bot_' + (data._bot ? data._bot.id : 'unknown'), 2222e56d7b71SSascha Leib 'title': "Bot: " + botName 2223e56d7b71SSascha Leib }, botName)); 2224e56d7b71SSascha Leib 2225e56d7b71SSascha Leib } else if (data._type == BM_USERTYPE.KNOWN_USER) { /* User only */ 2226e56d7b71SSascha Leib 2227e56d7b71SSascha Leib span1.appendChild(make('span', { /* User */ 2228e56d7b71SSascha Leib 'class': 'has_icon user_known', 2229e56d7b71SSascha Leib 'title': "User: " + data.usr 2230e56d7b71SSascha Leib }, data.usr)); 2231e56d7b71SSascha Leib 2232e56d7b71SSascha Leib } else { /* others */ 2233e56d7b71SSascha Leib 2234e56d7b71SSascha Leib 2235e56d7b71SSascha Leib /*span1.appendChild(make('span', { // IP-Address 2236e56d7b71SSascha Leib 'class': 'has_icon ipaddr ip' + ipType, 2237e56d7b71SSascha Leib 'title': "IP-Address: " + data.ip 2238e56d7b71SSascha Leib }, data.ip));*/ 2239e56d7b71SSascha Leib 2240e56d7b71SSascha Leib span1.appendChild(make('span', { /* Internal ID */ 2241e56d7b71SSascha Leib 'class': 'has_icon session typ_' + data.typ, 2242e56d7b71SSascha Leib 'title': "ID: " + data.id 2243e56d7b71SSascha Leib }, data.id)); 2244e56d7b71SSascha Leib } 2245e56d7b71SSascha Leib 2246e56d7b71SSascha Leib // country flag: 2247e56d7b71SSascha Leib if (data.geo && data.geo !== 'ZZ') { 2248e56d7b71SSascha Leib span1.appendChild(make('span', { 2249e56d7b71SSascha Leib 'class': 'icon_only country ctry_' + data.geo.toLowerCase(), 2250e56d7b71SSascha Leib 'data-ctry': data.geo, 2251e56d7b71SSascha Leib 'title': "Country: " + ( data._country || "Unknown") 2252e56d7b71SSascha Leib }, ( data._country || "Unknown") )); 2253e56d7b71SSascha Leib } 2254e56d7b71SSascha Leib 2255e56d7b71SSascha Leib // referer icons: 2256e56d7b71SSascha Leib if ((data._type == BM_USERTYPE.PROBABLY_HUMAN || data._type == BM_USERTYPE.LIKELY_BOT) && data.ref) { 2257e56d7b71SSascha Leib const refInfo = BotMon.live.data.analytics.getRefererInfo(data.ref); 2258e56d7b71SSascha Leib span1.appendChild(make('span', { 2259e56d7b71SSascha Leib 'class': 'icon_only referer ref_' + refInfo.id, 2260e56d7b71SSascha Leib 'title': "Referer: " + data.ref 2261e56d7b71SSascha Leib }, refInfo.n)); 2262e56d7b71SSascha Leib } 2263e56d7b71SSascha Leib 2264e56d7b71SSascha Leib summary.appendChild(span1); 2265e56d7b71SSascha Leib const span2 = make('span'); /* right-hand group */ 2266e56d7b71SSascha Leib 2267e56d7b71SSascha Leib span2.appendChild(make('span', { /* first-seen */ 2268e56d7b71SSascha Leib 'class': 'has_iconfirst-seen', 2269e56d7b71SSascha Leib 'title': "First seen: " + data._firstSeen.toLocaleString() + " UTC" 2270e56d7b71SSascha Leib }, BotMon.t._formatTime(data._firstSeen))); 2271e56d7b71SSascha Leib 2272e56d7b71SSascha Leib span2.appendChild(make('span', { /* page views */ 2273e56d7b71SSascha Leib 'class': 'has_icon pageviews', 2274e56d7b71SSascha Leib 'title': data._pageViews.length + " page view(s)" 2275e56d7b71SSascha Leib }, data._pageViews.length)); 2276e56d7b71SSascha Leib 2277e56d7b71SSascha Leib summary.appendChild(span2); 2278e56d7b71SSascha Leib 2279e56d7b71SSascha Leib // add details expandable section: 2280e56d7b71SSascha Leib details.appendChild(BotMon.live.gui.lists._makeVisitorDetails(data, type)); 2281e56d7b71SSascha Leib 2282e56d7b71SSascha Leib li.appendChild(details); 2283e56d7b71SSascha Leib return li; 2284e56d7b71SSascha Leib }, 2285e56d7b71SSascha Leib 2286e56d7b71SSascha Leib _makeVisitorDetails: function(data, type) { 2287e56d7b71SSascha Leib 2288e56d7b71SSascha Leib // shortcut for neater code: 2289e56d7b71SSascha Leib const make = BotMon.t._makeElement; 2290e56d7b71SSascha Leib 2291e56d7b71SSascha Leib let ipType = ( data.ip.indexOf(':') >= 0 ? '6' : '4' ); 2292e56d7b71SSascha Leib if (data.ip == '127.0.0.1' || data.ip == '::1' ) ipType = '0'; 2293e56d7b71SSascha Leib const platformName = (data._platform ? data._platform.n : 'Unknown'); 2294e56d7b71SSascha Leib const clientName = (data._client ? data._client.n: 'Unknown'); 2295e56d7b71SSascha Leib 2296e56d7b71SSascha Leib const dl = make('dl', {'class': 'visitor_details'}); 2297e56d7b71SSascha Leib 2298e56d7b71SSascha Leib if (data._type == BM_USERTYPE.KNOWN_BOT) { 2299e56d7b71SSascha Leib 2300e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Bot name:")); /* bot info */ 2301e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'icon_only bot bot_' + (data._bot ? data._bot.id : 'unknown')}, 2302e56d7b71SSascha Leib (data._bot ? data._bot.n : 'Unknown'))); 2303e56d7b71SSascha Leib 2304e56d7b71SSascha Leib if (data._bot && data._bot.url) { 2305e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Bot info:")); /* bot info */ 2306e56d7b71SSascha Leib const botInfoDd = dl.appendChild(make('dd')); 2307e56d7b71SSascha Leib botInfoDd.appendChild(make('a', { 2308e56d7b71SSascha Leib 'href': data._bot.url, 2309e56d7b71SSascha Leib 'target': '_blank' 2310e56d7b71SSascha Leib }, data._bot.url)); /* bot info link*/ 2311e56d7b71SSascha Leib 2312e56d7b71SSascha Leib } 2313e56d7b71SSascha Leib 2314e56d7b71SSascha Leib } else { /* not for bots */ 2315e56d7b71SSascha Leib 2316e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Client:")); /* client */ 2317e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'has_icon client cl_' + (data._client ? data._client.id : 'unknown')}, 2318e56d7b71SSascha Leib clientName + ( data._client.v > 0 ? ' (' + data._client.v + ')' : '' ) )); 2319e56d7b71SSascha Leib 2320e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Platform:")); /* platform */ 2321e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'has_icon platform pf_' + (data._platform ? data._platform.id : 'unknown')}, 2322e56d7b71SSascha Leib platformName + ( data._platform.v > 0 ? ' (' + data._platform.v + ')' : '' ) )); 2323e56d7b71SSascha Leib 2324e56d7b71SSascha Leib /*dl.appendChild(make('dt', {}, "ID:")); 2325e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'has_icon ip' + data.typ}, data.id));*/ 2326e56d7b71SSascha Leib } 2327e56d7b71SSascha Leib 2328e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "IP-Address:")); 2329e56d7b71SSascha Leib const ipItem = make('dd', {'class': 'has_icon ipaddr ip' + ipType}); 2330e56d7b71SSascha Leib ipItem.appendChild(make('span', {'class': 'address'} , data.ip)); 2331e56d7b71SSascha Leib ipItem.appendChild(make('a', { 2332e56d7b71SSascha Leib 'class': 'icon_only extlink ipinfo', 2333e56d7b71SSascha Leib 'href': `https://ipinfo.io/${encodeURIComponent(data.ip)}`, 2334e56d7b71SSascha Leib 'target': 'ipinfo', 2335e56d7b71SSascha Leib 'title': "View this address on IPInfo.io" 2336e56d7b71SSascha Leib } , "DNS Info")); 2337e56d7b71SSascha Leib ipItem.appendChild(make('a', { 2338e56d7b71SSascha Leib 'class': 'icon_only extlink abuseipdb', 2339e56d7b71SSascha Leib 'href': `https://www.abuseipdb.com/check/${encodeURIComponent(data.ip)}`, 2340e56d7b71SSascha Leib 'target': 'abuseipdb', 2341e56d7b71SSascha Leib 'title': "Check this address on AbuseIPDB.com" 2342e56d7b71SSascha Leib } , "Check on AbuseIPDB")); 2343e56d7b71SSascha Leib dl.appendChild(ipItem); 2344e56d7b71SSascha Leib 2345e56d7b71SSascha Leib if (Math.abs(data._lastSeen - data._firstSeen) < 100) { 2346e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Seen:")); 2347e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'seen'}, data._firstSeen.toLocaleString())); 2348e56d7b71SSascha Leib } else { 2349e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "First seen:")); 2350e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'firstSeen'}, data._firstSeen.toLocaleString())); 2351e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Last seen:")); 2352e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'lastSeen'}, data._lastSeen.toLocaleString())); 2353e56d7b71SSascha Leib } 2354e56d7b71SSascha Leib 2355e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "User-Agent:")); 2356e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'agent'}, data.agent)); 2357e56d7b71SSascha Leib 2358e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Languages:")); 2359e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'langs'}, ` [${data.accept}]`)); 2360e56d7b71SSascha Leib 2361e56d7b71SSascha Leib if (data.geo && data.geo !=='') { 2362e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Location:")); 2363e56d7b71SSascha Leib dl.appendChild(make('dd', { 2364e56d7b71SSascha Leib 'class': 'has_icon country ctry_' + data.geo.toLowerCase(), 2365e56d7b71SSascha Leib 'data-ctry': data.geo, 2366e56d7b71SSascha Leib 'title': "Country: " + data._country 2367e56d7b71SSascha Leib }, data._country + ' (' + data.geo + ')')); 2368e56d7b71SSascha Leib } 2369e56d7b71SSascha Leib 2370e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Session ID:")); 2371e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'has_icon session typ_' + data.typ}, data.id)); 2372e56d7b71SSascha Leib 2373e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Seen by:")); 2374e56d7b71SSascha Leib dl.appendChild(make('dd', undefined, data._seenBy.join(', ') )); 2375e56d7b71SSascha Leib 2376e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Visited pages:")); 2377e56d7b71SSascha Leib const pagesDd = make('dd', {'class': 'pages'}); 2378e56d7b71SSascha Leib const pageList = make('ul'); 2379e56d7b71SSascha Leib 2380e56d7b71SSascha Leib /* list all page views */ 2381e56d7b71SSascha Leib data._pageViews.sort( (a, b) => a._firstSeen - b._firstSeen ); 2382e56d7b71SSascha Leib data._pageViews.forEach( (page) => { 2383e56d7b71SSascha Leib pageList.appendChild(BotMon.live.gui.lists._makePageViewItem(page)); 2384e56d7b71SSascha Leib }); 2385e56d7b71SSascha Leib pagesDd.appendChild(pageList); 2386e56d7b71SSascha Leib dl.appendChild(pagesDd); 2387e56d7b71SSascha Leib 2388e56d7b71SSascha Leib /* bot evaluation rating */ 2389e56d7b71SSascha Leib if (data._type !== BM_USERTYPE.KNOWN_BOT && data._type !== BM_USERTYPE.KNOWN_USER) { 2390e56d7b71SSascha Leib dl.appendChild(make('dt', undefined, "Bot rating:")); 2391e56d7b71SSascha Leib dl.appendChild(make('dd', {'class': 'bot-rating'}, ( data._botVal ? data._botVal : '–' ) + ' (of ' + BotMon.live.data.rules._threshold + ')')); 2392e56d7b71SSascha Leib 2393e56d7b71SSascha Leib /* add bot evaluation details: */ 2394e56d7b71SSascha Leib if (data._eval) { 2395e56d7b71SSascha Leib dl.appendChild(make('dt', {}, "Bot evaluation:")); 2396e56d7b71SSascha Leib const evalDd = make('dd', {'class': 'eval'}); 2397e56d7b71SSascha Leib const testList = make('ul'); 2398e56d7b71SSascha Leib data._eval.forEach( test => { 2399e56d7b71SSascha Leib 2400e56d7b71SSascha Leib const tObj = BotMon.live.data.rules.getRuleInfo(test); 2401e56d7b71SSascha Leib let tDesc = tObj ? tObj.desc : test; 2402e56d7b71SSascha Leib 2403e56d7b71SSascha Leib // special case for Bot IP range test: 2404e56d7b71SSascha Leib if (tObj.func == 'fromKnownBotIP') { 2405e56d7b71SSascha Leib const rangeInfo = BotMon.live.data.ipRanges.match(data.ip); 2406e56d7b71SSascha Leib if (rangeInfo) { 2407e56d7b71SSascha Leib const owner = BotMon.live.data.ipRanges.getOwner(rangeInfo.g); 2408e56d7b71SSascha Leib tDesc += ' (range: “' + rangeInfo.cidr + '”, ' + owner + ')'; 2409e56d7b71SSascha Leib } 2410e56d7b71SSascha Leib } 2411e56d7b71SSascha Leib 2412e56d7b71SSascha Leib // create the entry field 2413e56d7b71SSascha Leib const tstLi = make('li'); 2414e56d7b71SSascha Leib tstLi.appendChild(make('span', { 2415e56d7b71SSascha Leib 'data-testid': test 2416e56d7b71SSascha Leib }, tDesc)); 2417e56d7b71SSascha Leib tstLi.appendChild(make('span', {}, ( tObj ? tObj.bot : '—') )); 2418e56d7b71SSascha Leib testList.appendChild(tstLi); 2419e56d7b71SSascha Leib }); 2420e56d7b71SSascha Leib 2421e56d7b71SSascha Leib // add total row 2422e56d7b71SSascha Leib const tst2Li = make('li', { 2423e56d7b71SSascha Leib 'class': 'total' 2424e56d7b71SSascha Leib }); 2425e56d7b71SSascha Leib /*tst2Li.appendChild(make('span', {}, "Total:")); 2426e56d7b71SSascha Leib tst2Li.appendChild(make('span', {}, data._botVal)); 2427e56d7b71SSascha Leib testList.appendChild(tst2Li);*/ 2428e56d7b71SSascha Leib 2429e56d7b71SSascha Leib evalDd.appendChild(testList); 2430e56d7b71SSascha Leib dl.appendChild(evalDd); 2431e56d7b71SSascha Leib } 2432e56d7b71SSascha Leib } 2433e56d7b71SSascha Leib // return the element to add to the UI: 2434e56d7b71SSascha Leib return dl; 2435e56d7b71SSascha Leib }, 2436e56d7b71SSascha Leib 2437e56d7b71SSascha Leib // make a page view item: 2438e56d7b71SSascha Leib _makePageViewItem: function(page) { 2439e56d7b71SSascha Leib //console.log("makePageViewItem:",page); 2440e56d7b71SSascha Leib 2441e56d7b71SSascha Leib // shortcut for neater code: 2442e56d7b71SSascha Leib const make = BotMon.t._makeElement; 2443e56d7b71SSascha Leib 2444e56d7b71SSascha Leib // the actual list item: 2445e56d7b71SSascha Leib const pgLi = make('li'); 2446e56d7b71SSascha Leib 2447e56d7b71SSascha Leib const row1 = make('div', {'class': 'row'}); 2448e56d7b71SSascha Leib 2449e56d7b71SSascha Leib row1.appendChild(make('a', { // page id is the left group 2450e56d7b71SSascha Leib 'href': DOKU_BASE + 'doku.php?id=' + encodeURIComponent(page.pg), 2451e56d7b71SSascha Leib 'target': 'preview', 2452e56d7b71SSascha Leib 'hreflang': page.lang, 2453e56d7b71SSascha Leib 'title': "PageID: " + page.pg 2454e56d7b71SSascha Leib }, page.pg)); /* DW Page ID */ 2455e56d7b71SSascha Leib 2456e56d7b71SSascha Leib // get the time difference: 2457e56d7b71SSascha Leib row1.appendChild(make('span', { 2458e56d7b71SSascha Leib 'class': 'first-seen', 2459e56d7b71SSascha Leib 'title': "First visited: " + page._firstSeen.toLocaleString() + " UTC" 2460e56d7b71SSascha Leib }, BotMon.t._formatTime(page._firstSeen))); 2461e56d7b71SSascha Leib 2462e56d7b71SSascha Leib pgLi.appendChild(row1); 2463e56d7b71SSascha Leib 2464e56d7b71SSascha Leib /* LINE 2 */ 2465e56d7b71SSascha Leib 2466e56d7b71SSascha Leib const row2 = make('div', {'class': 'row'}); 2467e56d7b71SSascha Leib 2468e56d7b71SSascha Leib // page referrer: 2469e56d7b71SSascha Leib if (page._ref) { 2470e56d7b71SSascha Leib row2.appendChild(make('span', { 2471e56d7b71SSascha Leib 'class': 'referer', 2472e56d7b71SSascha Leib 'title': "Referrer: " + page._ref.href 2473e56d7b71SSascha Leib }, page._ref.hostname)); 2474e56d7b71SSascha Leib } else { 2475e56d7b71SSascha Leib row2.appendChild(make('span', { 2476e56d7b71SSascha Leib 'class': 'referer' 2477e56d7b71SSascha Leib }, "No referer")); 2478e56d7b71SSascha Leib } 2479e56d7b71SSascha Leib 2480e56d7b71SSascha Leib // visit duration: 2481e56d7b71SSascha Leib let visitTimeStr = "Bounce"; 2482e56d7b71SSascha Leib const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime(); 2483e56d7b71SSascha Leib if (visitDuration > 0) { 2484e56d7b71SSascha Leib visitTimeStr = Math.floor(visitDuration / 1000) + "s"; 2485e56d7b71SSascha Leib } 2486e56d7b71SSascha Leib const tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen); 2487e56d7b71SSascha Leib if (tDiff) { 2488e56d7b71SSascha Leib row2.appendChild(make('span', {'class': 'visit-length', 'title': 'Last seen: ' + page._lastSeen.toLocaleString()}, tDiff)); 2489e56d7b71SSascha Leib } else { 2490e56d7b71SSascha Leib row2.appendChild(make('span', { 2491e56d7b71SSascha Leib 'class': 'bounce', 2492e56d7b71SSascha Leib 'title': "Visitor bounced"}, "Bounce")); 2493e56d7b71SSascha Leib } 2494e56d7b71SSascha Leib 2495e56d7b71SSascha Leib pgLi.appendChild(row2); 2496e56d7b71SSascha Leib 2497e56d7b71SSascha Leib return pgLi; 2498e56d7b71SSascha Leib } 2499e56d7b71SSascha Leib } 2500e56d7b71SSascha Leib } 2501e56d7b71SSascha Leib}; 2502e56d7b71SSascha Leib 2503e56d7b71SSascha Leib/* launch only if the BotMon admin panel is open: */ 2504e56d7b71SSascha Leibif (document.getElementById('botmon__admin')) { 2505e56d7b71SSascha Leib BotMon.init(); 2506e56d7b71SSascha Leib}