1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
logs/*.log.txt
|
||||
logs/*.srv.txt
|
||||
logs/*.tck.txt
|
||||
config/user-config.json
|
||||
php_errors.log
|
||||
|
||||
73
action.php
73
action.php
@@ -20,12 +20,19 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin {
|
||||
* @return void
|
||||
*/
|
||||
public function register(EventHandler $controller) {
|
||||
|
||||
// insert header data into the page:
|
||||
$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader');
|
||||
|
||||
// write to the log after the page content was displayed:
|
||||
$controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog');
|
||||
|
||||
}
|
||||
|
||||
/* session information */
|
||||
private $sessionId = null;
|
||||
private $sessionType = '';
|
||||
private $ipAddress = null;
|
||||
|
||||
/**
|
||||
* Inserts tracking code to the page header
|
||||
@@ -41,28 +48,23 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin {
|
||||
$this->getSessionInfo();
|
||||
|
||||
// is there a user logged in?
|
||||
$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name'])
|
||||
? $INFO['userinfo']['name'] : '');
|
||||
$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;
|
||||
if ($username) {
|
||||
$code .= DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL;
|
||||
}
|
||||
$code .= DOKU_TAB . "addEventListener('load',function(){" . NL;
|
||||
|
||||
// add the deferred script loader::
|
||||
$code .= DOKU_TAB . "addEventListener('load', 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;
|
||||
|
||||
$event->data['script'][] = [
|
||||
'_data' => $code
|
||||
];
|
||||
|
||||
/* Write out server-side info to a server log: */
|
||||
$this->writeServerLog($username);
|
||||
$event->data['script'][] = ['_data' => $code];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,38 +72,32 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function writeServerLog($username) {
|
||||
public function writeServerLog(Event $event, $param) {
|
||||
|
||||
global $conf;
|
||||
global $INFO;
|
||||
|
||||
// is there a user logged in?
|
||||
$username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name'])
|
||||
? $INFO['userinfo']['name'] : '');
|
||||
|
||||
|
||||
|
||||
// clean the page ID
|
||||
$pageId = preg_replace('/[\x00-\x1F]/', "\u{FFFD}", $INFO['id'] ?? '');
|
||||
|
||||
// collect GeoIP information (if available):
|
||||
$geoIp = ( $this->sessionId == 'localhost' ? 'local' : 'ZZ' ); /* User-defined code for unknown country */
|
||||
try {
|
||||
if (extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) {
|
||||
$geoIp = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);
|
||||
} else {
|
||||
Logger::debug('BotMon Plugin: GeoIP module not available');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage());
|
||||
}
|
||||
|
||||
// create the log array:
|
||||
$logArr = Array(
|
||||
$_SERVER['REMOTE_ADDR'] ?? '', /* remote IP */
|
||||
$this->ipAddress, /* remote IP */
|
||||
$pageId, /* page ID */
|
||||
$this->sessionId, /* Session ID */
|
||||
$this->sessionType, /* session ID type */
|
||||
$username,
|
||||
$username, /* user name */
|
||||
$_SERVER['HTTP_USER_AGENT'] ?? '', /* User agent */
|
||||
$_SERVER['HTTP_REFERER'] ?? '', /* HTTP Referrer */
|
||||
substr($conf['lang'],0,2), /* page language */
|
||||
implode(',', array_unique(array_map( function($it) { return substr($it,0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */
|
||||
$geoIp /* GeoIP country code */
|
||||
$this->getCountryCode() /* GeoIP country code */
|
||||
);
|
||||
|
||||
//* create the log line */
|
||||
@@ -123,8 +119,31 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin {
|
||||
fclose($logfile);
|
||||
}
|
||||
|
||||
private function getCountryCode() {
|
||||
|
||||
$country = ( $this->ipAddress == 'localhost' ? 'AA' : 'ZZ' ); // default if no geoip is available!
|
||||
|
||||
$lib = $this->getConf('geoiplib'); /* which library to use? (can only be phpgeoip or disabled) */
|
||||
|
||||
try {
|
||||
|
||||
// use GeoIP module?
|
||||
if ($lib == 'phpgeoip' && extension_loaded('geoip') && geoip_db_avail(GEOIP_COUNTRY_EDITION)) { // Use PHP GeoIP module
|
||||
$result = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);
|
||||
$country = ($result ? $result : $country);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Logger::error('BotMon Plugin: GeoIP Error', $e->getMessage());
|
||||
}
|
||||
|
||||
return $country;
|
||||
}
|
||||
|
||||
private function getSessionInfo() {
|
||||
|
||||
$this->ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
|
||||
if ($this->ipAddress == '127.0.0.1' || $this->ipAddress == '::1') $this->ipAddress = 'localhost';
|
||||
|
||||
// what is the session identifier?
|
||||
if (isset($_SESSION)) {
|
||||
$sesKeys = array_keys($_SESSION); /* DokuWiki Session ID preferred */
|
||||
@@ -140,8 +159,8 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin {
|
||||
$this->sessionId = session_id();
|
||||
$this->sessionType = 'php';
|
||||
}
|
||||
if (!$this->sessionId) { /* no PHP session ID, try IP address */
|
||||
$this->sessionId = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
if (!$this->sessionId && $this->ipAddress) { /* no PHP session ID, try IP address */
|
||||
$this->sessionId = $this->ipAddress;
|
||||
$this->sessionType = 'ip';
|
||||
}
|
||||
if (!$this->sessionId) { /* if everything else fails, just us a random ID */
|
||||
|
||||
@@ -59,7 +59,6 @@ class admin_plugin_botmon extends AdminPlugin {
|
||||
<div class="botmon_overview_grid">
|
||||
<dl id="botmon__today__botsvshumans"></dl>
|
||||
<dl id="botmon__botslist"></dl>
|
||||
<dl id="botmon__today__botips"></dl>
|
||||
<dl id="botmon__today__countries"></dl>
|
||||
</div>
|
||||
</details>
|
||||
@@ -69,7 +68,6 @@ class admin_plugin_botmon extends AdminPlugin {
|
||||
<dl id="botmon__today__wm_overview"></dl>
|
||||
<dl id="botmon__today__wm_clients"></dl>
|
||||
<dl id="botmon__today__wm_platforms"></dl>
|
||||
<dl></dl>
|
||||
</div>
|
||||
</details>
|
||||
<details id="botmon__today__visitors">
|
||||
|
||||
@@ -73,7 +73,7 @@ botmon_client = {
|
||||
console.error(err);
|
||||
} finally {
|
||||
/* send the next heartbeat signal after x seconds: */
|
||||
setTimeout(this._onHeartbeat.bind(this, this._src.replace( this._scriptName, '/tick.php')),this._heartbeat * 1000);
|
||||
// setTimeout(this._onHeartbeat.bind(this, this._src.replace( this._scriptName, '/tick.php')),this._heartbeat * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
conf/default.php
Normal file
8
conf/default.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuration settings for the BotMon Plugin
|
||||
*
|
||||
* @author Sascha Leib <sascha@leib.be>
|
||||
*/
|
||||
|
||||
$conf['geoiplib'] = 'disabled';
|
||||
9
conf/metadata.php
Normal file
9
conf/metadata.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuration settings for the BotMon Plugin
|
||||
*
|
||||
* @author Sascha Leib <sascha@leib.be>
|
||||
*/
|
||||
|
||||
$meta['geoiplib'] = array('multichoice',
|
||||
'_choices' => array ('disabled', 'phpgeoip'));
|
||||
118
config/default-config.json
Normal file
118
config/default-config.json
Normal file
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"threshold": 100,
|
||||
"rules": [
|
||||
{"func": "fromKnownBotIP",
|
||||
"id": "botIpRange", "desc": "Common Bot IP range",
|
||||
"bot": 50
|
||||
},
|
||||
{"func": "matchesClient", "params": ["aol", "msie", "chromeold","oldedge"],
|
||||
"id": "oldClient", "desc": "Obsolete browser version",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "matchesPlatform", "params": ["winold", "macosold", "androidold"],
|
||||
"id": "oldOS", "desc": "Obsolete platform version",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "matchesPlatform", "params": ["winsrvr", "bsd"],
|
||||
"id": "serverOS", "desc": "Server OS",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "matchesPlatform", "params": ["null"],
|
||||
"id": "noOS", "desc": "Unknown or missing OS information",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "smallPageCount", "params": [1],
|
||||
"id": "onePage", "desc": "Visiter viewed only a single page",
|
||||
"bot": 30
|
||||
},
|
||||
{"func": "noRecord", "params": ["log"],
|
||||
"id": "noClient", "desc": "No client-side JS log was recorded",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "noRecord", "params": ["tck"],
|
||||
"id": "noTicks", "desc": "No client ticks were recorded",
|
||||
"bot": 10
|
||||
},
|
||||
{"func": "noReferrer",
|
||||
"id": "noRefs", "desc": "No referer field",
|
||||
"bot": 30
|
||||
},
|
||||
{"func": "matchLang", "params": [],
|
||||
"id": "langMatch", "desc": "Client’s ‘Accept-Language’ header does not match the page language",
|
||||
"bot": 30
|
||||
},
|
||||
{"func": "matchesClient", "params": ["brave"],
|
||||
"id": "susClient", "desc": "Client identifier that is popular with bot networks",
|
||||
"bot": 10
|
||||
},
|
||||
{"func": "combinationTest", "params": [["macos","chromeold"],["macos","msie"],["winold","edge"],["winold","brave"]],
|
||||
"id": "suspPC", "desc": "Suspicious combination of platform and client",
|
||||
"bot": 30
|
||||
},
|
||||
{"func": "combinationTest", "params": [["macos","msie"],["win10","safari"],["macosold","brave"]],
|
||||
"id": "impPC", "desc": "Impossible combination of platform and client",
|
||||
"bot": 70
|
||||
},
|
||||
{"func": "loadSpeed", "params": [3, 20],
|
||||
"id": "speedRun", "desc": "Average time between page loads is less than 20 seconds",
|
||||
"bot": 80
|
||||
},
|
||||
{"func": "noAcceptLang",
|
||||
"id": "noAcc", "desc": "No “Accept-Language” header",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "clientAccepts", "params": ["zh"],
|
||||
"id": "zhLang", "desc": "Client accepts Chinese language",
|
||||
"bot": 60
|
||||
},
|
||||
{"func": "matchesCountry", "params": ["BR", "CN", "RU", "US", "MX", "SG", "IN", "UY"],
|
||||
"id": "isFrom", "desc": "Location is in a known bot-spamming country.",
|
||||
"bot": 50
|
||||
}
|
||||
],
|
||||
"ipRanges": [
|
||||
{"from": "3.0.0.0", "to": "3.255.255.254", "label": "Amazon Data Services [US]"},
|
||||
{"from": "8.127.0.0", "to": "8.223.255.254", "label": "Alibaba [CN]"},
|
||||
{"from": "24.240.0.0", "to": "24.247.255.255", "m": 13, "label": "Charter [US]"},
|
||||
{"from": "27.106.0.0", "to": "27.106.127.254", "label": "Huawei [US]"},
|
||||
{"from": "34.0.0.0", "to": "34.191.255.254", "label": "Google LLC"},
|
||||
{"from": "45.0.0.0", "to": "45.255.255.254", "label": "Various small ISPs, mostly BR"},
|
||||
{"from": "46.250.160.0", "to": "46.250.191.254", "label": "Huawei [MX]"},
|
||||
{"from": "47.200.0.0", "to": "47.203.255.255", "m": 14, "label": "Frontier Communications [US]"},
|
||||
{"from": "49.0.200.0", "to": "49.0.255.254", "label": "Huawei [SG]"},
|
||||
{"from": "66.249.64.0", "to": "66.249.95.255", "m": 19, "label": "Google LLC [US]"},
|
||||
{"from": "84.37.35.0", "to": "84.37.255.254", "label": "GTT.net [US]"},
|
||||
{"from": "94.74.64.0", "to": "94.74.127.254", "label": "Huawei [HK]"},
|
||||
{"from": "101.0.0.0", "to": "101.255.255.254", "label": "ChinaNet [CN]"},
|
||||
{"from": "110.238.80.0", "to": "110.238.127.254", "label": "Huawei [SG]"},
|
||||
{"from": "111.119.192.0", "to": "111.119.255.254", "label": "Huawei [SG]"},
|
||||
{"from": "119.0.0.0", "to": "119.207.255.254", "label": "Unicom [CN]"},
|
||||
{"from": "121.91.168.", "to": "121.91.175.254", "label": "Huawei [HK]"},
|
||||
{"from": "122.8.0.0", "to": "122.8.255.254", "label": "CN-ISP [CN]"},
|
||||
{"from": "122.9.0.0", "to": "122.9.255.254", "label": "Huawei [CN]"},
|
||||
{"from": "124.243.128.0", "to": "124.243.191.254", "label": "Huawei [SG]"},
|
||||
{"from": "142.147.128.0", "to": "1142.147.255.254", "label": "Web2Objects LLC [US]"},
|
||||
{"from": "150.40.128.0", "to": "150.40.255.254", "label": "Huawei [HK]"},
|
||||
{"from": "159.138.0.0", "to": "159.138.225.254", "label": "Huawei [TH]"},
|
||||
{"from": "162.128.0.0", "to": "162.128.255.254", "label": "Zenlayer [SG]"},
|
||||
{"from": "166.108.192.0", "to": "166.108.255.254", "label": "Huawei [SG]"},
|
||||
{"from": "177.0.0.0", "to": "177.255.255.254", "label": "BrasilNET [BR]"},
|
||||
{"from": "179.0.0.0", "to": "179.255.255.254", "label": "BrasilNET [BR]"},
|
||||
{"from": "183.87.32.0", "to": "183.87.159.254", "label": "Huawei [HK]"},
|
||||
{"from": "186.0.0.0", "to": "186.255.255.254", "label": "South-American ISPs (186.x)"},
|
||||
{"from": "187.0.0.0", "to": "187.255.255.254", "label": "South-American ISPs (187.x)"},
|
||||
{"from": "188.0.0.0", "to": "188.255.255.254", "label": "South-American ISPs (188.x)"},
|
||||
{"from": "189.0.0.0", "to": "189.255.255.254", "label": "South-American ISPs (189.x)"},
|
||||
{"from": "190.0.0.0", "to": "190.255.255.254", "label": "South-American ISPs (190.x)"},
|
||||
{"from": "192.124.170.0", "to": "192.124.182.254", "label": "Relcom [CZ]"},
|
||||
{"from": "195.37.0.0", "to": "195.37.255.255", "label": "DFN [DE]"},
|
||||
{"from": "2001:4860::::::", "to": "2001:4860:ffff:ffff:ffff:ffff:ffff:ffff", "m": 32, "label": "Google LLC [US]"},
|
||||
{"from": "2001:0ee0::::::", "to": "2001:ee3:ffff:ffff:ffff:ffff:ffff:ffff", "m": 30, "label": "VNPT [VN]"},
|
||||
{"from": "2408:8210::::::", "to": "2408:8210:ffff:ffff:ffff:ffff:ffff:ffff", "m": 30, "label": "China Unicom [CN]"},
|
||||
{"from": "2600:1f00::::::", "to": "2600:1fff:ffff:ffff:ffff:ffff:ffff:ffff", "m": "Amazon Cloud [US]"},
|
||||
{"from": "2603:6010::::::", "to": "2603:6010:ffff:ffff:ffff:ffff:ffff:ffff", "m": 32, "label": "Charter [US]"},
|
||||
{"from": "2603:8000::::::", "to": "2603:80ff:ffff:ffff:ffff:ffff:ffff:ffff", "m": 24, "label": "Charter [US]"},
|
||||
{"from": "2804:::::::", "to": "2804:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "m": 16, "label": "Inspire [BR]"},
|
||||
{"from": "2a0a:4cc0::::::", "to": "2a0a:4cc0:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "label": "Netcup [DE]"}
|
||||
]
|
||||
}
|
||||
@@ -1,35 +1,41 @@
|
||||
[ {"id": "bingbot",
|
||||
"n": "BingBot",
|
||||
"n": "BingBot", "geo": "US",
|
||||
"r": ["bingbot"],
|
||||
"rx": ["\\sbingbot\\/(\\d+\\.\\d+);"],
|
||||
"url": "http://www.bing.com/bingbot.htm"
|
||||
},
|
||||
{"id": "googlebot",
|
||||
"n": "GoogleBot",
|
||||
"n": "GoogleBot", "geo": "US",
|
||||
"r": ["Googlebot"],
|
||||
"rx": ["Googlebot\\/(\\d+\\.\\d+)", "Googlebot-Image\\/(\\d+\\.\\d+)"],
|
||||
"url": "http://www.google.com/bot.html"
|
||||
},
|
||||
{"id": "googleads",
|
||||
"n": "Google Ads",
|
||||
"n": "Google Ads", "geo": "US",
|
||||
"r": ["AdsBot-Google", "AdsBot-Google-Mobile", "Mediapartners-Google"],
|
||||
"rx": ["AdsBot-Google;","AdsBot-Google-Mobile;", "Mediapartners-Google\\/(\\d+\\.\\d+);"],
|
||||
"url": "https://developers.google.com/search/docs/crawling-indexing/google-special-case-crawlers"
|
||||
},
|
||||
{"id": "googleapi",
|
||||
"n": "Google API Crawler",
|
||||
"n": "Google API Crawler", "geo": "US",
|
||||
"r": ["APIs-Google"],
|
||||
"rx": ["APIs-Google"],
|
||||
"url": "https://developers.google.com/search/docs/crawling-indexing/google-special-case-crawlers"
|
||||
},
|
||||
{"id": "googleother",
|
||||
"n": "GoogleOther", "geo": "US",
|
||||
"r": ["GoogleOther"],
|
||||
"rx": ["\\sGoogleOther(\\-\\w+)?[\\)\\/]"],
|
||||
"url": "https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers#googleother"
|
||||
},
|
||||
{"id": "applebot",
|
||||
"n": "Applebot",
|
||||
"n": "Applebot", "geo": "US",
|
||||
"r": ["Applebot"],
|
||||
"rx": ["Applebot\\/(\\d+\\.\\d+);"],
|
||||
"url": "http://www.apple.com/go/applebot"
|
||||
},
|
||||
{"id": "slurp",
|
||||
"n": "Slurp (Yahoo!)",
|
||||
"n": "Slurp (Yahoo!)", "geo": "US",
|
||||
"r": ["Slurp"],
|
||||
"rx": ["Slurp[\\s;\\)]"],
|
||||
"url": "http://help.yahoo.com/help/us/ysearch/slurp"
|
||||
@@ -41,25 +47,25 @@
|
||||
"url": "https://duckduckgo.com/duckduckbot.html"
|
||||
},
|
||||
{"id": "openai",
|
||||
"n": "OpenAI/ChatGPT",
|
||||
"n": "OpenAI/ChatGPT", "geo": "US",
|
||||
"r": ["OAI-SearchBot", "ChatGPT-User", "GPTBot"],
|
||||
"rx": ["OAI-SearchBot\\/(\\d+\\.\\d+);", "ChatGPT-User\\/(\\d+\\.\\d+);", "GPTBot\\/(\\d+\\.\\d+);"],
|
||||
"url": "https://platform.openai.com/docs/bots/"
|
||||
},
|
||||
{"id": "claude",
|
||||
"n": "Anthropic Claude",
|
||||
"n": "Anthropic Claude", "geo": "US",
|
||||
"r": ["ClaudeBot", "Claude-User", "Claude-SearchBot"],
|
||||
"rx": ["ClaudeBot\\/(\\d+\\.\\d+);"],
|
||||
"url": "https://darkvisitors.com/agents/claudebot"
|
||||
},
|
||||
{"id": "perplexity",
|
||||
"n": "Perplexity",
|
||||
"n": "Perplexity", "geo": "US",
|
||||
"r": ["PerplexityBot", "Perplexity‑User"],
|
||||
"rx": ["PerplexityBot\\/(\\d+\\.\\d+);", "Perplexity‑User\\/(\\d+\\.\\d+);"],
|
||||
"url": "https://perplexity.ai/perplexitybot"
|
||||
},
|
||||
{"id": "metabots",
|
||||
"n": "Meta/Facebook",
|
||||
"n": "Meta/Facebook", "geo": "US",
|
||||
"r": ["facebookexternalhit", "facebookcatalog","meta-webindexer","meta-externalads","meta-externalagent","meta-externalfetcher"],
|
||||
"rx": ["facebook\\w+\\/(\\d+\\.\\d+)", "meta-\\w+\\/(\\d+\\.\\d+)"],
|
||||
"url": "https://developers.facebook.com/docs/sharing/webmasters/crawler"
|
||||
@@ -89,7 +95,7 @@
|
||||
"url": "https://ahrefs.com/robot/"
|
||||
},
|
||||
{"id": "ccbot",
|
||||
"n": "CommonCrawl Bot",
|
||||
"n": "CommonCrawl Bot", "geo": "US",
|
||||
"r": ["CCBot"],
|
||||
"rx": ["CCBot\\/(\\d+\\.\\d+)[\\s\\.;]*"],
|
||||
"url": "https://commoncrawl.org/bot.html"
|
||||
@@ -185,7 +191,7 @@
|
||||
"url": "http://www.sogou.com/docs/help/webmasters.htm#07"
|
||||
},
|
||||
{"id": "amazon",
|
||||
"n": "Amazonbot",
|
||||
"n": "Amazonbot", "geo": "US",
|
||||
"r": ["Amazonbot"],
|
||||
"rx": ["Amazonbot\\/(\\d+\\.\\d+)[;\\s\\(\\.]"],
|
||||
"url": "https://developer.amazon.com/amazonbot"
|
||||
@@ -23,6 +23,14 @@
|
||||
"id": "huawei",
|
||||
"rx": [ "\\sHuaweiBrowser\\/(\\d+\\.\\d+)[\\s\\.]", "\\/harmony360Browser\\/(\\d+\\.\\d+)[\\s\\.]"]
|
||||
},
|
||||
{"n": "Ecosia Browser",
|
||||
"id": "ecosia",
|
||||
"rx": [ "\\(Ecosia ios@(\\d+)\\." ]
|
||||
},
|
||||
{"n": "Silk",
|
||||
"id": "silk",
|
||||
"rx": [ "\\Silk\\/(\\d+)\\." ]
|
||||
},
|
||||
{"n": "DuckDuckGo",
|
||||
"id": "ddg",
|
||||
"rx": [ "\\sDdg\\/(\\S+)" ]
|
||||
@@ -59,8 +67,12 @@
|
||||
"id": "safari",
|
||||
"rx": [ "\\sSafari\\/(\\d+)" ]
|
||||
},
|
||||
{"n": "Firefox Old",
|
||||
"id": "ffold",
|
||||
"rx": [ "\\sFirefox\\/(\\d\\d)\\.", "\\sFirefox\\s" ]
|
||||
},
|
||||
{"n": "Firefox",
|
||||
"id": "firefox",
|
||||
"rx": [ "\\sFirefox\\/(\\S+)" ]
|
||||
"rx": [ "\\sFirefox\\/(\\d\\d\\d)\\." ]
|
||||
}
|
||||
]
|
||||
160
config/known-ipranges.json
Normal file
160
config/known-ipranges.json
Normal file
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"isps": [
|
||||
{"i": "hetzner", "n": "Hetzner Online GmbH", "g": "DE"},
|
||||
{"i": "huawei", "n": "Huawei Cloud", "g": "HK"}
|
||||
],
|
||||
"5": [
|
||||
{"9": {"i": "hetzner", "g": "FI"}},
|
||||
{"75": [
|
||||
{"128": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
],
|
||||
"23": [
|
||||
{"88": [
|
||||
{"0": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
],
|
||||
"37": [
|
||||
{"27": {"i": "hetzner", "g": "FI"}}
|
||||
],
|
||||
"46": [
|
||||
{"4": {"i": "hetzner", "g": "DE"}},
|
||||
{"62": [
|
||||
{"128": {"m": 17, "i": "hetzner", "g": "FI"}}
|
||||
]}
|
||||
],
|
||||
"49": [
|
||||
{"12": {"i": "hetzner", "g": "DE"}},
|
||||
{"13": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"65": [
|
||||
{"108": {"i": "hetzner", "g": "FI"}},
|
||||
{"109": {"i": "hetzner", "g": "FI"}},
|
||||
{"210": {"i": "hetzner", "g": "FI"}}
|
||||
],
|
||||
"77": [
|
||||
{"42": [
|
||||
{"0": {"m": 17, "i": "hetzner", "g": "FI"}}
|
||||
]}
|
||||
],
|
||||
"78": [
|
||||
{"46": {"i": "hetzner", "g": "DE"}},
|
||||
{"47": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"85": [
|
||||
{"10": [
|
||||
{"192": {"m": 18, "i": "hetzner", "g": "FI"}}
|
||||
]}
|
||||
],
|
||||
"88": [
|
||||
{"99": {"i": "hetzner", "g": "DE"}},
|
||||
{"198": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"91": [
|
||||
{"98": {"i": "hetzner", "g": "DE"}},
|
||||
{"99": {"i": "hetzner", "g": "DE"}},
|
||||
{"107": [
|
||||
{"128": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
],
|
||||
"94": [
|
||||
{"130": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"95": [
|
||||
{"216": {"i": "hetzner", "g": "FI"}},
|
||||
{"217": {"i": "hetzner", "g": "FI"}}
|
||||
],
|
||||
"110": [
|
||||
{"239": [
|
||||
{"64": {"m": 19, "i": "huawei", "g": "SG"}},
|
||||
{"96": {"m": 19, "i": "huawei", "g": "SG"}}
|
||||
]}
|
||||
],
|
||||
"114": [
|
||||
{"119": [
|
||||
{"128": {"m": 19, "i": "huawei", "g": "SG"}}
|
||||
]}
|
||||
],
|
||||
"116": [
|
||||
{"202": {"i": "hetzner", "g": "DE"}},
|
||||
{"203": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"119": [
|
||||
{"8": [
|
||||
{"32": {"m": 19, "i": "huawei", "g": "HK"}},
|
||||
{"96": {"m": 19, "i": "huawei", "g": "HK"}},
|
||||
{"160": {"m": 19, "i": "huawei", "g": "SG"}}
|
||||
]}
|
||||
],
|
||||
"124": [
|
||||
{"243": [
|
||||
{"128": {"m": 18, "i": "huawei", "g": "SG"}}
|
||||
]}
|
||||
],
|
||||
"128": [
|
||||
{"140": [
|
||||
{"0": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
],
|
||||
"135": [
|
||||
{"181": {"i": "hetzner", "g": "FI"}}
|
||||
],
|
||||
"138": [
|
||||
{"199": [
|
||||
{"128": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
],
|
||||
"142": [
|
||||
{"132": [
|
||||
{"128": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
],
|
||||
"154": [
|
||||
{"220": [
|
||||
{"192": {"m": 19, "i": "huawei", "g": "HK"}}
|
||||
]}
|
||||
],
|
||||
"157": [
|
||||
{"90": {"i": "hetzner", "g": "DE"}},
|
||||
{"180": [
|
||||
{"0": {"m": 17, "i": "hetzner", "g": "FI"}}
|
||||
]}
|
||||
],
|
||||
"159": [
|
||||
{"69": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"162": [
|
||||
{"55": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"167": [
|
||||
{"233": {"i": "hetzner", "g": "DE"}},
|
||||
{"235": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"168": [
|
||||
{"119": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"176": [
|
||||
{"9": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"178": [
|
||||
{"63": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"188": [
|
||||
{"34": [
|
||||
{"128": {"m": 17, "i": "hetzner", "g": "DE"}}
|
||||
]},
|
||||
{"40": {"i": "hetzner", "g": "DE"}},
|
||||
{"245": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"195": [
|
||||
{"201": {"i": "hetzner", "g": "DE"}}
|
||||
],
|
||||
"213": [
|
||||
{"133": [
|
||||
{"69": {"m": 19, "i": "hetzner", "g": "DE"}}
|
||||
]},
|
||||
{"239": [
|
||||
{"192": {"m": 18, "i": "hetzner", "g": "DE"}}
|
||||
]}
|
||||
]
|
||||
}
|
||||
@@ -15,13 +15,21 @@
|
||||
"id": "ios",
|
||||
"rx": [ "\\sFxiOS\\/(\\d+\\.\\d+)\\s", "\\siPhone\\sOS\\s([\\d\\._]+)\\s", "\\siPadOS\\s([\\d\\._]+)\\s", "iPad; CPU OS (\\d+)[_\\.\\s]", "\\sCriOS\\/(\\d+\\.\\d+)\\." ]
|
||||
},
|
||||
{"n": "FireOS",
|
||||
"id": "fire",
|
||||
"rx": [ "\\sKFFOWI[\\)\\s]]", "\\sKFTHWI[\\)\\s]]" , "\\sSilk\\/"]
|
||||
},
|
||||
{"n": "Old Android",
|
||||
"id": "androidold",
|
||||
"rx": [ "Android[\\s;\\/](\\d)\\." ]
|
||||
},
|
||||
{"n": "Android",
|
||||
"id": "android",
|
||||
"rx": [ "[\\(\\s]Android\\s([^;]+);" ]
|
||||
"rx": [ "Android[\\s;\\/](\\d\\d)[\\.;\\s]" ]
|
||||
},
|
||||
{"n": "Old MacOS",
|
||||
"id": "macosold",
|
||||
"rx": [ "\\sMac OS X 10[\\._](\\d|1[0-3])[\\._;\\s\\)]", "\\sMac OS X (1[123])[\\._]" ]
|
||||
"rx": [ "\\sMac OS X 10[\\._](\\d|1[0-3])[\\._;\\s\\)]", "\\sMac OS X (1[123])[\\.;_\\s]" ]
|
||||
},
|
||||
{"n": "MacOS",
|
||||
"id": "macos",
|
||||
@@ -9,7 +9,7 @@
|
||||
"id": "oldClient", "desc": "Obsolete browser version",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "matchesPlatform", "params": ["winold", "macosold"],
|
||||
{"func": "matchesPlatform", "params": ["winold", "macosold", "androidold"],
|
||||
"id": "oldOS", "desc": "Obsolete platform version",
|
||||
"bot": 40
|
||||
},
|
||||
@@ -17,6 +17,10 @@
|
||||
"id": "serverOS", "desc": "Server OS",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "matchesPlatform", "params": ["null"],
|
||||
"id": "noOS", "desc": "Unknown or missing OS information",
|
||||
"bot": 40
|
||||
},
|
||||
{"func": "smallPageCount", "params": [1],
|
||||
"id": "onePage", "desc": "Visiter viewed only a single page",
|
||||
"bot": 40
|
||||
@@ -65,6 +69,10 @@
|
||||
"id": "isFrom", "desc": "Location is in a known bot-spamming country.",
|
||||
"bot": 50
|
||||
},
|
||||
{"func": "notFromCountry", "params": ["DE", "AT", "CH", "LI", "LU", "BE"],
|
||||
"id": "notFromHere", "desc": "Location is not among the site’s main target countries.",
|
||||
"bot": 20
|
||||
},
|
||||
{"func": "matchesCountry", "params": ["ZZ"],
|
||||
"id": "zzCtry", "desc": "Location could not be determined",
|
||||
"bot": 20
|
||||
@@ -73,12 +81,13 @@
|
||||
"ipRanges": [
|
||||
{"from": "3.0.0.0", "to": "3.255.255.254", "label": "Amazon Data Services [US]"},
|
||||
{"from": "8.127.0.0", "to": "8.223.255.254", "label": "Alibaba [CN]"},
|
||||
{"from": "24.240.0.0", "to": "24.243.255.254", "label": "Charter [US]"},
|
||||
{"from": "24.240.0.0", "to": "24.247.255.255", "m": 13, "label": "Charter [US]"},
|
||||
{"from": "27.106.0.0", "to": "27.106.127.254", "label": "Huawei [US]"},
|
||||
{"from": "34.0.0.0", "to": "34.191.255.254", "label": "Google LLC"},
|
||||
{"from": "45.0.0.0", "to": "45.255.255.254", "label": "Various small ISPs, mostly BR"},
|
||||
{"from": "46.250.160.0", "to": "46.250.191.254", "label": "Huawei [MX]"},
|
||||
{"from": "49.0.200.0", "to": "49.0.255.254", "label": "Huawei [SG]"},
|
||||
{"from": "66.249.64.0", "to": "66.249.95.255", "m": 19, "label": "Google LLC [US]"},
|
||||
{"from": "84.37.35.0", "to": "84.37.255.254", "label": "GTT.net [US]"},
|
||||
{"from": "94.74.64.0", "to": "94.74.127.254", "label": "Huawei [HK]"},
|
||||
{"from": "101.0.0.0", "to": "101.255.255.254", "label": "ChinaNet [CN]"},
|
||||
@@ -102,12 +111,14 @@
|
||||
{"from": "188.0.0.0", "to": "188.255.255.254", "label": "South-American ISPs (188.x)"},
|
||||
{"from": "189.0.0.0", "to": "189.255.255.254", "label": "South-American ISPs (189.x)"},
|
||||
{"from": "190.0.0.0", "to": "190.255.255.254", "label": "South-American ISPs (190.x)"},
|
||||
{"from": "192.124.170.0", "to": "192.124.182.254", "label": "Relcom [CZ]"},
|
||||
{"from": "192.124.170.0", "to": "192.124.182.254", "label": "Relcom [CZ]"},
|
||||
{"from": "195.37.0.0", "to": "195.37.255.255", "label": "DFN [DE]"},
|
||||
{"from": "2001:4800::::::", "to": "2001:4fff:ffff:ffff:ffff:ffff:ffff:ffff", "label": "Rackspace/Google [US]"},
|
||||
{"from": "2001:0ee0::::::", "to": "2001:ee3:ffff:ffff:ffff:ffff:ffff:ffff", "mask": 30, "label": "VNPT [VN]"},
|
||||
{"from": "2600:1f00::::::", "to": "2600:1fff:ffff:ffff:ffff:ffff:ffff:ffff", "label": "Amazon Cloud [US]"},
|
||||
{"from": "2804:::::::", "to": "2804:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "label": "Inspire [BR]"},
|
||||
{"from": "2001:4860::::::", "to": "2001:4860:ffff:ffff:ffff:ffff:ffff:ffff", "m": 32, "label": "Google LLC [US]"},
|
||||
{"from": "2001:0ee0::::::", "to": "2001:ee3:ffff:ffff:ffff:ffff:ffff:ffff", "m": 30, "label": "VNPT [VN]"},
|
||||
{"from": "2600:1f00::::::", "to": "2600:1fff:ffff:ffff:ffff:ffff:ffff:ffff", "m": "Amazon Cloud [US]"},
|
||||
{"from": "2603:6010::::::", "to": "2603:6010:ffff:ffff:ffff:ffff:ffff:ffff", "m": 32, "label": "Charter [US]"},
|
||||
{"from": "2603:8000::::::", "to": "2603:80ff:ffff:ffff:ffff:ffff:ffff:ffff", "m": 24, "label": "Charter [US]"},
|
||||
{"from": "2804:::::::", "to": "2804:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "m": 16, "label": "Inspire [BR]"},
|
||||
{"from": "2a0a:4cc0::::::", "to": "2a0a:4cc0:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "label": "Netcup [DE]"}
|
||||
]
|
||||
}
|
||||
BIN
img/clients.png
BIN
img/clients.png
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 20 KiB |
BIN
img/links.png
Normal file
BIN
img/links.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
10
lang/en/settings.php
Normal file
10
lang/en/settings.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* English language file for the BotMon Plugin Settings
|
||||
*
|
||||
* @author Sascha Leib <sascha@leib.be>
|
||||
*/
|
||||
|
||||
$lang['geoiplib'] = 'Add GeoIP Information<br><small>(requires PHP module to be installed)</small>';
|
||||
$lang['geoiplib_o_disabled'] = 'Disabled';
|
||||
$lang['geoiplib_o_phpgeoip'] = 'Use GeoIP Module';
|
||||
@@ -1,7 +1,7 @@
|
||||
base botmon
|
||||
author Sascha Leib
|
||||
email ad@hominem.com
|
||||
date 2025-09-12
|
||||
date 2025-09-13
|
||||
name Bot Monitoring
|
||||
desc A tool for monitoring and analysing bot traffic to your wiki (under development)
|
||||
url https://www.dokuwiki.org/plugin:botmon
|
||||
|
||||
207
script.js
207
script.js
@@ -21,7 +21,6 @@ const BotMon = {
|
||||
// find the plugin basedir:
|
||||
this._baseDir = document.currentScript.src.substring(0, document.currentScript.src.indexOf('/exe/'))
|
||||
+ '/plugins/botmon/';
|
||||
this._DWBaseDir = document.currentScript.src.substring(0, document.currentScript.src.indexOf('/lib/')) + '/';
|
||||
|
||||
// read the page language from the DOM:
|
||||
this._lang = document.getRootNode().documentElement.lang || this._lang;
|
||||
@@ -34,7 +33,6 @@ const BotMon = {
|
||||
},
|
||||
|
||||
_baseDir: null,
|
||||
_DWBaseDir: null,
|
||||
_lang: 'en',
|
||||
_today: (new Date()).toISOString().slice(0, 10),
|
||||
_timeDiff: '',
|
||||
@@ -109,7 +107,7 @@ const BotMon = {
|
||||
_formatTime: function(date) {
|
||||
|
||||
if (date) {
|
||||
return ('0'+date.getHours()).slice(-2) + ':' + ('0'+date.getMinutes()).slice(-2) + ':' + ('0'+date.getSeconds()).slice(-2);
|
||||
return date.getHours() + ':' + ('0'+date.getMinutes()).slice(-2) + ':' + ('0'+date.getSeconds()).slice(-2);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -320,7 +318,7 @@ BotMon.live = {
|
||||
nv._country = ( nv.geo == 'local' ? "localhost" : "Unknown" );
|
||||
if (nv.geo && nv.geo !== '' && nv.geo !== 'ZZ' && nv.geo !== 'local') {
|
||||
const countryName = new Intl.DisplayNames(['en', BotMon._lang], {type: 'region'});
|
||||
nv._country = countryName.of(nv.geo) ?? nv.geo;
|
||||
nv._country = countryName.of(nv.geo.substring(0,2)) ?? nv.geo;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -435,7 +433,7 @@ BotMon.live = {
|
||||
// get the page view info:
|
||||
let pv = model._getPageView(visitor, dat);
|
||||
if (!pv) {
|
||||
console.warn(`No page view for visit ID “${dat.id}”, page “${dat.pg}”, registering a new one.`);
|
||||
console.info(`No page view for visit ID “${dat.id}”, page “${dat.pg}”, registering a new one.`);
|
||||
pv = model._makePageView(dat, type);
|
||||
visitor._pageViews.push(pv);
|
||||
}
|
||||
@@ -450,19 +448,21 @@ BotMon.live = {
|
||||
|
||||
// helper function to create a new "page view" item:
|
||||
_makePageView: function(data, type) {
|
||||
// console.info('_makePageView', data);
|
||||
|
||||
// try to parse the referrer:
|
||||
let rUrl = null;
|
||||
try {
|
||||
rUrl = ( data.ref && data.ref !== '' ? new URL(data.ref) : null );
|
||||
} catch (e) {
|
||||
console.info(`Invalid referer: “${data.ref}”.`);
|
||||
console.warn(`Invalid referer: “${data.ref}”.`);
|
||||
}
|
||||
|
||||
return {
|
||||
_by: type,
|
||||
ip: data.ip,
|
||||
pg: data.pg,
|
||||
lang: data.lang || '??',
|
||||
_ref: rUrl,
|
||||
_firstSeen: data.ts,
|
||||
_lastSeen: data.ts,
|
||||
@@ -553,9 +553,9 @@ BotMon.live = {
|
||||
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 => {
|
||||
/*v._pageViews.forEach( pv => {
|
||||
me.addToIPRanges(pv.ip);
|
||||
});
|
||||
});*/
|
||||
|
||||
// add to the country lists:
|
||||
me.addToCountries(v.geo, v._country, v._type);
|
||||
@@ -572,10 +572,10 @@ BotMon.live = {
|
||||
},
|
||||
|
||||
// visits from IP ranges:
|
||||
_ipRange: {
|
||||
/*_ipRange: {
|
||||
ip4: [],
|
||||
ip6: []
|
||||
},
|
||||
},*/
|
||||
/**
|
||||
* Adds a visit to the IP range statistics.
|
||||
*
|
||||
@@ -583,7 +583,7 @@ BotMon.live = {
|
||||
*
|
||||
* @param {string} ip The IP address to add.
|
||||
*/
|
||||
addToIPRanges: function(ip) {
|
||||
/*addToIPRanges: function(ip) {
|
||||
|
||||
// #TODO: handle nestled ranges!
|
||||
const me = BotMon.live.data.analytics;
|
||||
@@ -615,8 +615,8 @@ BotMon.live = {
|
||||
it.count += 1;
|
||||
}
|
||||
|
||||
},
|
||||
getTopBotIPRanges: function(max) {
|
||||
},*/
|
||||
/*getTopBotIPRanges: function(max) {
|
||||
|
||||
const me = BotMon.live.data.analytics;
|
||||
|
||||
@@ -649,7 +649,7 @@ BotMon.live = {
|
||||
}
|
||||
|
||||
return rList;
|
||||
},
|
||||
},*/
|
||||
|
||||
/* countries of visits */
|
||||
_countries: {
|
||||
@@ -885,7 +885,7 @@ BotMon.live = {
|
||||
|
||||
// Load the list of known bots:
|
||||
BotMon.live.gui.status.showBusy("Loading known bots …");
|
||||
const url = BotMon._baseDir + 'conf/known-bots.json';
|
||||
const url = BotMon._baseDir + 'config/known-bots.json';
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
@@ -896,7 +896,7 @@ BotMon.live = {
|
||||
this._ready = true;
|
||||
|
||||
} catch (error) {
|
||||
BotMon.live.gui.status.setError("Error while loading the ‘known bots’ file: " + error.message);
|
||||
BotMon.live.gui.status.setError("Error while loading the known bots file:", error.message);
|
||||
} finally {
|
||||
BotMon.live.gui.status.hideBusy("Status: Done.");
|
||||
BotMon.live.data._dispatch('bots')
|
||||
@@ -961,7 +961,7 @@ BotMon.live = {
|
||||
|
||||
// Load the list of known bots:
|
||||
BotMon.live.gui.status.showBusy("Loading known clients");
|
||||
const url = BotMon._baseDir + 'conf/known-clients.json';
|
||||
const url = BotMon._baseDir + 'config/known-clients.json';
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
@@ -983,7 +983,7 @@ BotMon.live = {
|
||||
match: function(agent) {
|
||||
//console.info('BotMon.live.data.clients.match(',agent,')');
|
||||
|
||||
let match = {"n": "Unknown", "v": -1, "id": null};
|
||||
let match = {"n": "Unknown", "v": -1, "id": 'null'};
|
||||
|
||||
if (agent) {
|
||||
BotMon.live.data.clients._list.find(client => {
|
||||
@@ -1009,7 +1009,7 @@ BotMon.live = {
|
||||
// return the browser name for a browser ID:
|
||||
getName: function(id) {
|
||||
const it = BotMon.live.data.clients._list.find(client => client.id == id);
|
||||
return it.n;
|
||||
return ( it && it.n ? it.n : "Unknown"); //it.n;
|
||||
},
|
||||
|
||||
// indicates if the list is loaded and ready to use:
|
||||
@@ -1027,7 +1027,7 @@ BotMon.live = {
|
||||
|
||||
// Load the list of known bots:
|
||||
BotMon.live.gui.status.showBusy("Loading known platforms");
|
||||
const url = BotMon._baseDir + 'conf/known-platforms.json';
|
||||
const url = BotMon._baseDir + 'config/known-platforms.json';
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
@@ -1049,7 +1049,7 @@ BotMon.live = {
|
||||
match: function(cid) {
|
||||
//console.info('BotMon.live.data.platforms.match(',cid,')');
|
||||
|
||||
let match = {"n": "Unknown", "id": null};
|
||||
let match = {"n": "Unknown", "id": 'null'};
|
||||
|
||||
if (cid) {
|
||||
BotMon.live.data.platforms._list.find(platform => {
|
||||
@@ -1095,13 +1095,10 @@ BotMon.live = {
|
||||
BotMon.live.gui.status.showBusy("Loading list of rules …");
|
||||
|
||||
// relative file path to the rules file:
|
||||
const filePath = 'conf/botmon-config.json';
|
||||
const filePath = 'config/default-config.json';
|
||||
|
||||
// check if the user has a configuration file in their DokuWiki installation,
|
||||
// then load the appropriate file:
|
||||
this._checkForUserConfig( filePath, (hasUserConfig) => {
|
||||
this._loadrulesFile(( hasUserConfig ? BotMon._DWBaseDir : BotMon._baseDir ) + filePath);
|
||||
});
|
||||
// load the rules file:
|
||||
this._loadrulesFile(BotMon._baseDir + filePath);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1146,35 +1143,13 @@ BotMon.live = {
|
||||
me._ready = true;
|
||||
|
||||
} catch (error) {
|
||||
BotMon.live.gui.status.setError("Error while loading the ‘rules’ file: " + error.message);
|
||||
BotMon.live.gui.status.setError("Error while loading the config file: " + error.message);
|
||||
} finally {
|
||||
BotMon.live.gui.status.hideBusy("Status: Done.");
|
||||
BotMon.live.data._dispatch('rules')
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the user has a configuration file in their DokuWiki installation.
|
||||
* @param {function} whenDone - an optional callback function to call when the check is finished.
|
||||
*/
|
||||
_checkForUserConfig: async function(filePath, whenDone = undefined) {
|
||||
//console.info('BotMon.live.data.rules._checkForUserConfig()');
|
||||
|
||||
let hasUserConfig = false;
|
||||
try {
|
||||
const response = await fetch(BotMon._DWBaseDir + '/' + filePath, {
|
||||
method: 'HEAD'
|
||||
});
|
||||
hasUserConfig = response.ok;
|
||||
} catch (err) {
|
||||
console.info("An error occured while trying to check for a user configuration file:", err);
|
||||
} finally {
|
||||
if (whenDone) {
|
||||
whenDone(hasUserConfig);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_rulesList: [], // list of rules to find out if a visitor is a bot
|
||||
_threshold: 100, // above this, it is considered a bot.
|
||||
|
||||
@@ -1243,6 +1218,9 @@ BotMon.live = {
|
||||
matchesPlatform: function(visitor, ...platforms) {
|
||||
|
||||
const pId = ( visitor._platform ? visitor._platform.id : '');
|
||||
|
||||
if (visitor._platform.id == null) console.log(visitor._platform);
|
||||
|
||||
return platforms.includes(pId);
|
||||
},
|
||||
|
||||
@@ -1317,6 +1295,16 @@ BotMon.live = {
|
||||
return false;
|
||||
},
|
||||
|
||||
// the "Accept language" header contains certain entries:
|
||||
clientAccepts: function(visitor, ...languages) {
|
||||
//console.info('clientAccepts', visitor.accept, languages);
|
||||
|
||||
if (visitor.accept && languages) {;
|
||||
return ( visitor.accept.split(',').filter(lang => languages.includes(lang)).length > 0 );
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Is there an accept-language field defined at all?
|
||||
noAcceptLang: function(visitor) {
|
||||
|
||||
@@ -1476,7 +1464,7 @@ BotMon.live = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
BotMon.live.gui.status.setError(`Error while loading the ${typeName} log file: ${error.message}.`);
|
||||
BotMon.live.gui.status.setError(`Error while loading the ${typeName} log file: ${error.message} – data may be incomplete.`);
|
||||
} finally {
|
||||
BotMon.live.gui.status.hideBusy("Status: Done.");
|
||||
if (onLoaded) {
|
||||
@@ -1557,7 +1545,7 @@ BotMon.live = {
|
||||
}
|
||||
|
||||
// update the suspected bot IP ranges list:
|
||||
const botIps = document.getElementById('botmon__today__botips');
|
||||
/*const botIps = document.getElementById('botmon__today__botips');
|
||||
if (botIps) {
|
||||
botIps.appendChild(makeElement('dt', {}, "Bot IP ranges (top 5)"));
|
||||
|
||||
@@ -1568,7 +1556,7 @@ BotMon.live = {
|
||||
li.appendChild(makeElement('span', {'class': 'count' }, ipInfo.num));
|
||||
botIps.append(li)
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
// update the top bot countries list:
|
||||
const botCountries = document.getElementById('botmon__today__countries');
|
||||
@@ -1678,7 +1666,7 @@ BotMon.live = {
|
||||
BotMon.live.gui.status._errorCount += 1;
|
||||
const el = document.getElementById('botmon__today__status');
|
||||
if (el) {
|
||||
el.innerText = "An error occurred. Data may be incomplete! See browser console for details";
|
||||
el.innerText = "Data may be incomplete.";
|
||||
el.classList.add('error');
|
||||
}
|
||||
},
|
||||
@@ -1792,20 +1780,17 @@ BotMon.live = {
|
||||
const platformName = (data._platform ? data._platform.n : 'Unknown');
|
||||
const clientName = (data._client ? data._client.n: 'Unknown');
|
||||
|
||||
const sumClass = ( data._seenBy.indexOf('srv') < 0 ? 'noServer' : 'hasServer');
|
||||
|
||||
const li = make('li'); // root list item
|
||||
const details = make('details');
|
||||
const summary = make('summary');
|
||||
const summary = make('summary', {
|
||||
'class': sumClass
|
||||
});
|
||||
details.appendChild(summary);
|
||||
|
||||
const span1 = make('span'); /* left-hand group */
|
||||
|
||||
// country flag:
|
||||
span1.appendChild(make('span', {
|
||||
'class': 'icon_only country ctry_' + data.geo.toLowerCase(),
|
||||
'data-ctry': (data.geo | 'ZZ'),
|
||||
'title': "Country: " + ( data._country || "Unknown")
|
||||
}, ( data._country || "Unknown") ));
|
||||
|
||||
if (data._type !== BM_USERTYPE.KNOWN_BOT) { /* No platform/client for bots */
|
||||
span1.appendChild(make('span', { /* Platform */
|
||||
'class': 'icon_only platform pf_' + (data._platform ? data._platform.id : 'unknown'),
|
||||
@@ -1848,6 +1833,15 @@ BotMon.live = {
|
||||
}, data.id));
|
||||
}
|
||||
|
||||
// country flag:
|
||||
if (data.geo && data.geo !== 'ZZ') {
|
||||
span1.appendChild(make('span', {
|
||||
'class': 'icon_only country ctry_' + data.geo.toLowerCase(),
|
||||
'data-ctry': data.geo,
|
||||
'title': "Country: " + ( data._country || "Unknown")
|
||||
}, ( data._country || "Unknown") ));
|
||||
}
|
||||
|
||||
summary.appendChild(span1);
|
||||
const span2 = make('span'); /* right-hand group */
|
||||
|
||||
@@ -1904,7 +1898,21 @@ BotMon.live = {
|
||||
platformName + ( data._platform.v > 0 ? ' (' + data._platform.v + ')' : '' ) ));
|
||||
|
||||
dl.appendChild(make('dt', {}, "IP-Address:"));
|
||||
dl.appendChild(make('dd', {'class': 'has_icon ipaddr ip' + ipType}, data.ip));
|
||||
const ipItem = make('dd', {'class': 'has_icon ipaddr ip' + ipType});
|
||||
ipItem.appendChild(make('span', {'class': 'address'} , data.ip));
|
||||
ipItem.appendChild(make('a', {
|
||||
'class': 'icon_only extlink dnscheck',
|
||||
'href': `https://dnschecker.org/ip-location.php?ip=${encodeURIComponent(data.ip)}`,
|
||||
'target': 'dnscheck',
|
||||
'title': "View this address on DNSChecker.org"
|
||||
} , "Check Address"));
|
||||
ipItem.appendChild(make('a', {
|
||||
'class': 'icon_only extlink ipinfo',
|
||||
'href': `https://ipinfo.io/${encodeURIComponent(data.ip)}`,
|
||||
'target': 'ipinfo',
|
||||
'title': "View this address on IPInfo.io"
|
||||
} , "DNS Info"));
|
||||
dl.appendChild(ipItem);
|
||||
|
||||
/*dl.appendChild(make('dt', {}, "ID:"));
|
||||
dl.appendChild(make('dd', {'class': 'has_icon ip' + data.typ}, data.id));*/
|
||||
@@ -1924,7 +1932,7 @@ BotMon.live = {
|
||||
dl.appendChild(make('dd', {'class': 'agent'}, data.agent));
|
||||
|
||||
dl.appendChild(make('dt', {}, "Languages:"));
|
||||
dl.appendChild(make('dd', {'class': 'langs'}, "Client accepts: [" + data.accept + "]; Page: [" + data.lang + ']'));
|
||||
dl.appendChild(make('dd', {'class': 'langs'}, ` [${data.accept}]`));
|
||||
|
||||
if (data.geo && data.geo !=='') {
|
||||
dl.appendChild(make('dt', {}, "Location:"));
|
||||
@@ -1949,37 +1957,56 @@ BotMon.live = {
|
||||
const pageList = make('ul');
|
||||
|
||||
/* list all page views */
|
||||
data._pageViews.sort( (a, b) => a._firstSeen - b._firstSeen );
|
||||
data._pageViews.forEach( (page) => {
|
||||
//console.log("page:",page);
|
||||
|
||||
const pgLi = make('li');
|
||||
|
||||
let visitTimeStr = "Bounce";
|
||||
const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime();
|
||||
if (visitDuration > 0) {
|
||||
visitTimeStr = Math.floor(visitDuration / 1000) + "s";
|
||||
}
|
||||
const lGroup = make('span'); // left group:
|
||||
|
||||
pgLi.appendChild(make('span', {}, page.pg)); /* DW Page ID */
|
||||
if (page._ref) {
|
||||
pgLi.appendChild(make('span', {
|
||||
'data-ref': page._ref.host,
|
||||
'title': "Referrer: " + page._ref.full
|
||||
}, page._ref.site));
|
||||
} else {
|
||||
pgLi.appendChild(make('span', {
|
||||
}, "No referer"));
|
||||
}
|
||||
pgLi.appendChild(make('span', {}, ( page._seenBy ? page._seenBy.join(', ') : '—') + '; ' + page._tickCount));
|
||||
pgLi.appendChild(make('span', {}, BotMon.t._formatTime(page._firstSeen)));
|
||||
lGroup.appendChild(make('span', {
|
||||
'data-lang': page.lang,
|
||||
'title': "PageID: " + page.pg
|
||||
}, page.pg)); /* DW Page ID */
|
||||
|
||||
pgLi.appendChild(lGroup); // end of left group
|
||||
|
||||
const rGroup = make('span'); // right group:
|
||||
|
||||
let visitTimeStr = "Bounce";
|
||||
const visitDuration = page._lastSeen.getTime() - page._firstSeen.getTime();
|
||||
if (visitDuration > 0) {
|
||||
visitTimeStr = Math.floor(visitDuration / 1000) + "s";
|
||||
}
|
||||
|
||||
/*if (page._ref) {
|
||||
rGroup.appendChild(make('span', {
|
||||
'data-ref': page._ref.host,
|
||||
'title': "Referrer: " + page._ref.full
|
||||
}, page._ref.site));
|
||||
} else {
|
||||
rGroup.appendChild(make('span', {
|
||||
}, "No referer"));
|
||||
}*/
|
||||
//rGroup.appendChild(make('span', {}, ( page._seenBy ? page._seenBy.join(', ') : '—') + '; ' + page._tickCount));
|
||||
|
||||
// get the time difference:
|
||||
const tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen);
|
||||
if (tDiff) {
|
||||
rGroup.appendChild(make('span', {'class': 'visit-length', 'title': 'Last seen: ' + page._lastSeen.toLocaleString()}, tDiff));
|
||||
} else {
|
||||
rGroup.appendChild(make('span', {
|
||||
'class': 'bounce',
|
||||
'title': "Visitor bounced"}, "Bounce"));
|
||||
}
|
||||
rGroup.appendChild(make('span', {
|
||||
'class': 'first-seen',
|
||||
'title': "First visited: " + page._firstSeen.toLocaleString()
|
||||
}, BotMon.t._formatTime(page._firstSeen)));
|
||||
|
||||
pgLi.appendChild(rGroup); // end of right group
|
||||
|
||||
// get the time difference:
|
||||
const tDiff = BotMon.t._formatTimeDiff(page._firstSeen, page._lastSeen);
|
||||
if (tDiff) {
|
||||
pgLi.appendChild(make('span', {'class': 'visit-length', 'title': 'Last seen: ' + page._lastSeen.toLocaleString()}, tDiff));
|
||||
} else {
|
||||
pgLi.appendChild(make('span', {
|
||||
'class': 'bounce',
|
||||
'title': "Visitor bounced"}, "Bounce"));
|
||||
}
|
||||
|
||||
pageList.appendChild(pgLi);
|
||||
});
|
||||
|
||||
86
style.less
86
style.less
@@ -40,7 +40,7 @@
|
||||
|
||||
/* Bot icons */
|
||||
&.bot::before { background-image: url('img/bots.png') }
|
||||
&.bot_googlebot::before, &.bot_googleads::before, &.bot_googleapi::before { background-position-y: -20px }
|
||||
&.bot_googlebot::before, &.bot_googleads::before, &.bot_googleapi::before, &.bot_googleother::before { background-position-y: -20px }
|
||||
&.bot_bingbot::before { background-position-y: -40px }
|
||||
&.bot_applebot::before { background-position-y: -60px }
|
||||
&.bot_openai::before { background-position-y: -80px }
|
||||
@@ -49,8 +49,7 @@
|
||||
&.bot_seznambot::before { background-position-y: -140px }
|
||||
&.bot_claude::before { background-position-y: -160px }
|
||||
|
||||
/* platform icons */
|
||||
|
||||
/* platform icons */
|
||||
&.platform::before { background-image: url('img/platforms.png') }
|
||||
&.pf_win10::before { background-position-y: -20px }
|
||||
&.pf_winold::before, dd.platform_winold::before,
|
||||
@@ -59,18 +58,19 @@
|
||||
&.pf_macosold::before { background-position-y: -80px }
|
||||
&.pf_ios::before { background-position-y: -100px }
|
||||
&.pf_android::before { background-position-y: -120px }
|
||||
&.pf_androidold::before { background-position-y: -140px }
|
||||
&.pf_linux::before { background-position-y: -160px }
|
||||
&.pf_bsd::before { background-position-y: -180px }
|
||||
&.pf_chromium::before { background-position-y: -200px }
|
||||
&.pf_hmos::before { background-position-y: -220px }
|
||||
&.pf_tizen::before { background-position-y: -240px }
|
||||
&.pf_fire::before { background-position-y: -260px }
|
||||
|
||||
/* browser icons */
|
||||
&.client::before { background-image: url('img/clients.png') }
|
||||
&.cl_firefox::before { background-position-y: -20px }
|
||||
&.cl_safari::before { background-position-y: -40px }
|
||||
&.cl_chrome::before { background-position-y: -60px }
|
||||
&.cl_chromeold::before { background-position-y: -60px; opacity: 75%; filter: ~"saturate(25%)"; }
|
||||
&.cl_msedge::before { background-position-y: -80px }
|
||||
&.cl_msie::before { background-position-y: -100px }
|
||||
&.cl_opera::before { background-position-y: -120px }
|
||||
@@ -81,6 +81,10 @@
|
||||
&.cl_vivaldi::before { background-position-y: -220px }
|
||||
&.cl_aol::before { background-position-y: -240px }
|
||||
&.cl_ya::before { background-position-y: -260px }
|
||||
&.cl_silk::before { background-position-y: -280px }
|
||||
&.cl_ffold::before { background-position-y: -300px }
|
||||
&.cl_chromeold::before { background-position-y: -320px }
|
||||
&.cl_ecosia::before { background-position-y: -340px }
|
||||
|
||||
/* Country flags */
|
||||
/* Note: flag images and CSS adapted from: https://github.com/lafeber/world-flags-sprite/ */
|
||||
@@ -329,13 +333,18 @@
|
||||
&.typ_php::before { background-position-y: -40px }
|
||||
&.typ_ip::before { background-position-y: -60px }
|
||||
&.typ_usr::before { background-position-y: -80px }
|
||||
|
||||
/* External link icons */
|
||||
&.extlink::before { background-image: url('img/links.png') }
|
||||
&.extlink.dnscheck::before { background-position-y: -20px }
|
||||
&.extlink.ipinfo::before { background-position-y: -40px }
|
||||
}
|
||||
|
||||
/* grid layout for the overview: */
|
||||
.botmon_overview_grid {
|
||||
& {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 0 .33em;
|
||||
}
|
||||
dl {
|
||||
@@ -354,7 +363,7 @@
|
||||
header {
|
||||
background-color: #F0F0F0;
|
||||
color: #333;
|
||||
border: #666 solid 1px;
|
||||
border: #999 solid 1px;
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
margin: .5rem 0 1pt 0;
|
||||
padding: .25rem .5rem;
|
||||
@@ -432,7 +441,7 @@
|
||||
}
|
||||
& > div {
|
||||
padding: .5rem;
|
||||
border: #CCC solid 1px;
|
||||
border: #999 solid 1px;
|
||||
border-top-width: 0;
|
||||
border-radius: 0 0 .25rem .25rem;
|
||||
}
|
||||
@@ -448,7 +457,7 @@
|
||||
}
|
||||
& > details > summary {
|
||||
background-color: #F0F0F0;
|
||||
border: #CCC solid 1px;
|
||||
border: #999 solid 1px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +492,9 @@
|
||||
border-bottom: #CCC solid 1px;
|
||||
border-radius: .7em;
|
||||
}
|
||||
|
||||
details ul > li > details > summary.noServer {
|
||||
opacity: 67%;
|
||||
}
|
||||
details ul > li > details > summary > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -519,26 +530,49 @@
|
||||
& {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2rem;
|
||||
margin: 0;
|
||||
padding: 0 .25em;
|
||||
}
|
||||
&:nth-child(odd) {
|
||||
background-color: #DFDFDF;
|
||||
}
|
||||
span {
|
||||
&.visit-length {
|
||||
min-width: min-content;
|
||||
}
|
||||
&.bounce {
|
||||
width: 1.25em; height: 1.25em;
|
||||
overflow: hidden;
|
||||
}
|
||||
&.bounce::before {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 1.25em; height: 1.25em;
|
||||
background: transparent url('img/bounce.svg') center no-repeat;
|
||||
background-size: 1.25em;
|
||||
}
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
span[data-lang] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
span[data-lang]::after {
|
||||
content: attr(data-lang);
|
||||
font-size: smaller;
|
||||
color: #555;
|
||||
border: #555 solid 1px;
|
||||
line-height: 1.25;
|
||||
border-radius: 2pt;
|
||||
padding: 0 1pt;
|
||||
margin-left: .2em;
|
||||
}
|
||||
span.first-seen {
|
||||
min-width: 4.2em;
|
||||
text-align: right;;
|
||||
}
|
||||
span.bounce {
|
||||
width: 1.25em; height: 1.25em;
|
||||
overflow: hidden;
|
||||
}
|
||||
span.bounce::before {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 1.25em; height: 1.25em;
|
||||
background: transparent url('img/bounce.svg') center no-repeat;
|
||||
background-size: 1.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +583,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
li:nth-child(odd) {
|
||||
background-color: #EEE;
|
||||
background-color: #DFDFDF;
|
||||
}
|
||||
li.total {
|
||||
border-top: #333 solid 1px;
|
||||
@@ -587,7 +621,7 @@
|
||||
column-gap: .25rem;
|
||||
background-color: #F0F0F0;
|
||||
color: #333;
|
||||
border: #666 solid 1px;
|
||||
border: #999 solid 1px;
|
||||
border-radius: 0 0 .5rem .5rem;
|
||||
margin: 1pt 0 0 0;
|
||||
padding: .25rem .5rem;
|
||||
|
||||
6
tick.php
6
tick.php
@@ -2,6 +2,12 @@
|
||||
|
||||
// Note: this script is normally called in HEAD mode, therefore it can not return any payload.
|
||||
|
||||
// quit out if it is called without athe right parameters:
|
||||
if (!isset($_GET['id']) || !isset($_GET['p'])) {
|
||||
http_response_code(400);
|
||||
die("Parameter error.");
|
||||
}
|
||||
|
||||
// what is the session identifier?
|
||||
$sessionId = preg_replace('/[\x00-\x1F{};\"\']/', "\u{FFFD}", $_GET['id']) /* clean json parameter */
|
||||
?? session_id()
|
||||
|
||||
Reference in New Issue
Block a user