diff --git a/action.php b/action.php index 7cac917..319d45d 100644 --- a/action.php +++ b/action.php @@ -21,9 +21,15 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { */ public function register(EventHandler $controller) { - // insert header data into the page: - $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader'); + global $ACT; + // insert header data into the page: + if ($ACT == 'show') { + $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader'); + } else if ($ACT == 'admin' && isset($_REQUEST['page']) && $_REQUEST['page'] == 'botmon') { + $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertAdminHeader'); + } + // write to the log after the page content was displayed: $controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog'); @@ -36,6 +42,7 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { /** * Inserts tracking code to the page header + * (only called on 'show' actions) * * @param Event $event event object by reference * @return void @@ -51,22 +58,36 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ? $INFO['userinfo']['name'] : ''); // build the tracker code: - $code = NL . DOKU_TAB . "document._botmon = {'t0': Date.now(), 'session': '" . json_encode($this->sessionId) . "'};" . NL; + $code = "document._botmon = {'t0': Date.now(), 'session': '" . json_encode($this->sessionId) . "'};" . NL; if ($username) { - $code .= DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; + $code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; } // add the deferred script loader:: - $code .= DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; - $code .= DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL; - $code .= DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL; - $code .= DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL; - $code .= DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL; - $code .= DOKU_TAB . "});" . NL . DOKU_TAB; + $code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; + $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "const e=document.createElement('script');" . NL; + $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.async=true;e.defer=true;" . NL; + $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "e.src='".DOKU_BASE."lib/plugins/botmon/client.js';" . NL; + $code .= DOKU_TAB . DOKU_TAB . DOKU_TAB . "document.getElementsByTagName('head')[0].appendChild(e);" . NL; + $code .= DOKU_TAB . DOKU_TAB . "});"; $event->data['script'][] = ['_data' => $code]; } + /** + * Inserts tracking code to the page header + * (only called on 'show' actions) + * + * @param Event $event event object by reference + * @return void + */ + public function insertAdminHeader(Event $event, $param) { + + $event->data['link'][] = ['rel' => 'stylesheet', 'href' => DOKU_BASE.'lib/plugins/botmon/admin.css', 'defer' => 'defer']; + $event->data['script'][] = ['href' => DOKU_BASE.'lib/plugins/botmon/admin.js', 'defer' => 'defer', '_data' => '']; + } + + /** * Writes data to the server log. * diff --git a/style.less b/admin.css similarity index 100% rename from style.less rename to admin.css diff --git a/script.js b/admin.js similarity index 96% rename from script.js rename to admin.js index cdc3dde..14da250 100644 --- a/script.js +++ b/admin.js @@ -1,6 +1,6 @@ "use strict"; /* DokuWiki BotMon Plugin Script file */ -/* 12.09.2025 - 0.3.0 - beta */ +/* 14.10.2025 - 0.5.0 - pre-release */ /* Author: Sascha Leib */ // enumeration of user types: @@ -607,13 +607,12 @@ BotMon.live = { } // perform actions depending on the visitor type: - if (v._type == BM_USERTYPE.KNOWN_BOT || v._type == BM_USERTYPE.LIKELY_BOT) { /* bots only */ - - // add bot views to IP range information: - /*v._pageViews.forEach( pv => { - me.addToIPRanges(pv.ip); - });*/ + if (v._type == BM_USERTYPE.KNOWN_BOT ) { /* known bots only */ + } else if (v._type == BM_USERTYPE.LIKELY_BOT) { /* probable bots only */ + + // add bot views to IP range information: + me.addToIpRanges(v); } else { /* humans only */ @@ -1001,7 +1000,44 @@ BotMon.live = { }); return bounces; - } + }, + + _ipOwners: [], + + /* adds a visit to the ip ranges arrays */ + addToIpRanges: function(v) { + //console.info('addToIpRanges', v.ip); + + const me = BotMon.live.data.analytics; + const ipRanges = BotMon.live.data.ipRanges; + + let isp = 'null'; // default ISP id + let name = 'Unknown'; // default ISP name + + // is there already a known IP range assigned? + if (v._ipRange) { + isp = v._ipRange.g; + } + + let ispRec = me._ipOwners.find( it => it.id == isp); + if (!ispRec) { + ispRec = { + 'id': isp, + 'n': ipRanges.getOwner( isp ) || "Unknown", + 'count': v._pageViews.length + }; + me._ipOwners.push(ispRec); + } else { + ispRec.count += v._pageViews.length; + } + }, + + getTopBotISPs: function(max) { + + const me = BotMon.live.data.analytics; + + return me._makeTopList(me._ipOwners, max); + }, }, // information on "known bots": @@ -1272,17 +1308,17 @@ BotMon.live = { }, - getOwner: function(rangeInfo) { + getOwner: function(gid) { const me = BotMon.live.data.ipRanges; for (let i=0; i < me._groups.length; i++) { const it = me._groups[i]; - if (it.id == rangeInfo.g) { + if (it.id == gid) { return it.name; } } - return `Unknown (“${rangeInfo.g})`; + return null; }, match: function(ip) { @@ -1482,6 +1518,7 @@ BotMon.live = { if (ipInfo) { visitor._ipInKnownBotRange = true; + visitor._ipRange = ipInfo; } return (ipInfo !== null); @@ -1715,32 +1752,36 @@ BotMon.live = { const botsVsHumans = document.getElementById('botmon__today__botsvshumans'); if (botsVsHumans) { - botsVsHumans.appendChild(makeElement('dt', {}, "Bots’ metrics:")); + botsVsHumans.appendChild(makeElement('dt', {}, "Page views")); - for (let i = 0; i <= 4; i++) { + for (let i = 0; i <= 5; i++) { const dd = makeElement('dd'); let title = ''; let value = ''; switch(i) { case 0: - title = "Page views by known bots:"; + title = "Known bots:"; value = data.bots.known; break; case 1: - title = "Page views by suspected bots:"; + title = "Suspected bots:"; value = data.bots.suspected; break; case 2: - title = "Page views by humans:"; - value = data.bots.users + data.bots.human; + title = "Probably humans:"; + value = data.bots.human; break; case 3: - title = "Total page views:"; - value = data.totalPageViews; + title = "Registered users:"; + value = data.bots.users; break; case 4: - title = "Humans-to-bots ratio:"; - value = BotMon.t._getRatio(data.bots.users + data.bots.human, data.bots.suspected + data.bots.known, 100); + title = "Total:"; + value = data.totalPageViews; + break; + case 5: + title = "Bots-humans ratio:"; + value = BotMon.t._getRatio(data.bots.suspected + data.bots.known, data.bots.users + data.bots.human, 100); break; default: console.warn(`Unknown list type ${i}.`); @@ -1754,7 +1795,7 @@ BotMon.live = { // update known bots list: const botElement = document.getElementById('botmon__botslist'); /* Known bots */ if (botElement) { - botElement.innerHTML = `
Top known bots:
`; + botElement.appendChild(makeElement('dt', {}, `Top known bots`)); let botList = BotMon.live.data.analytics.getTopBots(maxItemsPerList); botList.forEach( (botInfo) => { @@ -1766,23 +1807,24 @@ BotMon.live = { } // update the suspected bot IP ranges list: - /*const botIps = document.getElementById('botmon__botips'); + const botIps = document.getElementById('botmon__botips'); if (botIps) { - botIps.appendChild(makeElement('dt', {}, "Bot IP ranges (top 5)")); + botIps.appendChild(makeElement('dt', {}, "Top bot ISPs")); - const ipList = BotMon.live.data.analytics.getTopBotIPRanges(5); - ipList.forEach( (ipInfo) => { + const ispList = BotMon.live.data.analytics.getTopBotISPs(5); + console.log(ispList); + ispList.forEach( (netInfo) => { const li = makeElement('dd'); - li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip' + ipInfo.typ }, ipInfo.ip)); - li.appendChild(makeElement('span', {'class': 'count' }, ipInfo.num)); + li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip_' + netInfo.id }, netInfo.name)); + li.appendChild(makeElement('span', {'class': 'count' }, netInfo.count)); botIps.append(li) }); - }*/ + } // update the top bot countries list: const botCountries = document.getElementById('botmon__botcountries'); if (botCountries) { - botCountries.appendChild(makeElement('dt', {}, `Top bot Countries:`)); + botCountries.appendChild(makeElement('dt', {}, `Top bot Countries`)); const countryList = BotMon.live.data.analytics.getCountryList('bot', 5); countryList.forEach( (cInfo) => { const cLi = makeElement('dd'); @@ -2292,7 +2334,7 @@ BotMon.live = { if (tObj.func == 'fromKnownBotIP') { const rangeInfo = BotMon.live.data.ipRanges.match(data.ip); if (rangeInfo) { - const owner = BotMon.live.data.ipRanges.getOwner(rangeInfo); + const owner = BotMon.live.data.ipRanges.getOwner(rangeInfo.g); tDesc += ' (range: “' + rangeInfo.cidr + '”, ' + owner + ')'; } } diff --git a/admin.php b/admin.php index 63a5efc..79b249f 100644 --- a/admin.php +++ b/admin.php @@ -58,11 +58,11 @@ class admin_plugin_botmon extends AdminPlugin {
Loading …
- Bots overview + Overview
-
Suspected bots’ top IP ranges
TODO
+
diff --git a/img/addr.png b/img/addr.png index a99f874..1a46134 100644 Binary files a/img/addr.png and b/img/addr.png differ