143d9de6bSSascha Leib"use strict"; 27bd08c30SSascha Leib/* DokuWiki BotMon Plugin Script file */ 35526d629SSascha Leib/* 06.09.2025 - 0.2.0 - beta */ 45526d629SSascha Leib/* Author: Sascha Leib <ad@hominem.info> */ 5f125bc8dSSascha Leib 6f4417fdeSSascha Leib// enumeration of user types: 7f4417fdeSSascha Leibconst BM_USERTYPE = Object.freeze({ 8f4417fdeSSascha Leib 'UNKNOWN': 'unknown', 9f4417fdeSSascha Leib 'KNOWN_USER': 'user', 10f4417fdeSSascha Leib 'HUMAN': 'human', 11f4417fdeSSascha Leib 'LIKELY_BOT': 'likely_bot', 12f4417fdeSSascha Leib 'KNOWN_BOT': 'known_bot' 13f4417fdeSSascha Leib}); 14f4417fdeSSascha Leib 15f4417fdeSSascha Leib/* BotMon root object */ 167bd08c30SSascha Leibconst BotMon = { 17f125bc8dSSascha Leib 18f125bc8dSSascha Leib init: function() { 1993a5b18bSSascha Leib //console.info('BotMon.init()'); 20f125bc8dSSascha Leib 21f125bc8dSSascha Leib // find the plugin basedir: 22f125bc8dSSascha Leib this._baseDir = document.currentScript.src.substring(0, document.currentScript.src.indexOf('/exe/')) 237bd08c30SSascha Leib + '/plugins/botmon/'; 24f125bc8dSSascha Leib 25f125bc8dSSascha Leib // read the page language from the DOM: 26f125bc8dSSascha Leib this._lang = document.getRootNode().documentElement.lang || this._lang; 27f125bc8dSSascha Leib 28f125bc8dSSascha Leib // get the time offset: 297bd08c30SSascha Leib this._timeDiff = BotMon.t._getTimeOffset(); 30f125bc8dSSascha Leib 31f125bc8dSSascha Leib // init the sub-objects: 327bd08c30SSascha Leib BotMon.t._callInit(this); 33f125bc8dSSascha Leib }, 34f125bc8dSSascha Leib 35f125bc8dSSascha Leib _baseDir: null, 36f125bc8dSSascha Leib _lang: 'en', 37f125bc8dSSascha Leib _today: (new Date()).toISOString().slice(0, 10), 38f125bc8dSSascha Leib _timeDiff: '', 39f125bc8dSSascha Leib 40f125bc8dSSascha Leib /* internal tools */ 41f125bc8dSSascha Leib t: { 42f125bc8dSSascha Leib /* helper function to call inits of sub-objects */ 43f125bc8dSSascha Leib _callInit: function(obj) { 447bd08c30SSascha Leib //console.info('BotMon.t._callInit(obj=',obj,')'); 45f125bc8dSSascha Leib 46f125bc8dSSascha Leib /* call init / _init on each sub-object: */ 47f125bc8dSSascha Leib Object.keys(obj).forEach( (key,i) => { 48f125bc8dSSascha Leib const sub = obj[key]; 49f125bc8dSSascha Leib let init = null; 50f125bc8dSSascha Leib if (typeof sub === 'object' && sub.init) { 51f125bc8dSSascha Leib init = sub.init; 52f125bc8dSSascha Leib } 53f125bc8dSSascha Leib 54f125bc8dSSascha Leib // bind to object 55f125bc8dSSascha Leib if (typeof init == 'function') { 56f125bc8dSSascha Leib const init2 = init.bind(sub); 57f125bc8dSSascha Leib init2(obj); 58f125bc8dSSascha Leib } 59f125bc8dSSascha Leib }); 60f125bc8dSSascha Leib }, 61f125bc8dSSascha Leib 62f125bc8dSSascha Leib /* helper function to calculate the time difference to UTC: */ 63f125bc8dSSascha Leib _getTimeOffset: function() { 64f125bc8dSSascha Leib const now = new Date(); 65f125bc8dSSascha Leib let offset = now.getTimezoneOffset(); // in minutes 66f125bc8dSSascha Leib const sign = Math.sign(offset); // +1 or -1 67f125bc8dSSascha Leib offset = Math.abs(offset); // always positive 68f125bc8dSSascha Leib 69f125bc8dSSascha Leib let hours = 0; 70f125bc8dSSascha Leib while (offset >= 60) { 71f125bc8dSSascha Leib hours += 1; 72f125bc8dSSascha Leib offset -= 60; 73f125bc8dSSascha Leib } 74f125bc8dSSascha Leib return ( hours > 0 ? sign * hours + ' h' : '') + (offset > 0 ? ` ${offset} min` : ''); 7593a5b18bSSascha Leib }, 7693a5b18bSSascha Leib 7793a5b18bSSascha Leib /* helper function to create a new element with all attributes and text content */ 7893a5b18bSSascha Leib _makeElement: function(name, atlist = undefined, text = undefined) { 7993a5b18bSSascha Leib var r = null; 8093a5b18bSSascha Leib try { 8193a5b18bSSascha Leib r = document.createElement(name); 8293a5b18bSSascha Leib if (atlist) { 8393a5b18bSSascha Leib for (let attr in atlist) { 8493a5b18bSSascha Leib r.setAttribute(attr, atlist[attr]); 8593a5b18bSSascha Leib } 8693a5b18bSSascha Leib } 8793a5b18bSSascha Leib if (text) { 8893a5b18bSSascha Leib r.textContent = text.toString(); 8993a5b18bSSascha Leib } 9093a5b18bSSascha Leib } catch(e) { 9193a5b18bSSascha Leib console.error(e); 9293a5b18bSSascha Leib } 9393a5b18bSSascha Leib return r; 945526d629SSascha Leib }, 955526d629SSascha Leib 965526d629SSascha Leib /* helper to convert an ip address string to a normalised format: */ 975526d629SSascha Leib _ip2Num: function(ip) { 985526d629SSascha Leib if (ip.indexOf(':') > 0) { /* IP6 */ 995526d629SSascha Leib return (ip.split(':').map(d => ('0000'+d).slice(-4) ).join('')); 1005526d629SSascha Leib } else { /* IP4 */ 1015526d629SSascha Leib return Number(ip.split('.').map(d => ('000'+d).slice(-3) ).join('')); 1025526d629SSascha Leib } 103446aa816SSascha Leib }, 104446aa816SSascha Leib 105446aa816SSascha Leib /* helper function to format a Date object to show only the time. */ 106446aa816SSascha Leib /* returns String */ 107446aa816SSascha Leib _formatTime: function(date) { 108446aa816SSascha Leib 109446aa816SSascha Leib if (date) { 110446aa816SSascha Leib return ('0'+date.getHours()).slice(-2) + ':' + ('0'+date.getMinutes()).slice(-2) + ':' + ('0'+date.getSeconds()).slice(-2); 111446aa816SSascha Leib } else { 112446aa816SSascha Leib return null; 113446aa816SSascha Leib } 114446aa816SSascha Leib 115446aa816SSascha Leib }, 116446aa816SSascha Leib 117446aa816SSascha Leib /* helper function to show a time difference in seconds or minutes */ 118446aa816SSascha Leib /* returns String */ 119446aa816SSascha Leib _formatTimeDiff: function(dateA, dateB) { 120446aa816SSascha Leib 121446aa816SSascha Leib // if the second date is ealier, swap them: 122446aa816SSascha Leib if (dateA > dateB) dateB = [dateA, dateA = dateB][0]; 123446aa816SSascha Leib 124446aa816SSascha Leib // get the difference in milliseconds: 125446aa816SSascha Leib let ms = dateB - dateA; 126446aa816SSascha Leib 127446aa816SSascha Leib if (ms > 50) { /* ignore small time spans */ 128446aa816SSascha Leib const h = Math.floor((ms / (1000 * 60 * 60)) % 24); 129446aa816SSascha Leib const m = Math.floor((ms / (1000 * 60)) % 60); 130446aa816SSascha Leib const s = Math.floor((ms / 1000) % 60); 131446aa816SSascha Leib 132446aa816SSascha Leib return ( h>0 ? h + 'h ': '') + ( m>0 ? m + 'm ': '') + ( s>0 ? s + 's': ''); 133446aa816SSascha Leib } 134446aa816SSascha Leib 135446aa816SSascha Leib return null; 136446aa816SSascha Leib 137f125bc8dSSascha Leib } 138f125bc8dSSascha Leib } 139c7931771SSascha Leib}; 140f125bc8dSSascha Leib 1419f1ee8c1SSascha Leib/* everything specific to the "Today" tab is self-contained in the "live" object: */ 1427bd08c30SSascha LeibBotMon.live = { 143f125bc8dSSascha Leib init: function() { 1447bd08c30SSascha Leib //console.info('BotMon.live.init()'); 145f125bc8dSSascha Leib 146f125bc8dSSascha Leib // set the title: 1477bd08c30SSascha Leib const tDiff = '(<abbr title="Coordinated Universal Time">UTC</abbr>' + (BotMon._timeDiff != '' ? `, ${BotMon._timeDiff}` : '' ) + ')'; 1487bd08c30SSascha Leib BotMon.live.gui.status.setTitle(`Data for <time datetime=${BotMon._today}>${BotMon._today}</time> ${tDiff}`); 149f125bc8dSSascha Leib 150f125bc8dSSascha Leib // init sub-objects: 1517bd08c30SSascha Leib BotMon.t._callInit(this); 152f125bc8dSSascha Leib }, 153f125bc8dSSascha Leib 154f125bc8dSSascha Leib data: { 155f125bc8dSSascha Leib init: function() { 1567bd08c30SSascha Leib //console.info('BotMon.live.data.init()'); 157f125bc8dSSascha Leib 158f125bc8dSSascha Leib // call sub-inits: 1597bd08c30SSascha Leib BotMon.t._callInit(this); 1609f1ee8c1SSascha Leib }, 161f125bc8dSSascha Leib 1629f1ee8c1SSascha Leib // this will be called when the known json files are done loading: 1639f1ee8c1SSascha Leib _dispatch: function(file) { 1647bd08c30SSascha Leib //console.info('BotMon.live.data._dispatch(,',file,')'); 1659f1ee8c1SSascha Leib 1669f1ee8c1SSascha Leib // shortcut to make code more readable: 1677bd08c30SSascha Leib const data = BotMon.live.data; 1689f1ee8c1SSascha Leib 1699f1ee8c1SSascha Leib // set the flags: 1709f1ee8c1SSascha Leib switch(file) { 1715526d629SSascha Leib case 'rules': 1725526d629SSascha Leib data._dispatchRulesLoaded = true; 1735526d629SSascha Leib break; 1749f1ee8c1SSascha Leib case 'bots': 1759f1ee8c1SSascha Leib data._dispatchBotsLoaded = true; 1769f1ee8c1SSascha Leib break; 1779f1ee8c1SSascha Leib case 'clients': 1789f1ee8c1SSascha Leib data._dispatchClientsLoaded = true; 1799f1ee8c1SSascha Leib break; 1809f1ee8c1SSascha Leib case 'platforms': 1819f1ee8c1SSascha Leib data._dispatchPlatformsLoaded = true; 1829f1ee8c1SSascha Leib break; 1839f1ee8c1SSascha Leib default: 1849f1ee8c1SSascha Leib // ignore 1859f1ee8c1SSascha Leib } 1869f1ee8c1SSascha Leib 1879f1ee8c1SSascha Leib // are all the flags set? 188b82cba27SSascha Leib if (data._dispatchBotsLoaded && data._dispatchClientsLoaded && data._dispatchPlatformsLoaded && data._dispatchRulesLoaded) { 1899f1ee8c1SSascha Leib // chain the log files loading: 1907bd08c30SSascha Leib BotMon.live.data.loadLogFile('srv', BotMon.live.data._onServerLogLoaded); 1919f1ee8c1SSascha Leib } 1922f2bc93aSSascha Leib }, 1939f1ee8c1SSascha Leib // flags to track which data files have been loaded: 1949f1ee8c1SSascha Leib _dispatchBotsLoaded: false, 1959f1ee8c1SSascha Leib _dispatchClientsLoaded: false, 1969f1ee8c1SSascha Leib _dispatchPlatformsLoaded: false, 197b82cba27SSascha Leib _dispatchRulesLoaded: false, 1982f2bc93aSSascha Leib 1999f1ee8c1SSascha Leib // event callback, after the server log has been loaded: 2002f2bc93aSSascha Leib _onServerLogLoaded: function() { 2017bd08c30SSascha Leib //console.info('BotMon.live.data._onServerLogLoaded()'); 2022f2bc93aSSascha Leib 2039f1ee8c1SSascha Leib // chain the client log file to load: 2047bd08c30SSascha Leib BotMon.live.data.loadLogFile('log', BotMon.live.data._onClientLogLoaded); 2052f2bc93aSSascha Leib }, 2062f2bc93aSSascha Leib 2079f1ee8c1SSascha Leib // event callback, after the client log has been loaded: 2082f2bc93aSSascha Leib _onClientLogLoaded: function() { 20993a5b18bSSascha Leib //console.info('BotMon.live.data._onClientLogLoaded()'); 2102f2bc93aSSascha Leib 2119f1ee8c1SSascha Leib // chain the ticks file to load: 2127bd08c30SSascha Leib BotMon.live.data.loadLogFile('tck', BotMon.live.data._onTicksLogLoaded); 2132f2bc93aSSascha Leib 2149f1ee8c1SSascha Leib }, 2159f1ee8c1SSascha Leib 2169f1ee8c1SSascha Leib // event callback, after the tiker log has been loaded: 2179f1ee8c1SSascha Leib _onTicksLogLoaded: function() { 21893a5b18bSSascha Leib //console.info('BotMon.live.data._onTicksLogLoaded()'); 2199f1ee8c1SSascha Leib 2209f1ee8c1SSascha Leib // analyse the data: 2217bd08c30SSascha Leib BotMon.live.data.analytics.analyseAll(); 2229f1ee8c1SSascha Leib 2239f1ee8c1SSascha Leib // sort the data: 2249f1ee8c1SSascha Leib // #TODO 2259f1ee8c1SSascha Leib 2269f1ee8c1SSascha Leib // display the data: 2277bd08c30SSascha Leib BotMon.live.gui.overview.make(); 2289f1ee8c1SSascha Leib 22993a5b18bSSascha Leib //console.log(BotMon.live.data.model._visitors); 2309f1ee8c1SSascha Leib 2312f2bc93aSSascha Leib }, 2322f2bc93aSSascha Leib 2332f2bc93aSSascha Leib model: { 2349f1ee8c1SSascha Leib // visitors storage: 2352f2bc93aSSascha Leib _visitors: [], 2362f2bc93aSSascha Leib 2372f2bc93aSSascha Leib // find an already existing visitor record: 238f4417fdeSSascha Leib findVisitor: function(visitor) { 239f4417fdeSSascha Leib //console.info('BotMon.live.data.model.findVisitor()'); 240f4417fdeSSascha Leib //console.log(visitor); 2412f2bc93aSSascha Leib 2422f2bc93aSSascha Leib // shortcut to make code more readable: 2437bd08c30SSascha Leib const model = BotMon.live.data.model; 2442f2bc93aSSascha Leib 245446aa816SSascha Leib const timeout = 60 * 60 * 1000; /* session timeout: One hour */ 246446aa816SSascha Leib 2472f2bc93aSSascha Leib // loop over all visitors already registered: 2482f2bc93aSSascha Leib for (let i=0; i<model._visitors.length; i++) { 2492f2bc93aSSascha Leib const v = model._visitors[i]; 250f4417fdeSSascha Leib 251f4417fdeSSascha Leib if (visitor._type == BM_USERTYPE.KNOWN_BOT) { /* known bots */ 252f4417fdeSSascha Leib 253f4417fdeSSascha Leib // bots match when their ID matches: 254f4417fdeSSascha Leib if (v._bot && v._bot.id == visitor._bot.id) { 255f4417fdeSSascha Leib return v; 256f4417fdeSSascha Leib } 257f4417fdeSSascha Leib 258f4417fdeSSascha Leib } else if (visitor._type == BM_USERTYPE.KNOWN_USER) { /* registered users */ 259f4417fdeSSascha Leib 260f4417fdeSSascha Leib // visitors match when their names match: 261f4417fdeSSascha Leib if ( v.usr == visitor.usr 262f4417fdeSSascha Leib && v.ip == visitor.ip 263f4417fdeSSascha Leib && v.agent == visitor.agent) { 264f4417fdeSSascha Leib return v; 265f4417fdeSSascha Leib } 266f4417fdeSSascha Leib } else { /* any other visitor */ 267f4417fdeSSascha Leib 2685d78a53bSSascha Leib if (Math.abs(v._lastSeen - visitor.ts) < timeout) { /* ignore timed out visits */ 269f4417fdeSSascha Leib if ( v.id == visitor.id) { /* match the pre-defined IDs */ 270f4417fdeSSascha Leib return v; 271259d3b85SSascha Leib } else if (v.ip == visitor.ip && v.agent == visitor.agent) { 272451abfadSSascha Leib if (v.typ !== 'ip') { 2735526d629SSascha Leib console.warn(`Visitor ID “${v.id}” not found, using matchin IP + User-Agent instead.`); 274451abfadSSascha Leib } 275259d3b85SSascha Leib return v; 276f4417fdeSSascha Leib } 277f4417fdeSSascha Leib } 2785d78a53bSSascha Leib 2792f2bc93aSSascha Leib } 280446aa816SSascha Leib } 2812f2bc93aSSascha Leib return null; // nothing found 2822f2bc93aSSascha Leib }, 2832f2bc93aSSascha Leib 284259d3b85SSascha Leib /* if there is already this visit registered, return the page view item */ 285259d3b85SSascha Leib _getPageView: function(visit, view) { 2862f2bc93aSSascha Leib 287294f6af8SSascha Leib // shortcut to make code more readable: 2887bd08c30SSascha Leib const model = BotMon.live.data.model; 289294f6af8SSascha Leib 2902f2bc93aSSascha Leib for (let i=0; i<visit._pageViews.length; i++) { 291294f6af8SSascha Leib const pv = visit._pageViews[i]; 292259d3b85SSascha Leib if (pv.pg == view.pg) { 293259d3b85SSascha Leib return pv; 2942f2bc93aSSascha Leib } 2952f2bc93aSSascha Leib } 2962f2bc93aSSascha Leib return null; // not found 2972f2bc93aSSascha Leib }, 2982f2bc93aSSascha Leib 2992f2bc93aSSascha Leib // register a new visitor (or update if already exists) 300f4417fdeSSascha Leib registerVisit: function(nv, type) { 301f4417fdeSSascha Leib //console.info('registerVisit', nv, type); 3022f2bc93aSSascha Leib 3032f2bc93aSSascha Leib // shortcut to make code more readable: 3047bd08c30SSascha Leib const model = BotMon.live.data.model; 3052f2bc93aSSascha Leib 30643d9de6bSSascha Leib // is it a known bot? 307f4417fdeSSascha Leib const bot = BotMon.live.data.bots.match(nv.agent); 3089f1ee8c1SSascha Leib 309f4417fdeSSascha Leib // enrich new visitor with relevant data: 310f4417fdeSSascha Leib if (!nv._bot) nv._bot = bot ?? null; // bot info 311259d3b85SSascha Leib nv._type = ( bot ? BM_USERTYPE.KNOWN_BOT : ( nv.usr && nv.usr !== '' ? BM_USERTYPE.KNOWN_USER : BM_USERTYPE.UNKNOWN ) ); 312f4417fdeSSascha Leib if (!nv._firstSeen) nv._firstSeen = nv.ts; 313259d3b85SSascha Leib nv._lastSeen = nv.ts; 31443d9de6bSSascha Leib 315abfc901fSSascha Leib // check if it already exists: 316f4417fdeSSascha Leib let visitor = model.findVisitor(nv); 317abfc901fSSascha Leib if (!visitor) { 318f4417fdeSSascha Leib visitor = nv; 31943d9de6bSSascha Leib visitor._seenBy = [type]; 3202f2bc93aSSascha Leib visitor._pageViews = []; // array of page views 3212f2bc93aSSascha Leib visitor._hasReferrer = false; // has at least one referrer 3222f2bc93aSSascha Leib visitor._jsClient = false; // visitor has been seen logged by client js as well 323f4417fdeSSascha Leib visitor._client = BotMon.live.data.clients.match(nv.agent) ?? null; // client info 324f4417fdeSSascha Leib visitor._platform = BotMon.live.data.platforms.match(nv.agent); // platform info 325f4417fdeSSascha Leib model._visitors.push(visitor); 326451abfadSSascha Leib } else { // update existing 327446aa816SSascha Leib if (visitor._firstSeen > nv.ts) { 328451abfadSSascha Leib visitor._firstSeen = nv.ts; 329451abfadSSascha Leib } 3302f2bc93aSSascha Leib } 3312f2bc93aSSascha Leib 3329f1ee8c1SSascha Leib // find browser 3339f1ee8c1SSascha Leib 3342f2bc93aSSascha Leib // is this visit already registered? 335259d3b85SSascha Leib let prereg = model._getPageView(visitor, nv); 3362f2bc93aSSascha Leib if (!prereg) { 337259d3b85SSascha Leib // add new page view: 338259d3b85SSascha Leib prereg = model._makePageView(nv, type); 3392f2bc93aSSascha Leib visitor._pageViews.push(prereg); 340259d3b85SSascha Leib } else { 341259d3b85SSascha Leib // update last seen date 342259d3b85SSascha Leib prereg._lastSeen = nv.ts; 343259d3b85SSascha Leib // increase view count: 344259d3b85SSascha Leib prereg._viewCount += 1; 345446aa816SSascha Leib prereg._tickCount += 1; 3462f2bc93aSSascha Leib } 3472f2bc93aSSascha Leib 3482f2bc93aSSascha Leib // update referrer state: 3492f2bc93aSSascha Leib visitor._hasReferrer = visitor._hasReferrer || 3502f2bc93aSSascha Leib (prereg.ref !== undefined && prereg.ref !== ''); 3512f2bc93aSSascha Leib 3522f2bc93aSSascha Leib // update time stamp for last-seen: 353451abfadSSascha Leib if (visitor._lastSeen < nv.ts) { 354f4417fdeSSascha Leib visitor._lastSeen = nv.ts; 355451abfadSSascha Leib } 3569f1ee8c1SSascha Leib 3579f1ee8c1SSascha Leib // if needed: 3589f1ee8c1SSascha Leib return visitor; 3592f2bc93aSSascha Leib }, 3602f2bc93aSSascha Leib 3612f2bc93aSSascha Leib // updating visit data from the client-side log: 3622f2bc93aSSascha Leib updateVisit: function(dat) { 3639f1ee8c1SSascha Leib //console.info('updateVisit', dat); 3642f2bc93aSSascha Leib 3652f2bc93aSSascha Leib // shortcut to make code more readable: 3667bd08c30SSascha Leib const model = BotMon.live.data.model; 3672f2bc93aSSascha Leib 36843d9de6bSSascha Leib const type = 'log'; 36943d9de6bSSascha Leib 370f4417fdeSSascha Leib let visitor = BotMon.live.data.model.findVisitor(dat); 3719f1ee8c1SSascha Leib if (!visitor) { 37243d9de6bSSascha Leib visitor = model.registerVisit(dat, type); 3739f1ee8c1SSascha Leib } 3742f2bc93aSSascha Leib if (visitor) { 37543d9de6bSSascha Leib 376446aa816SSascha Leib if (visitor._lastSeen < dat.ts) { 3772f2bc93aSSascha Leib visitor._lastSeen = dat.ts; 378446aa816SSascha Leib } 379259d3b85SSascha Leib if (!visitor._seenBy.includes(type)) { 380259d3b85SSascha Leib visitor._seenBy.push(type); 381259d3b85SSascha Leib } 3822f2bc93aSSascha Leib visitor._jsClient = true; // seen by client js 3832f2bc93aSSascha Leib } 3842f2bc93aSSascha Leib 3852f2bc93aSSascha Leib // find the page view: 386259d3b85SSascha Leib let prereg = BotMon.live.data.model._getPageView(visitor, dat); 3872f2bc93aSSascha Leib if (prereg) { 3882f2bc93aSSascha Leib // update the page view: 3892f2bc93aSSascha Leib prereg._lastSeen = dat.ts; 390259d3b85SSascha Leib if (!prereg._seenBy.includes(type)) prereg._seenBy.push(type); 3912f2bc93aSSascha Leib prereg._jsClient = true; // seen by client js 3922f2bc93aSSascha Leib } else { 3932f2bc93aSSascha Leib // add the page view to the visitor: 394259d3b85SSascha Leib prereg = model._makePageView(dat, type); 3952f2bc93aSSascha Leib visitor._pageViews.push(prereg); 3962f2bc93aSSascha Leib } 397446aa816SSascha Leib prereg._tickCount += 1; 3989f1ee8c1SSascha Leib }, 3999f1ee8c1SSascha Leib 4009f1ee8c1SSascha Leib // updating visit data from the ticker log: 4019f1ee8c1SSascha Leib updateTicks: function(dat) { 402294f6af8SSascha Leib //console.info('updateTicks', dat); 4039f1ee8c1SSascha Leib 4049f1ee8c1SSascha Leib // shortcut to make code more readable: 4057bd08c30SSascha Leib const model = BotMon.live.data.model; 4069f1ee8c1SSascha Leib 407259d3b85SSascha Leib const type = 'tck'; 408259d3b85SSascha Leib 4099f1ee8c1SSascha Leib // find the visit info: 410f4417fdeSSascha Leib let visitor = model.findVisitor(dat); 411294f6af8SSascha Leib if (!visitor) { 4125526d629SSascha Leib console.info(`No visitor with ID “${dat.id}” found, registering as a new one.`); 413259d3b85SSascha Leib visitor = model.registerVisit(dat, type); 414294f6af8SSascha Leib } 4159f1ee8c1SSascha Leib if (visitor) { 41643d9de6bSSascha Leib // update visitor: 417294f6af8SSascha Leib if (visitor._lastSeen < dat.ts) visitor._lastSeen = dat.ts; 418259d3b85SSascha Leib if (!visitor._seenBy.includes(type)) visitor._seenBy.push(type); 419294f6af8SSascha Leib 420294f6af8SSascha Leib // get the page view info: 421259d3b85SSascha Leib let pv = model._getPageView(visitor, dat); 422259d3b85SSascha Leib if (!pv) { 423446aa816SSascha Leib console.warn(`No page view for visit ID “${dat.id}”, page “${dat.pg}”, registering a new one.`); 424259d3b85SSascha Leib pv = model._makePageView(dat, type); 425259d3b85SSascha Leib visitor._pageViews.push(pv); 426259d3b85SSascha Leib } 427294f6af8SSascha Leib 428259d3b85SSascha Leib // update the page view info: 429259d3b85SSascha Leib if (!pv._seenBy.includes(type)) pv._seenBy.push(type); 430259d3b85SSascha Leib if (pv._lastSeen.getTime() < dat.ts.getTime()) pv._lastSeen = dat.ts; 431259d3b85SSascha Leib pv._tickCount += 1; 432259d3b85SSascha Leib 433259d3b85SSascha Leib } 434259d3b85SSascha Leib }, 435259d3b85SSascha Leib 436259d3b85SSascha Leib // helper function to create a new "page view" item: 437259d3b85SSascha Leib _makePageView: function(data, type) { 4385526d629SSascha Leib 4395526d629SSascha Leib // try to parse the referrer: 4405526d629SSascha Leib let rUrl = null; 4415526d629SSascha Leib try { 4425526d629SSascha Leib rUrl = ( data.ref && data.ref !== '' ? new URL(data.ref) : null ); 4435526d629SSascha Leib } catch (e) { 4445526d629SSascha Leib console.info(`Invalid referer: “${data.ref}”.`); 4455526d629SSascha Leib } 4465526d629SSascha Leib 447259d3b85SSascha Leib return { 448259d3b85SSascha Leib _by: type, 449259d3b85SSascha Leib ip: data.ip, 450259d3b85SSascha Leib pg: data.pg, 4515526d629SSascha Leib _ref: rUrl, 452259d3b85SSascha Leib _firstSeen: data.ts, 453259d3b85SSascha Leib _lastSeen: data.ts, 454259d3b85SSascha Leib _seenBy: [type], 455259d3b85SSascha Leib _jsClient: ( type !== 'srv'), 456259d3b85SSascha Leib _viewCount: 1, 457259d3b85SSascha Leib _tickCount: 0 458294f6af8SSascha Leib }; 4592f2bc93aSSascha Leib } 460f125bc8dSSascha Leib }, 461f125bc8dSSascha Leib 462294f6af8SSascha Leib analytics: { 463294f6af8SSascha Leib 464294f6af8SSascha Leib init: function() { 465f4417fdeSSascha Leib //console.info('BotMon.live.data.analytics.init()'); 466294f6af8SSascha Leib }, 467294f6af8SSascha Leib 468294f6af8SSascha Leib // data storage: 469294f6af8SSascha Leib data: { 470294f6af8SSascha Leib totalVisits: 0, 471294f6af8SSascha Leib totalPageViews: 0, 472294f6af8SSascha Leib bots: { 473294f6af8SSascha Leib known: 0, 47493a5b18bSSascha Leib suspected: 0, 4759bc80cc5SSascha Leib human: 0, 4769bc80cc5SSascha Leib users: 0 477294f6af8SSascha Leib } 478294f6af8SSascha Leib }, 479294f6af8SSascha Leib 480294f6af8SSascha Leib // sort the visits by type: 481294f6af8SSascha Leib groups: { 482294f6af8SSascha Leib knownBots: [], 48393a5b18bSSascha Leib suspectedBots: [], 484294f6af8SSascha Leib humans: [], 485294f6af8SSascha Leib users: [] 486294f6af8SSascha Leib }, 487294f6af8SSascha Leib 488294f6af8SSascha Leib // all analytics 489294f6af8SSascha Leib analyseAll: function() { 4907bd08c30SSascha Leib //console.info('BotMon.live.data.analytics.analyseAll()'); 491294f6af8SSascha Leib 492294f6af8SSascha Leib // shortcut to make code more readable: 4937bd08c30SSascha Leib const model = BotMon.live.data.model; 494446aa816SSascha Leib const me = BotMon.live.data.analytics; 495294f6af8SSascha Leib 496451abfadSSascha Leib BotMon.live.gui.status.showBusy("Analysing data …"); 497451abfadSSascha Leib 498294f6af8SSascha Leib // loop over all visitors: 499294f6af8SSascha Leib model._visitors.forEach( (v) => { 500294f6af8SSascha Leib 501294f6af8SSascha Leib // count visits and page views: 502294f6af8SSascha Leib this.data.totalVisits += 1; 503294f6af8SSascha Leib this.data.totalPageViews += v._pageViews.length; 504294f6af8SSascha Leib 505294f6af8SSascha Leib // check for typical bot aspects: 50643d9de6bSSascha Leib let botScore = 0; 507294f6af8SSascha Leib 508f4417fdeSSascha Leib if (v._type == BM_USERTYPE.KNOWN_BOT) { // known bots 509294f6af8SSascha Leib 510b82cba27SSascha Leib this.data.bots.known += v._pageViews.length; 511294f6af8SSascha Leib this.groups.knownBots.push(v); 512294f6af8SSascha Leib 513f4417fdeSSascha Leib } else if (v._type == BM_USERTYPE.KNOWN_USER) { // known users */ 514f4417fdeSSascha Leib 515b82cba27SSascha Leib this.data.bots.users += v._pageViews.length; 516294f6af8SSascha Leib this.groups.users.push(v); 517294f6af8SSascha Leib 518f4417fdeSSascha Leib } else { 519294f6af8SSascha Leib 520b82cba27SSascha Leib // get evaluation: 521b82cba27SSascha Leib const e = BotMon.live.data.rules.evaluate(v); 522b82cba27SSascha Leib v._eval = e.rules; 523b82cba27SSascha Leib v._botVal = e.val; 524b82cba27SSascha Leib 525446aa816SSascha Leib // add each page view to IP range information (unless it is already from a known bot IP range): 526446aa816SSascha Leib v._pageViews.forEach( pv => { 527446aa816SSascha Leib me._addToIPRanges(pv.ip); 528446aa816SSascha Leib }); 529446aa816SSascha Leib 530b82cba27SSascha Leib if (e.isBot) { // likely bots 531b82cba27SSascha Leib v._type = BM_USERTYPE.LIKELY_BOT; 532b82cba27SSascha Leib this.data.bots.suspected += v._pageViews.length; 53393a5b18bSSascha Leib this.groups.suspectedBots.push(v); 534b82cba27SSascha Leib } else { // probably humans 535b82cba27SSascha Leib v._type = BM_USERTYPE.HUMAN; 536b82cba27SSascha Leib this.data.bots.human += v._pageViews.length; 537b82cba27SSascha Leib this.groups.humans.push(v); 538b82cba27SSascha Leib } 539b82cba27SSascha Leib // TODO: find suspected bots 540f4417fdeSSascha Leib 541294f6af8SSascha Leib } 542294f6af8SSascha Leib }); 543294f6af8SSascha Leib 5445d78a53bSSascha Leib BotMon.live.gui.status.hideBusy('Done.'); 545446aa816SSascha Leib }, 546446aa816SSascha Leib 547446aa816SSascha Leib // visits from IP ranges: 548446aa816SSascha Leib _ipRange: { 549446aa816SSascha Leib ip4: [], 550446aa816SSascha Leib ip6: [] 551446aa816SSascha Leib }, 552446aa816SSascha Leib /** 553446aa816SSascha Leib * Adds a visit to the IP range statistics. 554446aa816SSascha Leib * 555446aa816SSascha Leib * This helps to identify IP ranges that are used by bots. 556446aa816SSascha Leib * 557446aa816SSascha Leib * @param {string} ip The IP address to add. 558446aa816SSascha Leib */ 559446aa816SSascha Leib _addToIPRanges: function(ip) { 560446aa816SSascha Leib 561*0c039615SSascha Leib // #TODO: handle nestled ranges! 562446aa816SSascha Leib const me = BotMon.live.data.analytics; 563446aa816SSascha Leib const ipv = (ip.indexOf(':') > 0 ? 6 : 4); 564446aa816SSascha Leib 565446aa816SSascha Leib const ipArr = ip.split( ipv == 6 ? ':' : '.'); 566446aa816SSascha Leib const maxSegments = (ipv == 6 ? 4 : 3); 567446aa816SSascha Leib 568446aa816SSascha Leib let arr = (ipv == 6 ? me._ipRange.ip6 : me._ipRange.ip4); 5695d78a53bSSascha Leib 5705d78a53bSSascha Leib // find any existing segment entry: 5715d78a53bSSascha Leib it = null; 5725d78a53bSSascha Leib for (let i=0; i < arr.length; i++) { 5735d78a53bSSascha Leib const sig = arr[i]; 5745d78a53bSSascha Leib if (sig.seg == ipArr[0]) { 5755d78a53bSSascha Leib it = sig; 5765d78a53bSSascha Leib break; 5775d78a53bSSascha Leib } 5785d78a53bSSascha Leib } 5795d78a53bSSascha Leib 5805d78a53bSSascha Leib // create if not found: 581446aa816SSascha Leib if (!it) { 5825d78a53bSSascha Leib it = {seg: ipArr[0], count: 1}; 5835d78a53bSSascha Leib //if (i<maxSegments) it.sub = []; 584446aa816SSascha Leib arr.push(it); 5855d78a53bSSascha Leib 5865d78a53bSSascha Leib } else { // increase count: 5875d78a53bSSascha Leib 588446aa816SSascha Leib it.count += 1; 589446aa816SSascha Leib } 5905d78a53bSSascha Leib 591446aa816SSascha Leib }, 592*0c039615SSascha Leib getTopBotIPRanges: function(max) { 593*0c039615SSascha Leib 594446aa816SSascha Leib const me = BotMon.live.data.analytics; 595446aa816SSascha Leib 596*0c039615SSascha Leib const kMinHits = 2; 597451abfadSSascha Leib 598*0c039615SSascha Leib // combine the ip lists, removing all lower volume branches: 599*0c039615SSascha Leib let ipTypes = [4,6]; 600*0c039615SSascha Leib const tmpList = []; 601*0c039615SSascha Leib for (let i=0; i<ipTypes.length; i++) { 602*0c039615SSascha Leib const ipType = ipTypes[i]; 603*0c039615SSascha Leib (ipType == 6 ? me._ipRange.ip6 : me._ipRange.ip4).forEach( it => { 604*0c039615SSascha Leib if (it.count > kMinHits) { 605*0c039615SSascha Leib it.type = ipType; 606*0c039615SSascha Leib tmpList.push(it); 607*0c039615SSascha Leib } 6085d78a53bSSascha Leib }); 609*0c039615SSascha Leib tmpList.sort( (a,b) => b.count - a.count); 610*0c039615SSascha Leib } 6115d78a53bSSascha Leib 612*0c039615SSascha Leib // reduce to only the top (max) items and create the target format: 613*0c039615SSascha Leib // #TODO: handle nestled ranges! 614*0c039615SSascha Leib let rList = []; 615*0c039615SSascha Leib for (let j=0; Math.min(max, tmpList.length) > j; j++) { 616*0c039615SSascha Leib const rangeInfo = tmpList[j]; 617*0c039615SSascha Leib rList.push({ 618*0c039615SSascha Leib 'ip': rangeInfo.seg + ( rangeInfo.type == 4 ? '.x.x.x' : '::x'), 619*0c039615SSascha Leib 'typ': rangeInfo.type, 620*0c039615SSascha Leib 'num': rangeInfo.count 621*0c039615SSascha Leib }); 622*0c039615SSascha Leib } 623*0c039615SSascha Leib 624*0c039615SSascha Leib return rList; 625446aa816SSascha Leib } 626294f6af8SSascha Leib }, 627294f6af8SSascha Leib 628f125bc8dSSascha Leib bots: { 629f125bc8dSSascha Leib // loads the list of known bots from a JSON file: 630f125bc8dSSascha Leib init: async function() { 6317bd08c30SSascha Leib //console.info('BotMon.live.data.bots.init()'); 632f125bc8dSSascha Leib 633f125bc8dSSascha Leib // Load the list of known bots: 6347bd08c30SSascha Leib BotMon.live.gui.status.showBusy("Loading known bots …"); 635*0c039615SSascha Leib const url = BotMon._baseDir + 'config/known-bots.json'; 636f125bc8dSSascha Leib try { 637f125bc8dSSascha Leib const response = await fetch(url); 638f125bc8dSSascha Leib if (!response.ok) { 639f125bc8dSSascha Leib throw new Error(`${response.status} ${response.statusText}`); 640f125bc8dSSascha Leib } 641f125bc8dSSascha Leib 64243d9de6bSSascha Leib this._list = await response.json(); 64343d9de6bSSascha Leib this._ready = true; 644f125bc8dSSascha Leib 645f125bc8dSSascha Leib } catch (error) { 6467bd08c30SSascha Leib BotMon.live.gui.status.setError("Error while loading the ‘known bots’ file: " + error.message); 647f125bc8dSSascha Leib } finally { 6487bd08c30SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 6497bd08c30SSascha Leib BotMon.live.data._dispatch('bots') 650f125bc8dSSascha Leib } 651f125bc8dSSascha Leib }, 652f125bc8dSSascha Leib 653f125bc8dSSascha Leib // returns bot info if the clientId matches a known bot, null otherwise: 65443d9de6bSSascha Leib match: function(agent) { 65543d9de6bSSascha Leib //console.info('BotMon.live.data.bots.match(',agent,')'); 656f125bc8dSSascha Leib 65743d9de6bSSascha Leib const BotList = BotMon.live.data.bots._list; 65843d9de6bSSascha Leib 65943d9de6bSSascha Leib // default is: not found! 66043d9de6bSSascha Leib let botInfo = null; 66143d9de6bSSascha Leib 662fa1532fcSSascha Leib if (!agent) return null; 663fa1532fcSSascha Leib 66443d9de6bSSascha Leib // check for known bots: 66543d9de6bSSascha Leib BotList.find(bot => { 66643d9de6bSSascha Leib let r = false; 6679f1ee8c1SSascha Leib for (let j=0; j<bot.rx.length; j++) { 66843d9de6bSSascha Leib const rxr = agent.match(new RegExp(bot.rx[j])); 66943d9de6bSSascha Leib if (rxr) { 67043d9de6bSSascha Leib botInfo = { 67143d9de6bSSascha Leib n : bot.n, 67243d9de6bSSascha Leib id: bot.id, 67343d9de6bSSascha Leib url: bot.url, 67443d9de6bSSascha Leib v: (rxr.length > 1 ? rxr[1] : -1) 6750359f250SSascha Leib }; 67643d9de6bSSascha Leib r = true; 67743d9de6bSSascha Leib break; 678446aa816SSascha Leib }; 6790359f250SSascha Leib }; 68043d9de6bSSascha Leib return r; 68143d9de6bSSascha Leib }); 682259d3b85SSascha Leib 683259d3b85SSascha Leib // check for unknown bots: 684259d3b85SSascha Leib if (!botInfo) { 685259d3b85SSascha Leib const botmatch = agent.match(/[^\s](\w*bot)[\/\s;\),$]/i); 686259d3b85SSascha Leib if(botmatch) { 687259d3b85SSascha Leib botInfo = {'id': "other", 'n': "Other", "bot": botmatch[0] }; 688259d3b85SSascha Leib } 6899f1ee8c1SSascha Leib } 69043d9de6bSSascha Leib 69143d9de6bSSascha Leib //console.log("botInfo:", botInfo); 69243d9de6bSSascha Leib return botInfo; 693f125bc8dSSascha Leib }, 694f125bc8dSSascha Leib 69543d9de6bSSascha Leib 696f125bc8dSSascha Leib // indicates if the list is loaded and ready to use: 697f125bc8dSSascha Leib _ready: false, 698f125bc8dSSascha Leib 699f125bc8dSSascha Leib // the actual bot list is stored here: 700f125bc8dSSascha Leib _list: [] 701f125bc8dSSascha Leib }, 702f125bc8dSSascha Leib 7039f1ee8c1SSascha Leib clients: { 7049f1ee8c1SSascha Leib // loads the list of known clients from a JSON file: 7059f1ee8c1SSascha Leib init: async function() { 7067bd08c30SSascha Leib //console.info('BotMon.live.data.clients.init()'); 7079f1ee8c1SSascha Leib 7089f1ee8c1SSascha Leib // Load the list of known bots: 7097bd08c30SSascha Leib BotMon.live.gui.status.showBusy("Loading known clients"); 710*0c039615SSascha Leib const url = BotMon._baseDir + 'config/known-clients.json'; 7119f1ee8c1SSascha Leib try { 7129f1ee8c1SSascha Leib const response = await fetch(url); 7139f1ee8c1SSascha Leib if (!response.ok) { 7149f1ee8c1SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 7159f1ee8c1SSascha Leib } 7169f1ee8c1SSascha Leib 7177bd08c30SSascha Leib BotMon.live.data.clients._list = await response.json(); 7187bd08c30SSascha Leib BotMon.live.data.clients._ready = true; 7199f1ee8c1SSascha Leib 7209f1ee8c1SSascha Leib } catch (error) { 7217bd08c30SSascha Leib BotMon.live.gui.status.setError("Error while loading the known clients file: " + error.message); 7229f1ee8c1SSascha Leib } finally { 7237bd08c30SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 7247bd08c30SSascha Leib BotMon.live.data._dispatch('clients') 7259f1ee8c1SSascha Leib } 7269f1ee8c1SSascha Leib }, 7279f1ee8c1SSascha Leib 72893a5b18bSSascha Leib // returns bot info if the user-agent matches a known bot, null otherwise: 72943d9de6bSSascha Leib match: function(agent) { 73043d9de6bSSascha Leib //console.info('BotMon.live.data.clients.match(',agent,')'); 7319f1ee8c1SSascha Leib 7329f1ee8c1SSascha Leib let match = {"n": "Unknown", "v": -1, "id": null}; 7339f1ee8c1SSascha Leib 73443d9de6bSSascha Leib if (agent) { 7357bd08c30SSascha Leib BotMon.live.data.clients._list.find(client => { 7369f1ee8c1SSascha Leib let r = false; 7379f1ee8c1SSascha Leib for (let j=0; j<client.rx.length; j++) { 73843d9de6bSSascha Leib const rxr = agent.match(new RegExp(client.rx[j])); 7399f1ee8c1SSascha Leib if (rxr) { 7409f1ee8c1SSascha Leib match.n = client.n; 7419f1ee8c1SSascha Leib match.v = (rxr.length > 1 ? rxr[1] : -1); 7429f1ee8c1SSascha Leib match.id = client.id || null; 7439f1ee8c1SSascha Leib r = true; 7449f1ee8c1SSascha Leib break; 7459f1ee8c1SSascha Leib } 7469f1ee8c1SSascha Leib } 7479f1ee8c1SSascha Leib return r; 7489f1ee8c1SSascha Leib }); 7499f1ee8c1SSascha Leib } 7509f1ee8c1SSascha Leib 75143d9de6bSSascha Leib //console.log(match) 7529f1ee8c1SSascha Leib return match; 7539f1ee8c1SSascha Leib }, 7549f1ee8c1SSascha Leib 7559f1ee8c1SSascha Leib // indicates if the list is loaded and ready to use: 7569f1ee8c1SSascha Leib _ready: false, 7579f1ee8c1SSascha Leib 7589f1ee8c1SSascha Leib // the actual bot list is stored here: 7599f1ee8c1SSascha Leib _list: [] 7609f1ee8c1SSascha Leib 7619f1ee8c1SSascha Leib }, 7629f1ee8c1SSascha Leib 7639f1ee8c1SSascha Leib platforms: { 7649f1ee8c1SSascha Leib // loads the list of known platforms from a JSON file: 7659f1ee8c1SSascha Leib init: async function() { 7667bd08c30SSascha Leib //console.info('BotMon.live.data.platforms.init()'); 7679f1ee8c1SSascha Leib 7689f1ee8c1SSascha Leib // Load the list of known bots: 7697bd08c30SSascha Leib BotMon.live.gui.status.showBusy("Loading known platforms"); 770*0c039615SSascha Leib const url = BotMon._baseDir + 'config/known-platforms.json'; 7719f1ee8c1SSascha Leib try { 7729f1ee8c1SSascha Leib const response = await fetch(url); 7739f1ee8c1SSascha Leib if (!response.ok) { 7749f1ee8c1SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 7759f1ee8c1SSascha Leib } 7769f1ee8c1SSascha Leib 7777bd08c30SSascha Leib BotMon.live.data.platforms._list = await response.json(); 7787bd08c30SSascha Leib BotMon.live.data.platforms._ready = true; 7799f1ee8c1SSascha Leib 7809f1ee8c1SSascha Leib } catch (error) { 7817bd08c30SSascha Leib BotMon.live.gui.status.setError("Error while loading the known platforms file: " + error.message); 7829f1ee8c1SSascha Leib } finally { 7837bd08c30SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 7847bd08c30SSascha Leib BotMon.live.data._dispatch('platforms') 7859f1ee8c1SSascha Leib } 7869f1ee8c1SSascha Leib }, 7879f1ee8c1SSascha Leib 7889f1ee8c1SSascha Leib // returns bot info if the browser id matches a known platform: 7899f1ee8c1SSascha Leib match: function(cid) { 7907bd08c30SSascha Leib //console.info('BotMon.live.data.platforms.match(',cid,')'); 7919f1ee8c1SSascha Leib 7929f1ee8c1SSascha Leib let match = {"n": "Unknown", "id": null}; 7939f1ee8c1SSascha Leib 7949f1ee8c1SSascha Leib if (cid) { 7957bd08c30SSascha Leib BotMon.live.data.platforms._list.find(platform => { 7969f1ee8c1SSascha Leib let r = false; 7979f1ee8c1SSascha Leib for (let j=0; j<platform.rx.length; j++) { 7989f1ee8c1SSascha Leib const rxr = cid.match(new RegExp(platform.rx[j])); 7999f1ee8c1SSascha Leib if (rxr) { 8009f1ee8c1SSascha Leib match.n = platform.n; 8019f1ee8c1SSascha Leib match.v = (rxr.length > 1 ? rxr[1] : -1); 8029f1ee8c1SSascha Leib match.id = platform.id || null; 8039f1ee8c1SSascha Leib r = true; 8049f1ee8c1SSascha Leib break; 8059f1ee8c1SSascha Leib } 8069f1ee8c1SSascha Leib } 8079f1ee8c1SSascha Leib return r; 8089f1ee8c1SSascha Leib }); 8099f1ee8c1SSascha Leib } 8109f1ee8c1SSascha Leib 8119f1ee8c1SSascha Leib return match; 8129f1ee8c1SSascha Leib }, 8139f1ee8c1SSascha Leib 8149f1ee8c1SSascha Leib // indicates if the list is loaded and ready to use: 8159f1ee8c1SSascha Leib _ready: false, 8169f1ee8c1SSascha Leib 8179f1ee8c1SSascha Leib // the actual bot list is stored here: 8189f1ee8c1SSascha Leib _list: [] 8199f1ee8c1SSascha Leib 8209f1ee8c1SSascha Leib }, 8219f1ee8c1SSascha Leib 822b82cba27SSascha Leib rules: { 823b82cba27SSascha Leib // loads the list of rules and settings from a JSON file: 824b82cba27SSascha Leib init: async function() { 825b82cba27SSascha Leib //console.info('BotMon.live.data.rules.init()'); 826b82cba27SSascha Leib 827b82cba27SSascha Leib // Load the list of known bots: 828b82cba27SSascha Leib BotMon.live.gui.status.showBusy("Loading list of rules …"); 829*0c039615SSascha Leib const url = BotMon._baseDir + 'config/rules.json'; 830b82cba27SSascha Leib try { 831b82cba27SSascha Leib const response = await fetch(url); 832b82cba27SSascha Leib if (!response.ok) { 833b82cba27SSascha Leib throw new Error(`${response.status} ${response.statusText}`); 834b82cba27SSascha Leib } 835b82cba27SSascha Leib 836b82cba27SSascha Leib const json = await response.json(); 837b82cba27SSascha Leib 838b82cba27SSascha Leib if (json.rules) { 839b82cba27SSascha Leib this._rulesList = json.rules; 840b82cba27SSascha Leib } 841b82cba27SSascha Leib 842b82cba27SSascha Leib if (json.threshold) { 843b82cba27SSascha Leib this._threshold = json.threshold; 844b82cba27SSascha Leib } 845b82cba27SSascha Leib 8465526d629SSascha Leib if (json.ipRanges) { 8475526d629SSascha Leib // clean up the IPs first: 8485526d629SSascha Leib let list = []; 8495526d629SSascha Leib json.ipRanges.forEach( it => { 8505526d629SSascha Leib let item = { 8515526d629SSascha Leib 'from': BotMon.t._ip2Num(it.from), 8525526d629SSascha Leib 'to': BotMon.t._ip2Num(it.to), 8536f3fa739SSascha Leib 'label': it.label 8545526d629SSascha Leib }; 8555526d629SSascha Leib list.push(item); 8565526d629SSascha Leib }); 8575526d629SSascha Leib 8585526d629SSascha Leib this._botIPs = list; 8595526d629SSascha Leib } 8605526d629SSascha Leib 861b82cba27SSascha Leib this._ready = true; 862b82cba27SSascha Leib 863b82cba27SSascha Leib } catch (error) { 864b82cba27SSascha Leib BotMon.live.gui.status.setError("Error while loading the ‘rules’ file: " + error.message); 865b82cba27SSascha Leib } finally { 866b82cba27SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 867b82cba27SSascha Leib BotMon.live.data._dispatch('rules') 868b82cba27SSascha Leib } 869b82cba27SSascha Leib }, 870b82cba27SSascha Leib 871b82cba27SSascha Leib _rulesList: [], // list of rules to find out if a visitor is a bot 872b82cba27SSascha Leib _threshold: 100, // above this, it is considered a bot. 873b82cba27SSascha Leib 874b82cba27SSascha Leib // returns a descriptive text for a rule id 875b82cba27SSascha Leib getRuleInfo: function(ruleId) { 876b82cba27SSascha Leib // console.info('getRuleInfo', ruleId); 877b82cba27SSascha Leib 878b82cba27SSascha Leib // shortcut for neater code: 879b82cba27SSascha Leib const me = BotMon.live.data.rules; 880b82cba27SSascha Leib 881b82cba27SSascha Leib for (let i=0; i<me._rulesList.length; i++) { 882b82cba27SSascha Leib const rule = me._rulesList[i]; 883b82cba27SSascha Leib if (rule.id == ruleId) { 884b82cba27SSascha Leib return rule; 885b82cba27SSascha Leib } 886b82cba27SSascha Leib } 887b82cba27SSascha Leib return null; 888b82cba27SSascha Leib 889b82cba27SSascha Leib }, 890b82cba27SSascha Leib 891b82cba27SSascha Leib // evaluate a visitor for lkikelihood of being a bot 892b82cba27SSascha Leib evaluate: function(visitor) { 893b82cba27SSascha Leib 894b82cba27SSascha Leib // shortcut for neater code: 895b82cba27SSascha Leib const me = BotMon.live.data.rules; 896b82cba27SSascha Leib 897b82cba27SSascha Leib let r = { // evaluation result 898b82cba27SSascha Leib 'val': 0, 899b82cba27SSascha Leib 'rules': [], 900b82cba27SSascha Leib 'isBot': false 901b82cba27SSascha Leib }; 902b82cba27SSascha Leib 903b82cba27SSascha Leib for (let i=0; i<me._rulesList.length; i++) { 904b82cba27SSascha Leib const rule = me._rulesList[i]; 905b82cba27SSascha Leib const params = ( rule.params ? rule.params : [] ); 906b82cba27SSascha Leib 907b82cba27SSascha Leib if (rule.func) { // rule is calling a function 908b82cba27SSascha Leib if (me.func[rule.func]) { 909b82cba27SSascha Leib if(me.func[rule.func](visitor, ...params)) { 910b82cba27SSascha Leib r.val += rule.bot; 911b82cba27SSascha Leib r.rules.push(rule.id) 912b82cba27SSascha Leib } 913b82cba27SSascha Leib } else { 914b82cba27SSascha Leib //console.warn("Unknown rule function: “${rule.func}”. Ignoring rule.") 915b82cba27SSascha Leib } 916b82cba27SSascha Leib } 917b82cba27SSascha Leib } 918b82cba27SSascha Leib 919b82cba27SSascha Leib // is a bot? 920b82cba27SSascha Leib r.isBot = (r.val >= me._threshold); 921b82cba27SSascha Leib 922b82cba27SSascha Leib return r; 923b82cba27SSascha Leib }, 924b82cba27SSascha Leib 925b82cba27SSascha Leib // list of functions that can be called by the rules list to evaluate a visitor: 926b82cba27SSascha Leib func: { 927b82cba27SSascha Leib 9286f3fa739SSascha Leib // check if client is on the list passed as parameter: 9296f3fa739SSascha Leib matchesClient: function(visitor, ...clients) { 930b82cba27SSascha Leib 931b82cba27SSascha Leib const clientId = ( visitor._client ? visitor._client.id : ''); 93213592cacSSascha Leib return clients.includes(clientId); 933b82cba27SSascha Leib }, 934b82cba27SSascha Leib 935b82cba27SSascha Leib // check if OS/Platform is one of the obsolete ones: 9366f3fa739SSascha Leib matchesPlatform: function(visitor, ...platforms) { 937b82cba27SSascha Leib 93813592cacSSascha Leib const pId = ( visitor._platform ? visitor._platform.id : ''); 93913592cacSSascha Leib return platforms.includes(pId); 940b82cba27SSascha Leib }, 941b82cba27SSascha Leib 942b82cba27SSascha Leib // are there at lest num pages loaded? 943b82cba27SSascha Leib smallPageCount: function(visitor, num) { 944b82cba27SSascha Leib return (visitor._pageViews.length <= Number(num)); 945b82cba27SSascha Leib }, 946b82cba27SSascha Leib 947446aa816SSascha Leib // There was no entry in a specific log file for this visitor: 948b82cba27SSascha Leib // note that this will also trigger the "noJavaScript" rule: 949446aa816SSascha Leib noRecord: function(visitor, type) { 950446aa816SSascha Leib return !visitor._seenBy.includes(type); 951b82cba27SSascha Leib }, 952b82cba27SSascha Leib 9535526d629SSascha Leib // there are no referrers in any of the page visits: 9545526d629SSascha Leib noReferrer: function(visitor) { 9555526d629SSascha Leib 9565526d629SSascha Leib let r = false; // return value 9575526d629SSascha Leib for (let i = 0; i < visitor._pageViews.length; i++) { 9585526d629SSascha Leib if (!visitor._pageViews[i]._ref) { 9595526d629SSascha Leib r = true; 9605526d629SSascha Leib break; 961b82cba27SSascha Leib } 962b82cba27SSascha Leib } 9635526d629SSascha Leib return r; 9645526d629SSascha Leib }, 9655526d629SSascha Leib 9665526d629SSascha Leib // test for specific client identifiers: 9676f3fa739SSascha Leib /*matchesClients: function(visitor, ...list) { 9685526d629SSascha Leib 9695526d629SSascha Leib for (let i=0; i<list.length; i++) { 9705526d629SSascha Leib if (visitor._client.id == list[i]) { 9715526d629SSascha Leib return true 9725526d629SSascha Leib } 9735526d629SSascha Leib }; 9745526d629SSascha Leib return false; 9756f3fa739SSascha Leib },*/ 9765526d629SSascha Leib 977451abfadSSascha Leib // unusual combinations of Platform and Client: 9786f3fa739SSascha Leib combinationTest: function(visitor, ...combinations) { 9795526d629SSascha Leib 9805526d629SSascha Leib for (let i=0; i<combinations.length; i++) { 9815526d629SSascha Leib 9825526d629SSascha Leib if (visitor._platform.id == combinations[i][0] 9835526d629SSascha Leib && visitor._client.id == combinations[i][1]) { 9845526d629SSascha Leib return true 9855526d629SSascha Leib } 9865526d629SSascha Leib }; 9875526d629SSascha Leib 9885526d629SSascha Leib return false; 9895526d629SSascha Leib }, 9905526d629SSascha Leib 9915526d629SSascha Leib // is the IP address from a known bot network? 9925526d629SSascha Leib fromKnownBotIP: function(visitor) { 9935526d629SSascha Leib 9945526d629SSascha Leib const ipInfo = BotMon.live.data.rules.getBotIPInfo(visitor.ip); 9955526d629SSascha Leib 996446aa816SSascha Leib if (ipInfo) { 997446aa816SSascha Leib visitor._ipInKnownBotRange = true; 998446aa816SSascha Leib } 999446aa816SSascha Leib 10005526d629SSascha Leib return (ipInfo !== null); 1001451abfadSSascha Leib }, 1002451abfadSSascha Leib 1003451abfadSSascha Leib // is the page language mentioned in the client's accepted languages? 1004451abfadSSascha Leib // the parameter holds an array of exceptions, i.e. page languages that should be ignored. 1005451abfadSSascha Leib matchLang: function(visitor, ...exceptions) { 1006451abfadSSascha Leib 100717cb08e8SSascha Leib if (visitor.lang && visitor.accept && exceptions.indexOf(visitor.lang) < 0) { 100817cb08e8SSascha Leib return (visitor.accept.split(',').indexOf(visitor.lang) < 0); 1009451abfadSSascha Leib } 1010451abfadSSascha Leib return false; 1011446aa816SSascha Leib }, 1012446aa816SSascha Leib 101343b05c93SSascha Leib // Is there an accept-language field defined at all? 101443b05c93SSascha Leib noAcceptLang: function(visitor) { 101543b05c93SSascha Leib 101643b05c93SSascha Leib if (!visitor.accept || visitor.accept.length <= 0) { // no accept-languages header 101743b05c93SSascha Leib return true; 101843b05c93SSascha Leib } 101943b05c93SSascha Leib // TODO: parametrize this! 102043b05c93SSascha Leib return false; 102143b05c93SSascha Leib }, 1022446aa816SSascha Leib // At least x page views were recorded, but they come within less than y seconds 1023446aa816SSascha Leib loadSpeed: function(visitor, minItems, maxTime) { 1024446aa816SSascha Leib 1025446aa816SSascha Leib if (visitor._pageViews.length >= minItems) { 1026446aa816SSascha Leib //console.log('loadSpeed', visitor._pageViews.length, minItems, maxTime); 1027446aa816SSascha Leib 1028446aa816SSascha Leib const pvArr = visitor._pageViews.map(pv => pv._lastSeen).sort(); 1029446aa816SSascha Leib 1030446aa816SSascha Leib let totalTime = 0; 1031446aa816SSascha Leib for (let i=1; i < pvArr.length; i++) { 1032446aa816SSascha Leib totalTime += (pvArr[i] - pvArr[i-1]); 1033446aa816SSascha Leib } 1034446aa816SSascha Leib 1035446aa816SSascha Leib //console.log(' ', totalTime , Math.round(totalTime / (pvArr.length * 1000)), (( totalTime / pvArr.length ) <= maxTime * 1000), visitor.ip); 1036446aa816SSascha Leib 1037446aa816SSascha Leib return (( totalTime / pvArr.length ) <= maxTime * 1000); 1038446aa816SSascha Leib } 10395526d629SSascha Leib } 10405526d629SSascha Leib }, 10415526d629SSascha Leib 10425526d629SSascha Leib /* known bot IP ranges: */ 10435526d629SSascha Leib _botIPs: [], 10445526d629SSascha Leib 10455526d629SSascha Leib // return information on a bot IP range: 10465526d629SSascha Leib getBotIPInfo: function(ip) { 10475526d629SSascha Leib 10485526d629SSascha Leib // shortcut to make code more readable: 10495526d629SSascha Leib const me = BotMon.live.data.rules; 10505526d629SSascha Leib 10515526d629SSascha Leib // convert IP address to easier comparable form: 10525526d629SSascha Leib const ipNum = BotMon.t._ip2Num(ip); 10535526d629SSascha Leib 10545526d629SSascha Leib for (let i=0; i < me._botIPs.length; i++) { 10555526d629SSascha Leib const ipRange = me._botIPs[i]; 10565526d629SSascha Leib 10575526d629SSascha Leib if (ipNum >= ipRange.from && ipNum <= ipRange.to) { 10585526d629SSascha Leib return ipRange; 10595526d629SSascha Leib } 10605526d629SSascha Leib 10615526d629SSascha Leib }; 10625526d629SSascha Leib return null; 10635526d629SSascha Leib 10645526d629SSascha Leib } 1065b82cba27SSascha Leib 1066b82cba27SSascha Leib }, 1067b82cba27SSascha Leib 10682f2bc93aSSascha Leib loadLogFile: async function(type, onLoaded = undefined) { 106913592cacSSascha Leib //console.info('BotMon.live.data.loadLogFile(',type,')'); 1070f125bc8dSSascha Leib 1071f125bc8dSSascha Leib let typeName = ''; 1072f125bc8dSSascha Leib let columns = []; 1073f125bc8dSSascha Leib 1074f125bc8dSSascha Leib switch (type) { 1075f125bc8dSSascha Leib case "srv": 1076f125bc8dSSascha Leib typeName = "Server"; 1077451abfadSSascha Leib columns = ['ts','ip','pg','id','typ','usr','agent','ref','lang','accept']; 1078f125bc8dSSascha Leib break; 1079f125bc8dSSascha Leib case "log": 1080f125bc8dSSascha Leib typeName = "Page load"; 108193a5b18bSSascha Leib columns = ['ts','ip','pg','id','usr','lt','ref','agent']; 1082f125bc8dSSascha Leib break; 1083f125bc8dSSascha Leib case "tck": 1084f125bc8dSSascha Leib typeName = "Ticker"; 108593a5b18bSSascha Leib columns = ['ts','ip','pg','id','agent']; 1086f125bc8dSSascha Leib break; 1087f125bc8dSSascha Leib default: 1088f125bc8dSSascha Leib console.warn(`Unknown log type ${type}.`); 1089f125bc8dSSascha Leib return; 1090f125bc8dSSascha Leib } 1091f125bc8dSSascha Leib 10922f2bc93aSSascha Leib // Show the busy indicator and set the visible status: 10937bd08c30SSascha Leib BotMon.live.gui.status.showBusy(`Loading ${typeName} log file …`); 1094f125bc8dSSascha Leib 10952f2bc93aSSascha Leib // compose the URL from which to load: 10967bd08c30SSascha Leib const url = BotMon._baseDir + `logs/${BotMon._today}.${type}.txt`; 10972f2bc93aSSascha Leib //console.log("Loading:",url); 1098f125bc8dSSascha Leib 10992f2bc93aSSascha Leib // fetch the data: 1100f125bc8dSSascha Leib try { 1101f125bc8dSSascha Leib const response = await fetch(url); 1102f125bc8dSSascha Leib if (!response.ok) { 1103f125bc8dSSascha Leib throw new Error(`${response.status} ${response.statusText}`); 1104f125bc8dSSascha Leib } 1105f125bc8dSSascha Leib 11062f2bc93aSSascha Leib const logtxt = await response.text(); 1107f125bc8dSSascha Leib 11082f2bc93aSSascha Leib logtxt.split('\n').forEach((line) => { 11092f2bc93aSSascha Leib if (line.trim() === '') return; // skip empty lines 11102f2bc93aSSascha Leib const cols = line.split('\t'); 11112f2bc93aSSascha Leib 11122f2bc93aSSascha Leib // assign the columns to an object: 11132f2bc93aSSascha Leib const data = {}; 11142f2bc93aSSascha Leib cols.forEach( (colVal,i) => { 11152f2bc93aSSascha Leib colName = columns[i] || `col${i}`; 111643d9de6bSSascha Leib const colValue = (colName == 'ts' ? new Date(colVal) : colVal.trim()); 11172f2bc93aSSascha Leib data[colName] = colValue; 11182f2bc93aSSascha Leib }); 11192f2bc93aSSascha Leib 11202f2bc93aSSascha Leib // register the visit in the model: 11212f2bc93aSSascha Leib switch(type) { 11222f2bc93aSSascha Leib case 'srv': 112343d9de6bSSascha Leib BotMon.live.data.model.registerVisit(data, type); 11242f2bc93aSSascha Leib break; 11252f2bc93aSSascha Leib case 'log': 112643d9de6bSSascha Leib data.typ = 'js'; 11277bd08c30SSascha Leib BotMon.live.data.model.updateVisit(data); 11282f2bc93aSSascha Leib break; 11299f1ee8c1SSascha Leib case 'tck': 113043d9de6bSSascha Leib data.typ = 'js'; 11317bd08c30SSascha Leib BotMon.live.data.model.updateTicks(data); 11329f1ee8c1SSascha Leib break; 11332f2bc93aSSascha Leib default: 11342f2bc93aSSascha Leib console.warn(`Unknown log type ${type}.`); 11352f2bc93aSSascha Leib return; 11362f2bc93aSSascha Leib } 11372f2bc93aSSascha Leib }); 11382f2bc93aSSascha Leib 11392f2bc93aSSascha Leib if (onLoaded) { 11402f2bc93aSSascha Leib onLoaded(); // callback after loading is finished. 11412f2bc93aSSascha Leib } 11422f2bc93aSSascha Leib 1143f125bc8dSSascha Leib } catch (error) { 11447bd08c30SSascha Leib BotMon.live.gui.status.setError(`Error while loading the ${typeName} log file: ${error.message}.`); 1145f125bc8dSSascha Leib } finally { 11467bd08c30SSascha Leib BotMon.live.gui.status.hideBusy("Status: Done."); 1147f125bc8dSSascha Leib } 1148f125bc8dSSascha Leib } 1149f125bc8dSSascha Leib }, 1150f125bc8dSSascha Leib 1151294f6af8SSascha Leib gui: { 115293a5b18bSSascha Leib init: function() { 115393a5b18bSSascha Leib // init the lists view: 115493a5b18bSSascha Leib this.lists.init(); 115593a5b18bSSascha Leib }, 1156294f6af8SSascha Leib 1157294f6af8SSascha Leib overview: { 1158294f6af8SSascha Leib make: function() { 1159f4417fdeSSascha Leib 11607bd08c30SSascha Leib const data = BotMon.live.data.analytics.data; 1161f4417fdeSSascha Leib 1162f4417fdeSSascha Leib // shortcut for neater code: 1163f4417fdeSSascha Leib const makeElement = BotMon.t._makeElement; 1164f4417fdeSSascha Leib 1165*0c039615SSascha Leib const botsVsHumans = document.getElementById('botmon__today__botsvshumans'); 1166*0c039615SSascha Leib if (botsVsHumans) { 1167*0c039615SSascha Leib botsVsHumans.appendChild(makeElement('dt', {}, "Bots vs. Humans (page views)")); 1168ade4db36SSascha Leib 1169*0c039615SSascha Leib for (let i = 3; i >= 0; i--) { 1170*0c039615SSascha Leib const dd = makeElement('dd'); 1171*0c039615SSascha Leib let title = ''; 1172*0c039615SSascha Leib let value = ''; 1173*0c039615SSascha Leib switch(i) { 1174*0c039615SSascha Leib case 0: 1175*0c039615SSascha Leib title = "Registered users:"; 1176*0c039615SSascha Leib value = data.bots.users; 1177*0c039615SSascha Leib break; 1178*0c039615SSascha Leib case 1: 1179*0c039615SSascha Leib title = "Probably humans:"; 1180*0c039615SSascha Leib value = data.bots.human; 1181*0c039615SSascha Leib break; 1182*0c039615SSascha Leib case 2: 1183*0c039615SSascha Leib title = "Suspected bots:"; 1184*0c039615SSascha Leib value = data.bots.suspected; 1185*0c039615SSascha Leib break; 1186*0c039615SSascha Leib case 3: 1187*0c039615SSascha Leib title = "Known bots:"; 1188*0c039615SSascha Leib value = data.bots.known; 1189*0c039615SSascha Leib break; 1190*0c039615SSascha Leib default: 1191*0c039615SSascha Leib console.warn(`Unknown list type ${i}.`); 1192*0c039615SSascha Leib } 1193*0c039615SSascha Leib dd.appendChild(makeElement('span', {}, title)); 1194*0c039615SSascha Leib dd.appendChild(makeElement('strong', {}, value)); 1195*0c039615SSascha Leib botsVsHumans.appendChild(dd); 1196*0c039615SSascha Leib } 1197*0c039615SSascha Leib } 1198f4417fdeSSascha Leib 1199f4417fdeSSascha Leib // update known bots list: 1200*0c039615SSascha Leib const botlist = document.getElementById('botmon__botslist'); 1201*0c039615SSascha Leib botlist.innerHTML = "<dt>Top 5 known bots (page views)</dt>"; 1202f4417fdeSSascha Leib 1203f4417fdeSSascha Leib let bots = BotMon.live.data.analytics.groups.knownBots.toSorted( (a, b) => { 1204f4417fdeSSascha Leib return b._pageViews.length - a._pageViews.length; 1205f4417fdeSSascha Leib }); 1206f4417fdeSSascha Leib 1207*0c039615SSascha Leib for (let i=0; i < Math.min(bots.length, 5); i++) { 1208f4417fdeSSascha Leib const dd = makeElement('dd'); 1209f4417fdeSSascha Leib dd.appendChild(makeElement('span', {'class': 'bot bot_' + bots[i]._bot.id }, bots[i]._bot.n)); 1210b82cba27SSascha Leib dd.appendChild(makeElement('strong', undefined, bots[i]._pageViews.length)); 1211*0c039615SSascha Leib botlist.appendChild(dd); 1212*0c039615SSascha Leib } 1213*0c039615SSascha Leib 1214*0c039615SSascha Leib // update the suspected bot IP ranges list: 1215*0c039615SSascha Leib const botIps = document.getElementById('botmon__today__botips'); 1216*0c039615SSascha Leib if (botIps) { 1217*0c039615SSascha Leib botIps.appendChild(makeElement('dt', {}, "Top 5 suspected bots’ IP ranges")); 1218*0c039615SSascha Leib 1219*0c039615SSascha Leib const ipList = BotMon.live.data.analytics.getTopBotIPRanges(5); 1220*0c039615SSascha Leib ipList.forEach( (ipInfo) => { 1221*0c039615SSascha Leib const li = makeElement('dd'); 1222*0c039615SSascha Leib li.appendChild(makeElement('span', {'class': 'ip ip' + ipInfo.typ }, ipInfo.ip)); 1223*0c039615SSascha Leib li.appendChild(makeElement('span', {'class': 'count' }, ipInfo.num)); 1224*0c039615SSascha Leib botIps.append(li) 1225*0c039615SSascha Leib }) 1226*0c039615SSascha Leib } 1227*0c039615SSascha Leib 1228*0c039615SSascha Leib // update the webmetrics overview: 1229*0c039615SSascha Leib const wmoverview = document.getElementById('botmon__today__wm_overview'); 1230*0c039615SSascha Leib if (wmoverview) { 1231*0c039615SSascha Leib const bounceRate = Math.round(data.totalVisits / data.totalPageViews * 100); 1232*0c039615SSascha Leib 1233*0c039615SSascha Leib wmoverview.appendChild(makeElement('dt', {}, "Overview")); 1234*0c039615SSascha Leib for (let i = 0; i < 3; i++) { 1235*0c039615SSascha Leib const dd = makeElement('dd'); 1236*0c039615SSascha Leib let title = ''; 1237*0c039615SSascha Leib let value = ''; 1238*0c039615SSascha Leib switch(i) { 1239*0c039615SSascha Leib case 0: 1240*0c039615SSascha Leib title = "Total page views:"; 1241*0c039615SSascha Leib value = data.totalPageViews; 1242*0c039615SSascha Leib break; 1243*0c039615SSascha Leib case 1: 1244*0c039615SSascha Leib title = "Total visitors (est.):"; 1245*0c039615SSascha Leib value = data.totalVisits; 1246*0c039615SSascha Leib break; 1247*0c039615SSascha Leib case 2: 1248*0c039615SSascha Leib title = "Bounce rate (est.):"; 1249*0c039615SSascha Leib value = bounceRate + '%'; 1250*0c039615SSascha Leib break; 1251*0c039615SSascha Leib default: 1252*0c039615SSascha Leib console.warn(`Unknown list type ${i}.`); 1253*0c039615SSascha Leib } 1254*0c039615SSascha Leib dd.appendChild(makeElement('span', {}, title)); 1255*0c039615SSascha Leib dd.appendChild(makeElement('strong', {}, value)); 1256*0c039615SSascha Leib wmoverview.appendChild(dd); 1257f4417fdeSSascha Leib } 1258294f6af8SSascha Leib } 1259*0c039615SSascha Leib 1260294f6af8SSascha Leib } 1261294f6af8SSascha Leib }, 1262f4417fdeSSascha Leib 1263f125bc8dSSascha Leib status: { 1264f125bc8dSSascha Leib setText: function(txt) { 12657bd08c30SSascha Leib const el = document.getElementById('botmon__today__status'); 12667bd08c30SSascha Leib if (el && BotMon.live.gui.status._errorCount <= 0) { 1267f125bc8dSSascha Leib el.innerText = txt; 1268f125bc8dSSascha Leib } 1269f125bc8dSSascha Leib }, 1270f125bc8dSSascha Leib 1271f125bc8dSSascha Leib setTitle: function(html) { 12727bd08c30SSascha Leib const el = document.getElementById('botmon__today__title'); 1273f125bc8dSSascha Leib if (el) { 1274f125bc8dSSascha Leib el.innerHTML = html; 1275f125bc8dSSascha Leib } 1276f125bc8dSSascha Leib }, 1277f125bc8dSSascha Leib 1278f125bc8dSSascha Leib setError: function(txt) { 1279f125bc8dSSascha Leib console.error(txt); 12807bd08c30SSascha Leib BotMon.live.gui.status._errorCount += 1; 12817bd08c30SSascha Leib const el = document.getElementById('botmon__today__status'); 1282f125bc8dSSascha Leib if (el) { 1283f125bc8dSSascha Leib el.innerText = "An error occured. See the browser log for details!"; 1284f125bc8dSSascha Leib el.classList.add('error'); 1285f125bc8dSSascha Leib } 1286f125bc8dSSascha Leib }, 1287f125bc8dSSascha Leib _errorCount: 0, 1288f125bc8dSSascha Leib 1289f125bc8dSSascha Leib showBusy: function(txt = null) { 12907bd08c30SSascha Leib BotMon.live.gui.status._busyCount += 1; 12917bd08c30SSascha Leib const el = document.getElementById('botmon__today__busy'); 1292f125bc8dSSascha Leib if (el) { 1293f125bc8dSSascha Leib el.style.display = 'inline-block'; 1294f125bc8dSSascha Leib } 12957bd08c30SSascha Leib if (txt) BotMon.live.gui.status.setText(txt); 1296f125bc8dSSascha Leib }, 1297f125bc8dSSascha Leib _busyCount: 0, 1298f125bc8dSSascha Leib 1299f125bc8dSSascha Leib hideBusy: function(txt = null) { 13007bd08c30SSascha Leib const el = document.getElementById('botmon__today__busy'); 13017bd08c30SSascha Leib BotMon.live.gui.status._busyCount -= 1; 13027bd08c30SSascha Leib if (BotMon.live.gui.status._busyCount <= 0) { 1303f125bc8dSSascha Leib if (el) el.style.display = 'none'; 13047bd08c30SSascha Leib if (txt) BotMon.live.gui.status.setText(txt); 1305f125bc8dSSascha Leib } 1306f125bc8dSSascha Leib } 130793a5b18bSSascha Leib }, 130893a5b18bSSascha Leib 130993a5b18bSSascha Leib lists: { 131093a5b18bSSascha Leib init: function() { 131193a5b18bSSascha Leib 131213592cacSSascha Leib // function shortcut: 131313592cacSSascha Leib const makeElement = BotMon.t._makeElement; 131413592cacSSascha Leib 131593a5b18bSSascha Leib const parent = document.getElementById('botmon__today__visitorlists'); 131693a5b18bSSascha Leib if (parent) { 131793a5b18bSSascha Leib 131893a5b18bSSascha Leib for (let i=0; i < 4; i++) { 131993a5b18bSSascha Leib 132093a5b18bSSascha Leib // change the id and title by number: 132193a5b18bSSascha Leib let listTitle = ''; 132293a5b18bSSascha Leib let listId = ''; 132393a5b18bSSascha Leib switch (i) { 132493a5b18bSSascha Leib case 0: 132593a5b18bSSascha Leib listTitle = "Registered users"; 132693a5b18bSSascha Leib listId = 'users'; 132793a5b18bSSascha Leib break; 132893a5b18bSSascha Leib case 1: 132993a5b18bSSascha Leib listTitle = "Probably humans"; 133093a5b18bSSascha Leib listId = 'humans'; 133193a5b18bSSascha Leib break; 133293a5b18bSSascha Leib case 2: 133393a5b18bSSascha Leib listTitle = "Suspected bots"; 133493a5b18bSSascha Leib listId = 'suspectedBots'; 133593a5b18bSSascha Leib break; 133693a5b18bSSascha Leib case 3: 133793a5b18bSSascha Leib listTitle = "Known bots"; 133893a5b18bSSascha Leib listId = 'knownBots'; 133993a5b18bSSascha Leib break; 134093a5b18bSSascha Leib default: 13414c5062c1SSascha Leib console.warn('Unknown list number.'); 1342f125bc8dSSascha Leib } 1343294f6af8SSascha Leib 134413592cacSSascha Leib const details = makeElement('details', { 134593a5b18bSSascha Leib 'data-group': listId, 134693a5b18bSSascha Leib 'data-loaded': false 134793a5b18bSSascha Leib }); 134813592cacSSascha Leib const title = details.appendChild(makeElement('summary')); 13494c5062c1SSascha Leib title.appendChild(makeElement('span', {'class':'title'}, listTitle)); 13504c5062c1SSascha Leib title.appendChild(makeElement('span', {'class':'counter'}, '–')); 135193a5b18bSSascha Leib details.addEventListener("toggle", this._onDetailsToggle); 135293a5b18bSSascha Leib 135393a5b18bSSascha Leib parent.appendChild(details); 135493a5b18bSSascha Leib 135593a5b18bSSascha Leib } 135693a5b18bSSascha Leib } 135793a5b18bSSascha Leib }, 135893a5b18bSSascha Leib 135993a5b18bSSascha Leib _onDetailsToggle: function(e) { 1360f4417fdeSSascha Leib //console.info('BotMon.live.gui.lists._onDetailsToggle()'); 136193a5b18bSSascha Leib 136293a5b18bSSascha Leib const target = e.target; 136393a5b18bSSascha Leib 136493a5b18bSSascha Leib if (target.getAttribute('data-loaded') == 'false') { // only if not loaded yet 136593a5b18bSSascha Leib target.setAttribute('data-loaded', 'loading'); 136693a5b18bSSascha Leib 136793a5b18bSSascha Leib const fillType = target.getAttribute('data-group'); 136893a5b18bSSascha Leib const fillList = BotMon.live.data.analytics.groups[fillType]; 136993a5b18bSSascha Leib if (fillList && fillList.length > 0) { 137093a5b18bSSascha Leib 137193a5b18bSSascha Leib const ul = BotMon.t._makeElement('ul'); 137293a5b18bSSascha Leib 137393a5b18bSSascha Leib fillList.forEach( (it) => { 137493a5b18bSSascha Leib ul.appendChild(BotMon.live.gui.lists._makeVisitorItem(it, fillType)); 137593a5b18bSSascha Leib }); 137693a5b18bSSascha Leib 137793a5b18bSSascha Leib target.appendChild(ul); 137893a5b18bSSascha Leib target.setAttribute('data-loaded', 'true'); 137993a5b18bSSascha Leib } else { 138093a5b18bSSascha Leib target.setAttribute('data-loaded', 'false'); 138193a5b18bSSascha Leib } 138293a5b18bSSascha Leib 138393a5b18bSSascha Leib } 138493a5b18bSSascha Leib }, 138593a5b18bSSascha Leib 138693a5b18bSSascha Leib _makeVisitorItem: function(data, type) { 138793a5b18bSSascha Leib 138893a5b18bSSascha Leib // shortcut for neater code: 138993a5b18bSSascha Leib const make = BotMon.t._makeElement; 139093a5b18bSSascha Leib 139143d9de6bSSascha Leib let ipType = ( data.ip.indexOf(':') >= 0 ? '6' : '4' ); 13925526d629SSascha Leib const platformName = (data._platform ? data._platform.n : 'Unknown'); 13935526d629SSascha Leib const clientName = (data._client ? data._client.n: 'Unknown'); 139443d9de6bSSascha Leib 139593a5b18bSSascha Leib const li = make('li'); // root list item 139693a5b18bSSascha Leib const details = make('details'); 139793a5b18bSSascha Leib const summary = make('summary'); 139893a5b18bSSascha Leib details.appendChild(summary); 139993a5b18bSSascha Leib 140093a5b18bSSascha Leib const span1 = make('span'); /* left-hand group */ 1401f4417fdeSSascha Leib if (data._type == BM_USERTYPE.KNOWN_BOT) { /* Bot only */ 140293a5b18bSSascha Leib 1403259d3b85SSascha Leib const botName = ( data._bot && data._bot.n ? data._bot.n : "Unknown"); 140443d9de6bSSascha Leib span1.appendChild(make('span', { /* Bot */ 140543d9de6bSSascha Leib 'class': 'bot bot_' + (data._bot ? data._bot.id : 'unknown'), 1406259d3b85SSascha Leib 'title': "Bot: " + botName 1407259d3b85SSascha Leib }, botName)); 140843d9de6bSSascha Leib 1409f4417fdeSSascha Leib } else if (data._type == BM_USERTYPE.KNOWN_USER) { /* User only */ 141043d9de6bSSascha Leib 141143d9de6bSSascha Leib span1.appendChild(make('span', { /* User */ 1412f4417fdeSSascha Leib 'class': 'user_known', 141343d9de6bSSascha Leib 'title': "User: " + data.usr 141443d9de6bSSascha Leib }, data.usr)); 141543d9de6bSSascha Leib 141643d9de6bSSascha Leib } else { /* others */ 141743d9de6bSSascha Leib 141843d9de6bSSascha Leib if (data.ip == '127.0.0.1' || data.ip == '::1' ) ipType = '0'; 141943d9de6bSSascha Leib span1.appendChild(make('span', { /* IP-Address */ 142043d9de6bSSascha Leib 'class': 'ipaddr ip' + ipType, 142143d9de6bSSascha Leib 'title': "IP-Address: " + data.ip 142243d9de6bSSascha Leib }, data.ip)); 142343d9de6bSSascha Leib 142443d9de6bSSascha Leib } 142593a5b18bSSascha Leib 1426f4417fdeSSascha Leib if (data._type !== BM_USERTYPE.KNOWN_BOT) { /* Not for bots */ 142793a5b18bSSascha Leib span1.appendChild(make('span', { /* Platform */ 142893a5b18bSSascha Leib 'class': 'icon platform platform_' + (data._platform ? data._platform.id : 'unknown'), 142993a5b18bSSascha Leib 'title': "Platform: " + platformName 143093a5b18bSSascha Leib }, platformName)); 143193a5b18bSSascha Leib 143293a5b18bSSascha Leib span1.appendChild(make('span', { /* Client */ 143393a5b18bSSascha Leib 'class': 'icon client client_' + (data._client ? data._client.id : 'unknown'), 143493a5b18bSSascha Leib 'title': "Client: " + clientName 143593a5b18bSSascha Leib }, clientName)); 1436f4417fdeSSascha Leib } 143793a5b18bSSascha Leib 143893a5b18bSSascha Leib summary.appendChild(span1); 143993a5b18bSSascha Leib const span2 = make('span'); /* right-hand group */ 144093a5b18bSSascha Leib 1441f4417fdeSSascha Leib span2.appendChild(make('span', { /* page views */ 1442f4417fdeSSascha Leib 'class': 'pageviews' 1443f4417fdeSSascha Leib }, data._pageViews.length)); 144493a5b18bSSascha Leib 144593a5b18bSSascha Leib summary.appendChild(span2); 144693a5b18bSSascha Leib 14475526d629SSascha Leib // add details expandable section: 14485526d629SSascha Leib details.appendChild(BotMon.live.gui.lists._makeVisitorDetails(data, type)); 14495526d629SSascha Leib 14505526d629SSascha Leib li.appendChild(details); 14515526d629SSascha Leib return li; 14525526d629SSascha Leib }, 14535526d629SSascha Leib 14545526d629SSascha Leib _makeVisitorDetails: function(data, type) { 14555526d629SSascha Leib 14565526d629SSascha Leib // shortcut for neater code: 14575526d629SSascha Leib const make = BotMon.t._makeElement; 14585526d629SSascha Leib 14595526d629SSascha Leib let ipType = ( data.ip.indexOf(':') >= 0 ? '6' : '4' ); 14605526d629SSascha Leib if (data.ip == '127.0.0.1' || data.ip == '::1' ) ipType = '0'; 14615526d629SSascha Leib const platformName = (data._platform ? data._platform.n : 'Unknown'); 14625526d629SSascha Leib const clientName = (data._client ? data._client.n: 'Unknown'); 146393a5b18bSSascha Leib 146493a5b18bSSascha Leib const dl = make('dl', {'class': 'visitor_details'}); 146593a5b18bSSascha Leib 1466f4417fdeSSascha Leib if (data._type == BM_USERTYPE.KNOWN_BOT) { 1467f4417fdeSSascha Leib 1468f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Bot name:")); /* bot info */ 146943d9de6bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon bot bot_' + (data._bot ? data._bot.id : 'unknown')}, 147043d9de6bSSascha Leib (data._bot ? data._bot.n : 'Unknown'))); 1471f4417fdeSSascha Leib 1472f4417fdeSSascha Leib if (data._bot && data._bot.url) { 1473f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Bot info:")); /* bot info */ 1474f4417fdeSSascha Leib const botInfoDd = dl.appendChild(make('dd')); 1475f4417fdeSSascha Leib botInfoDd.appendChild(make('a', { 1476f4417fdeSSascha Leib 'href': data._bot.url, 1477f4417fdeSSascha Leib 'target': '_blank' 1478f4417fdeSSascha Leib }, data._bot.url)); /* bot info link*/ 1479f4417fdeSSascha Leib 148043d9de6bSSascha Leib } 148143d9de6bSSascha Leib 1482f4417fdeSSascha Leib } else { /* not for bots */ 1483f4417fdeSSascha Leib 148493a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Client:")); /* client */ 148593a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon client_' + (data._client ? data._client.id : 'unknown')}, 148693a5b18bSSascha Leib clientName + ( data._client.v > 0 ? ' (' + data._client.v + ')' : '' ) )); 148793a5b18bSSascha Leib 148893a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Platform:")); /* platform */ 148993a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon platform_' + (data._platform ? data._platform.id : 'unknown')}, 149093a5b18bSSascha Leib platformName + ( data._platform.v > 0 ? ' (' + data._platform.v + ')' : '' ) )); 149193a5b18bSSascha Leib 149293a5b18bSSascha Leib dl.appendChild(make('dt', {}, "IP-Address:")); 149393a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon ip' + ipType}, data.ip)); 1494b2e3bd8bSSascha Leib 14955d78a53bSSascha Leib /*dl.appendChild(make('dt', {}, "ID:")); 14965d78a53bSSascha Leib dl.appendChild(make('dd', {'class': 'has_icon ip' + data.typ}, data.id));*/ 1497259d3b85SSascha Leib } 149893a5b18bSSascha Leib 1499446aa816SSascha Leib if (Math.abs(data._lastSeen - data._firstSeen) < 100) { 150093a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Seen:")); 150193a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'seen'}, data._firstSeen.toLocaleString())); 150293a5b18bSSascha Leib } else { 150393a5b18bSSascha Leib dl.appendChild(make('dt', {}, "First seen:")); 150493a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'firstSeen'}, data._firstSeen.toLocaleString())); 150593a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Last seen:")); 150693a5b18bSSascha Leib dl.appendChild(make('dd', {'class': 'lastSeen'}, data._lastSeen.toLocaleString())); 150793a5b18bSSascha Leib } 150893a5b18bSSascha Leib 150993a5b18bSSascha Leib dl.appendChild(make('dt', {}, "User-Agent:")); 15105526d629SSascha Leib dl.appendChild(make('dd', {'class': 'agent'}, data.agent)); 151193a5b18bSSascha Leib 15126f3fa739SSascha Leib dl.appendChild(make('dt', {}, "Languages:")); 15136f3fa739SSascha Leib dl.appendChild(make('dd', {'class': 'langs'}, "Client accepts: [" + data.accept + "]; Page: [" + data.lang + ']')); 15146f3fa739SSascha Leib 15155d78a53bSSascha Leib /*dl.appendChild(make('dt', {}, "Visitor Type:")); 15165d78a53bSSascha Leib dl.appendChild(make('dd', undefined, data._type ));*/ 1517f4417fdeSSascha Leib 1518f4417fdeSSascha Leib dl.appendChild(make('dt', {}, "Seen by:")); 1519f4417fdeSSascha Leib dl.appendChild(make('dd', undefined, data._seenBy.join(', ') )); 1520f4417fdeSSascha Leib 152193a5b18bSSascha Leib dl.appendChild(make('dt', {}, "Visited pages:")); 152293a5b18bSSascha Leib const pagesDd = make('dd', {'class': 'pages'}); 152393a5b18bSSascha Leib const pageList = make('ul'); 15245526d629SSascha Leib 15255526d629SSascha Leib /* list all page views */ 152693a5b18bSSascha Leib data._pageViews.forEach( (page) => { 152793a5b18bSSascha Leib const pgLi = make('li'); 152893a5b18bSSascha Leib 152993a5b18bSSascha Leib let visitTimeStr = "Bounce"; 153093a5b18bSSascha Leib const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime(); 153193a5b18bSSascha Leib if (visitDuration > 0) { 153293a5b18bSSascha Leib visitTimeStr = Math.floor(visitDuration / 1000) + "s"; 153393a5b18bSSascha Leib } 153493a5b18bSSascha Leib 15355526d629SSascha Leib pgLi.appendChild(make('span', {}, page.pg)); /* DW Page ID */ 15365526d629SSascha Leib if (page._ref) { 15375526d629SSascha Leib pgLi.appendChild(make('span', { 15385526d629SSascha Leib 'data-ref': page._ref.host, 15395526d629SSascha Leib 'title': "Referrer: " + page._ref.full 15405526d629SSascha Leib }, page._ref.site)); 15415526d629SSascha Leib } else { 15425526d629SSascha Leib pgLi.appendChild(make('span', { 15435526d629SSascha Leib }, "No referer")); 15445526d629SSascha Leib } 1545259d3b85SSascha Leib pgLi.appendChild(make('span', {}, ( page._seenBy ? page._seenBy.join(', ') : '—') + '; ' + page._tickCount)); 1546446aa816SSascha Leib pgLi.appendChild(make('span', {}, BotMon.t._formatTime(page._firstSeen))); 1547446aa816SSascha Leib 1548446aa816SSascha Leib // get the time difference: 1549446aa816SSascha Leib const tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen); 1550446aa816SSascha Leib if (tDiff) { 1551446aa816SSascha Leib pgLi.appendChild(make('span', {'class': 'visit-length', 'title': 'Last seen: ' + page._lastSeen.toLocaleString()}, tDiff)); 1552446aa816SSascha Leib } else { 1553446aa816SSascha Leib pgLi.appendChild(make('span', {'class': 'bounce'}, "Bounce")); 1554446aa816SSascha Leib } 1555446aa816SSascha Leib 155693a5b18bSSascha Leib pageList.appendChild(pgLi); 155793a5b18bSSascha Leib }); 155893a5b18bSSascha Leib pagesDd.appendChild(pageList); 155993a5b18bSSascha Leib dl.appendChild(pagesDd); 156093a5b18bSSascha Leib 1561446aa816SSascha Leib /* bot evaluation rating */ 15625d78a53bSSascha Leib if (data._type !== BM_USERTYPE.KNOWN_BOT && data._type !== BM_USERTYPE.KNOWN_USER) { 1563446aa816SSascha Leib dl.appendChild(make('dt', undefined, "Bot rating:")); 15645d78a53bSSascha Leib dl.appendChild(make('dd', {'class': 'bot-rating'}, ( data._botVal ? data._botVal : '–' ) + '/' + BotMon.live.data.rules._threshold )); 1565446aa816SSascha Leib 1566446aa816SSascha Leib /* add bot evaluation details: */ 15671c16f1b7SSascha Leib if (data._eval) { 1568446aa816SSascha Leib dl.appendChild(make('dt', {}, "Bot evaluation details:")); 1569b82cba27SSascha Leib const evalDd = make('dd'); 1570bc55f6a6SSascha Leib const testList = make('ul',{ 1571bc55f6a6SSascha Leib 'class': 'eval' 1572bc55f6a6SSascha Leib }); 15735526d629SSascha Leib data._eval.forEach( test => { 1574b82cba27SSascha Leib 1575b82cba27SSascha Leib const tObj = BotMon.live.data.rules.getRuleInfo(test); 15765526d629SSascha Leib let tDesc = tObj ? tObj.desc : test; 1577b82cba27SSascha Leib 15785526d629SSascha Leib // special case for Bot IP range test: 15795526d629SSascha Leib if (tObj.func == 'fromKnownBotIP') { 15805526d629SSascha Leib const rangeInfo = BotMon.live.data.rules.getBotIPInfo(data.ip); 15815526d629SSascha Leib if (rangeInfo) { 15826f3fa739SSascha Leib tDesc += ' (' + (rangeInfo.label ? rangeInfo.label : 'Unknown') + ')'; 15835526d629SSascha Leib } 15845526d629SSascha Leib } 15855526d629SSascha Leib 15865526d629SSascha Leib // create the entry field 1587b82cba27SSascha Leib const tstLi = make('li'); 1588b82cba27SSascha Leib tstLi.appendChild(make('span', { 15895526d629SSascha Leib 'data-testid': test 15905526d629SSascha Leib }, tDesc)); 1591b82cba27SSascha Leib tstLi.appendChild(make('span', {}, ( tObj ? tObj.bot : '—') )); 1592b82cba27SSascha Leib testList.appendChild(tstLi); 1593b82cba27SSascha Leib }); 1594b82cba27SSascha Leib 15955526d629SSascha Leib // add total row 1596bc55f6a6SSascha Leib const tst2Li = make('li', { 1597bc55f6a6SSascha Leib 'class': 'total' 1598bc55f6a6SSascha Leib }); 1599446aa816SSascha Leib /*tst2Li.appendChild(make('span', {}, "Total:")); 1600b82cba27SSascha Leib tst2Li.appendChild(make('span', {}, data._botVal)); 1601446aa816SSascha Leib testList.appendChild(tst2Li);*/ 1602b82cba27SSascha Leib 1603b82cba27SSascha Leib evalDd.appendChild(testList); 1604b82cba27SSascha Leib dl.appendChild(evalDd); 1605bc55f6a6SSascha Leib } 16065d78a53bSSascha Leib } 16075d78a53bSSascha Leib // return the element to add to the UI: 16085526d629SSascha Leib return dl; 160993a5b18bSSascha Leib } 1610f4417fdeSSascha Leib 161193a5b18bSSascha Leib } 1612294f6af8SSascha Leib } 1613c7931771SSascha Leib}; 1614f125bc8dSSascha Leib 16157bd08c30SSascha Leib/* launch only if the BotMon admin panel is open: */ 16167bd08c30SSascha Leibif (document.getElementById('botmon__admin')) { 16177bd08c30SSascha Leib BotMon.init(); 1618f125bc8dSSascha Leib}