From d49ab21345bfb6cded26d15d08eeffa4cb39f627 Mon Sep 17 00:00:00 2001 From: Sascha Leib Date: Sat, 25 Oct 2025 15:58:32 +0200 Subject: [PATCH] UI improvements And also style changes --- action.php | 124 ++++++++++++++++++--------- admin.css | 21 ++++- admin.js | 162 ++++++++++++++++++++++------------- captcha.js | 33 ++++--- conf/default.php | 4 +- config/default-whitelist.txt | 12 ++- config/known-ipranges.json | 7 +- img/captcha.png | Bin 0 -> 2705 bytes img/stages.png | Bin 3484 -> 4215 bytes lang/de/wordlist.txt | 5 +- style.less | 22 ++++- 11 files changed, 261 insertions(+), 129 deletions(-) create mode 100644 img/captcha.png diff --git a/action.php b/action.php index 75869d0..46bdcf3 100644 --- a/action.php +++ b/action.php @@ -23,17 +23,23 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { global $ACT; + // initialize the session id and type with random data: + $this->sessionId = rand(1000000, 9999999); + $this->sessionType = 'rnd'; + // insert header data into the page: - if ($ACT == 'show') { + if ($ACT == 'show' || $ACT == 'edit' || $ACT == 'media') { $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertHeader'); + + // Override the page rendering, if a captcha needs to be displayed: + $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'showCaptcha'); + } else if ($ACT == 'admin' && isset($_REQUEST['page']) && $_REQUEST['page'] == 'botmon') { $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'insertAdminHeader'); - } - - // Override the page rendering, if a captcha needs to be displayed: - if ($ACT !== 'admin') { - $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'showCaptcha'); - } + } + + // also show a captcha before the image preview + $controller->register_hook('TPL_IMG_DISPLAY', 'BEFORE', $this, 'showImageCaptcha'); // write to the log after the page content was displayed: $controller->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', $this, 'writeServerLog'); @@ -59,14 +65,8 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { // populate the session id and type: $this->getSessionInfo(); - // is there a user logged in? - $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ? $INFO['userinfo']['name'] : ''); - // build the tracker code: - $code = "document._botmon = {t0: Date.now(), session: " . json_encode($this->sessionId) . ", seed: " . json_encode($this->getConf('captchaSeed')) . ", ip: " . json_encode($_SERVER['REMOTE_ADDR']) . "};" . NL; - if ($username) { - $code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; - } + $code = $this->getBMHeader(); // add the deferred script loader:: $code .= DOKU_TAB . DOKU_TAB . "addEventListener('DOMContentLoaded', function(){" . NL; @@ -78,6 +78,22 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { $event->data['script'][] = ['_data' => $code]; } + /* create the BM object code for insertion into a script element: */ + private function getBMHeader() { + + // build the tracker code: + $code = DOKU_TAB . DOKU_TAB . "document._botmon = {t0: Date.now(), session: " . json_encode($this->sessionId) . ", seed: " . json_encode($this->getConf('captchaSeed')) . ", ip: " . json_encode($_SERVER['REMOTE_ADDR']) . "};" . NL; + + // is there a user logged in? + $username = ( !empty($INFO['userinfo']) && !empty($INFO['userinfo']['name']) ? $INFO['userinfo']['name'] : ''); + if ($username) { + $code .= DOKU_TAB . DOKU_TAB . 'document._botmon.user = "' . $username . '";'. NL; + } + + return $code; + + } + /** * Inserts tracking code to the page header * (only called on 'show' actions) @@ -120,7 +136,8 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { substr($conf['lang'],0,2), /* page language */ implode(',', array_unique(array_map( function($it) { return substr(trim($it),0,2); }, explode(',',trim($_SERVER['HTTP_ACCEPT_LANGUAGE'], " \t;,*"))))), /* accepted client languages */ $this->getCountryCode(), /* GeoIP country code */ - $this->showCaptcha /* show captcha? */ ); + $this->showCaptcha /* show captcha? */ + ); //* create the log line */ $filename = __DIR__ .'/logs/' . gmdate('Y-m-d') . '.srv.txt'; /* use GMT date for filename */ @@ -182,37 +199,68 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { $this->sessionId = $_SERVER['REMOTE_ADDR']; $this->sessionType = 'ip'; } - if (!$this->sessionId) { /* if everything else fails, just us a random ID */ - $this->sessionId = rand(1000000, 9999999); - $this->sessionType = 'rand'; - } } public function showCaptcha(Event $event) { $useCaptcha = $this->getConf('useCaptcha'); - if ($useCaptcha !== 'disabled' && $this->checkCaptchaCookie() && !$this->captchaWhitelisted()) { + $cCode = '-'; + if ($useCaptcha !== 'disabled') { + if ($this->captchaWhitelisted()) { + $cCode = 'W'; // whitelisted + } elseif ($this->hasCaptchaCookie()) { + $cCode = 'N'; // user already has a cookie + } else { + $cCode = 'Y'; // show the captcha - $this->showCaptcha = 'Y'; // captcha will be shown. - echo '

'; tpl_pagetitle(); echo "

\n"; // always show the original page title - $event->preventDefault(); // don't show normal content - switch ($useCaptcha) { - case 'blank': - $this->insertBlankBox(); // show dada filler instead of text - break; - case 'dada': - $this->insertDadaFiller(); // show dada filler instead of text - break; + echo '

'; tpl_pagetitle(); echo "

\n"; // always show the original page title + $event->preventDefault(); // don't show normal content + switch ($useCaptcha) { + case 'blank': + $this->insertBlankBox(); // show dada filler instead of text + break; + case 'dada': + $this->insertDadaFiller(); // show dada filler instead of text + break; + } + $this->insertCaptchaLoader(); // and load the captcha } - $this->insertCaptchaLoader(); // and load the captcha - } else { - $this->showCaptcha = 'N'; // do not show a captcha } + $this->showCaptcha = $cCode; // store the captcha code for the logfile + } - private function checkCaptchaCookie() { + public function showImageCaptcha(Event $event, $param) { + + $useCaptcha = $this->getConf('useCaptcha'); + + echo ''; + + $cCode = '-'; + if ($useCaptcha !== 'disabled') { + if ($this->captchaWhitelisted()) { + $cCode = 'W'; // whitelisted + } + elseif ($this->hasCaptchaCookie()) { + $cCode = 'N'; // user already has a cookie + } + else { + $cCode = 'Y'; // show the captcha + + echo ''; // placeholder image + $event->preventDefault(); // don't show normal content + + // TODO Insert dummy image + $this->insertCaptchaLoader(); // and load the captcha + } + }; + + $this->showCaptcha = $cCode; // store the captcha code for the logfile + } + + private function hasCaptchaCookie() { $cookieVal = isset($_COOKIE['DWConfirm']) ? $_COOKIE['DWConfirm'] : null; @@ -223,7 +271,7 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { //echo ''; - return $cookieVal !== $expected; + return $cookieVal == $expected; } // check if the visitor's IP is on a whitelist: @@ -251,15 +299,13 @@ class action_plugin_botmon extends DokuWiki_Action_Plugin { $to = inet_pton($col[1]); if ($ip >= $from && $ip <= $to) { - //echo "

Found my IP in range: " . $col[0] . " - " . $col[1] . "

"; - return true; + return true; /* IP whitelisted */ } } } } } - - return false; + return false; /* IP not found in whitelist */ } private function insertCaptchaLoader() { diff --git a/admin.css b/admin.css index 734ee97..d8dad0e 100644 --- a/admin.css +++ b/admin.css @@ -108,6 +108,12 @@ &.cl_operaold::before { background-position-y: -380px } &.cl_other::before { background-image: url('img/more.svg') } + /* Captcha statuses */ + &.captcha::before { background-image: url('img/captcha.png') } + &.cap_Y::before { background-position-y: -20px } + &.cap_YN::before { background-position-y: -40px } + &.cap_W::before { background-position-y: -60px } + /* Country flags */ /* Note: flag images and CSS adapted from: https://github.com/lafeber/world-flags-sprite/ */ &.country::before { @@ -730,21 +736,28 @@ background-image: url('img/info.svg') } - /* pageviews */ - span.pageviews { + /* pages seen */ + span.pageseen, span.pageviews { border: #999 solid 1px; padding: 0 2px; font-size: smaller; border-radius: .5em; margin-right: .25em; } - span.pageviews::before { + span.pageseen::before { content : ''; display: inline-block; width: 1.25em; height: 1.25em; background: transparent url('img/page.svg') center no-repeat; background-size: 1.25em; } + span.pageviews::before { + content : ''; + display: inline-block; + width: 1.25em; height: 1.25em; + background: transparent url('img/views.svg') center no-repeat; + background-size: 1.25em; + } } /* item footer */ @@ -890,7 +903,7 @@ border-top-color: #CCC; } } - span.pageviews { + span.pageseen, span.pageviews { border-color: #555; } diff --git a/admin.js b/admin.js index 49765e5..0661f9e 100644 --- a/admin.js +++ b/admin.js @@ -291,7 +291,7 @@ BotMon.live = { // shortcut to make code more readable: const model = BotMon.live.data.model; - const timeout = 60 * 60 * 1000; // session timeout: One hour + //const timeout = 60 * 60 * 1000; // session timeout: One hour if (visitor._type == BM_USERTYPE.KNOWN_BOT) { // known bots match by their bot ID: @@ -303,13 +303,14 @@ BotMon.live = { return v; } } + } else { // other types match by their DW/PHPIDs: // loop over all visitors already registered and check for ID matches: for (let i=0; i nv.ts) { - visitor._firstSeen = nv.ts; - } + }; + + // update first and last seen: + if (visitor._firstSeen > nv.ts) { + visitor._firstSeen = nv.ts; + } + if (visitor._lastSeen < nv.ts) { + visitor._lastSeen = nv.ts; } - // find browser + // update total loads and views (not the same!): + visitor._loadCount += 1; + visitor._viewCount += (nv.captcha == 'Y' ? 0 : 1); + + // ...because also a captcha is a "load", but not a "view". + // let's count the captcha statuses as well: + if (nv.captcha) visitor._captcha[nv.captcha] += 1; // is this visit already registered? let prereg = model._getPageView(visitor, nv); @@ -401,14 +416,15 @@ BotMon.live = { // add new page view: prereg = model._makePageView(nv, type); visitor._pageViews.push(prereg); - } else { - // update last seen date - prereg._lastSeen = nv.ts; - // increase view count: - prereg._viewCount += 1; - prereg._tickCount += 1; } + // update last seen date + prereg._lastSeen = nv.ts; + + // increase view count: + prereg._loadCount += (visitor.captcha == 'Y' ? 0 : 1); + //prereg._tickCount += 1; + // update referrer state: visitor._hasReferrer = visitor._hasReferrer || (prereg.ref !== undefined && prereg.ref !== ''); @@ -520,7 +536,8 @@ BotMon.live = { _lastSeen: data.ts, _seenBy: [type], _jsClient: ( type !== BM_LOGTYPE.SERVER), - _viewCount: 1, + _viewCount: 0, + _loadCount: 0, _tickCount: 0 }; } @@ -572,19 +589,19 @@ BotMon.live = { // count visits and page views: this.data.totalVisits += 1; - this.data.totalPageViews += v._pageViews.length; + this.data.totalPageViews += v._viewCount; // check for typical bot aspects: let botScore = 0; if (v._type == BM_USERTYPE.KNOWN_BOT) { // known bots - this.data.bots.known += v._pageViews.length; + this.data.bots.known += v._viewCount; this.groups.knownBots.push(v); } else if (v._type == BM_USERTYPE.KNOWN_USER) { // known users */ - this.data.bots.users += v._pageViews.length; + this.data.bots.users += v._viewCount; this.groups.users.push(v); } else { @@ -596,11 +613,11 @@ BotMon.live = { if (e.isBot) { // likely bots v._type = BM_USERTYPE.LIKELY_BOT; - this.data.bots.suspected += v._pageViews.length; + this.data.bots.suspected += v._viewCount; this.groups.suspectedBots.push(v); } else { // probably humans v._type = BM_USERTYPE.PROBABLY_HUMAN; - this.data.bots.human += v._pageViews.length; + this.data.bots.human += v._viewCount; this.groups.humans.push(v); } } @@ -640,7 +657,7 @@ BotMon.live = { //console.log(BotMon.live.data.analytics.groups.knownBots); let botsList = BotMon.live.data.analytics.groups.knownBots.toSorted( (a, b) => { - return b._pageViews.length - a._pageViews.length; + return b._viewCount - a._viewCount; }); const other = { @@ -659,12 +676,12 @@ BotMon.live = { rList.push({ id: it._bot.id, name: (it._bot.n ? it._bot.n : it._bot.id), - count: it._pageViews.length + count: it._viewCount }); } else { other.count += it._pageViews.length; }; - total += it._pageViews.length; + total += it._viewCount; } }; @@ -997,7 +1014,7 @@ BotMon.live = { const list = me.groups[type]; list.forEach(it => { - bounces += (it._pageViews.length <= 1 ? 1 : 0); + bounces += (it._viewCount <= 1 ? 1 : 0); }); return bounces; @@ -1085,7 +1102,7 @@ BotMon.live = { } // add to counter: - ipRec.count += v._pageViews.length; + ipRec.count += v._viewCount; }, @@ -1520,12 +1537,13 @@ BotMon.live = { // are there at lest num pages loaded? smallPageCount: function(visitor, num) { - return (visitor._pageViews.length <= Number(num)); + return (visitor._viewCount <= Number(num)); }, // There was no entry in a specific log file for this visitor: // note that this will also trigger the "noJavaScript" rule: noRecord: function(visitor, type) { + if (!visitor._seenBy.includes('srv')) return false; // only if 'srv' is also specified! return !visitor._seenBy.includes(type); }, @@ -1613,7 +1631,7 @@ BotMon.live = { // At least x page views were recorded, but they come within less than y seconds loadSpeed: function(visitor, minItems, maxTime) { - if (visitor._pageViews.length >= minItems) { + if (visitor._viewCount >= minItems) { //console.log('loadSpeed', visitor._pageViews.length, minItems, maxTime); const pvArr = visitor._pageViews.map(pv => pv._lastSeen).sort(); @@ -1704,7 +1722,7 @@ BotMon.live = { switch (type) { case "srv": typeName = "Server"; - columns = ['ts','ip','pg','id','typ','usr','agent','ref','lang','accept','geo']; + columns = ['ts','ip','pg','id','typ','usr','agent','ref','lang','accept','geo','captcha']; break; case "log": typeName = "Page load"; @@ -2299,20 +2317,11 @@ BotMon.live = { }, data.id)); } - // seen by icon: - span1.appendChild(make('span', { - 'class': 'icon_only seenby sb_' + data._seenBy.join(''), - 'title': "Seen by: " + data._seenBy.join('+') - }, data._seenBy.join(', '))); + span1.appendChild(make('span', { /* page views */ + 'class': 'has_icon pageseen', + 'title': data._pageViews.length + " page load(s)" + }, data._pageViews.length)); - // 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") )); - } // referer icons: if ((data._type == BM_USERTYPE.PROBABLY_HUMAN || data._type == BM_USERTYPE.LIKELY_BOT) && data.ref) { @@ -2326,15 +2335,26 @@ BotMon.live = { summary.appendChild(span1); const span2 = make('span'); /* right-hand group */ - span2.appendChild(make('span', { /* first-seen */ - 'class': 'has_iconfirst-seen', - 'title': "First seen: " + data._firstSeen.toLocaleString() + " UTC" - }, BotMon.t._formatTime(data._firstSeen))); + // country flag: + if (data.geo && data.geo !== 'ZZ') { + span2.appendChild(make('span', { + 'class': 'icon_only country ctry_' + data.geo.toLowerCase(), + 'data-ctry': data.geo, + 'title': "Country: " + ( data._country || "Unknown") + }, ( data._country || "Unknown") )); + } - span2.appendChild(make('span', { /* page views */ - 'class': 'has_icon pageviews', - 'title': data._pageViews.length + " page view(s)" - }, data._pageViews.length)); + span2.appendChild(make('span', { // seen-by icon: + 'class': 'icon_only seenby sb_' + data._seenBy.join(''), + 'title': "Seen by: " + data._seenBy.join('+') + }, data._seenBy.join(', '))); + + const captchaCode = '' + ( data._captcha['Y'] > 0 ? 'Y' : '' ) + ( data._captcha['N'] > 0 ? 'N' : '' ) + ( data._captcha['W'] > 0 ? 'W' : '' ); + if (captchaCode !== '') { + span2.appendChild(make('span', { // captcha status + 'class': 'icon_only captcha cap_' + captchaCode, + }, captchaCode)); + } summary.appendChild(span2); @@ -2414,6 +2434,13 @@ BotMon.live = { dl.appendChild(make('dd', {'class': 'lastSeen'}, data._lastSeen.toLocaleString())); } + dl.appendChild(make('dt', {}, "Actions:")); + dl.appendChild(make('dd', {'class': 'views'}, + "Page loads: " + data._loadCount.toString() + + ( data._captcha['Y'] > 0 || data._captcha['W'] || data._captcha['-'] > 0 ? ", captchas: " + (data._captcha['Y']+data._captcha['W']+data._captcha['-']).toString() : '') + + ", views: " + data._viewCount.toString() + )); + dl.appendChild(make('dt', {}, "User-Agent:")); dl.appendChild(make('dd', {'class': 'agent'}, data.agent)); @@ -2441,6 +2468,12 @@ BotMon.live = { 'title': "Country: " + data._country }, data._country + ' (' + data.geo + ')')); } + if (data.captcha && data.captcha !=='') { + dl.appendChild(make('dt', {}, "Captcha-status:")); + dl.appendChild(make('dd', { + 'class': 'captcha' + }, data.captcha)); + } dl.appendChild(make('dt', {}, "Session ID:")); dl.appendChild(make('dd', {'class': 'has_icon session typ_' + data.typ}, data.id)); @@ -2528,12 +2561,19 @@ BotMon.live = { 'title': "PageID: " + page.pg }, page.pg)); /* DW Page ID */ - // get the time difference: - row1.appendChild(make('span', { - 'class': 'first-seen', - 'title': "First visited: " + page._firstSeen.toLocaleString() + " UTC" - }, BotMon.t._formatTime(page._firstSeen))); - + const rightGroup = row1.appendChild(make('div')); // right-hand group + + // get the time difference: + rightGroup.appendChild(make('span', { + 'class': 'first-seen', + 'title': "First visited: " + page._firstSeen.toLocaleString() + " UTC" + }, BotMon.t._formatTime(page._firstSeen))); + + rightGroup.appendChild(make('span', { /* page loads */ + 'class': 'has_icon pageviews', + 'title': page._viewCount.toString() + " page load(s)" + }, page._viewCount.toString())); + pgLi.appendChild(row1); /* LINE 2 */ diff --git a/captcha.js b/captcha.js index 9f64912..cb79a4e 100644 --- a/captcha.js +++ b/captcha.js @@ -26,7 +26,7 @@ const $BMCaptcha = { // Checkbox: const lbl = document.createElement('label'); - lbl.innerHTML = 'Click to confirm.Checking …Loading …'; + lbl.innerHTML = 'Click to confirm.Checking …Loading …An error occured.'; const cb = document.createElement('input'); cb.setAttribute('type', 'checkbox'); cb.setAttribute('disabled', 'disabled'); @@ -152,26 +152,35 @@ const $BMCaptcha = { if (e.target.checked) { //document.getElementById('botmon_captcha_box').close(); - const dat = [ // the data to encode - document._botmon.seed || '', - location.hostname, - document._botmon.ip || '0.0.0.0', - (document._botmon.t0 ? new Date(document._botmon.t0) : new Date()).toISOString().substring(0, 10) - ]; - const hash = $BMCaptcha.digest.hash(dat.join('|')); + try { + var $status = 'loading'; - // set the cookie: - document.cookie = "DWConfirm=" + hash + ';path=/;'; + // generate the hash: + const dat = [ // the data to encode + document._botmon.seed || '', + location.hostname, + document._botmon.ip || '0.0.0.0', + (new Date()).toISOString().substring(0, 10) + ]; + const hash = $BMCaptcha.digest.hash(dat.join('|')); + + // set the cookie: + document.cookie = "DWConfirm=" + hash + ';path=/;'; + + } catch (err) { + console.error(err); + $status = 'error'; + } // change the interface: const dlg = document.getElementById('botmon_captcha_box'); if (dlg) { dlg.classList.remove('ready'); - dlg.classList.add('loading'); + dlg.classList.add( $status ); } // reload the page: - window.location.reload(true); + if ($status !== 'error')window.location.reload(true); } }, diff --git a/conf/default.php b/conf/default.php index 48f2cf5..53540b4 100644 --- a/conf/default.php +++ b/conf/default.php @@ -6,5 +6,5 @@ */ $conf['geoiplib'] = 'disabled'; -$conf['useCaptcha'] = 0; -$conf['captchaSeed'] = 'b472719ba5634d378a7d7f9bfc46659f'; +$conf['useCaptcha'] = 'disabled'; +$conf['captchaSeed'] = 'c53bc5f94929451987efa6c768d8856b'; diff --git a/config/default-whitelist.txt b/config/default-whitelist.txt index eaa0688..397720d 100644 --- a/config/default-whitelist.txt +++ b/config/default-whitelist.txt @@ -335,6 +335,12 @@ 2001:4860:4801:000c:: 2001:4860:4801:000c:FFFF:FFFF:FFFF:FFFF 64 2001:4860:4801:000f:: 2001:4860:4801:000f:FFFF:FFFF:FFFF:FFFF 64 -# PlagAware Bot - from previous visits’ IPs -157.90.90.163 157.90.90.163 32 -2a01:4f8:2190:21a0::2 2a01:4f8:2190:21a0::2 64 +# SeznamBot - IPs from: https://o-seznam.cz/napoveda/vyhledavani/en/seznambot-crawler/ +77.75.76.0 77.75.79.255 22 +2a02:0598:0064:8a00:0000:0000:3100:0000 2a02:0598:0064:8a00:0000:0000:3100:001f 123 +2a02:0598:0128:8a00:0000:0000:0b00:0000 2a02:0598:0128:8a00:0000:0000:0b00:001f 123 +2a02:0598:0096:8a00:0000:0000:1200:0120 2a02:0598:0096:8a00:0000:0000:1200:013f 123 + +# localhosts +127.0.0.1 127.255.255.255 8 +::1 ::1 128 \ No newline at end of file diff --git a/config/known-ipranges.json b/config/known-ipranges.json index 466dddd..4e955cb 100644 --- a/config/known-ipranges.json +++ b/config/known-ipranges.json @@ -1,10 +1,12 @@ { "groups": [ {"id": "alibaba", "name": "Alibaba"}, - {"id": "amazon", "name": "Amazon DS"}, + {"id": "amazon", "name": "Amazon"}, + {"id": "bezeq", "name": "Bezeq Int."}, {"id": "brasilnet", "name": "BrasilNet"}, {"id": "charter", "name": "Charter Inc."}, {"id": "chinanet", "name": "Chinanet"}, + {"id": "cloudflare", "name": "Cloudflare Inc."}, {"id": "cnisp", "name": "China ISP"}, {"id": "cnmob", "name": "China Mobile"}, {"id": "google", "name": "Google LLC"}, @@ -44,7 +46,7 @@ {"from": "52.96.0.0", "to": "52.127.255.254", "m": 11, "g": "microsoft"}, {"from": "54.0.0.0", "to": "54.255.255.254", "m": 8, "g": "amazon"}, {"from": "66.249.64.0", "to": "66.249.95.254", "m": 19, "g": "google"}, - {"from": "84.37.35.0", "to": "84.37.255.254", "g": "gtt"}, + {"from": "82.80.0.0", "to": "82.81.255.255", "m": 15, "g": "bezeq"}, {"from": "94.74.64.0", "to": "94.74.127.254", "m": 18, "g": "huawei"}, {"from": "91.84.96.0", "to": "91.84.127.254", "m": 19, "g": "vdsina"}, {"from": "101.0.0.0", "to": "101.255.255.254", "m": 8,"g": "chinanet"}, @@ -98,6 +100,7 @@ {"from": "2603:8000::::::", "to": "2603:80ff:ffff:ffff:ffff:ffff:ffff:ffff", "m": 24, "g": "charter"}, {"from": "2607:a400::::::", "to": "2607:a400:ffff:ffff:ffff:ffff:ffff:ffff", "m": 32, "g": "zenlayer"}, {"from": "2804:::::::", "to": "2804:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "m": 16, "g": "misc_sa"}, + {"from": "2a09:bac3::::::", "to": "2a09:bac3:ffff:ffff:ffff:ffff:ffff:ffff", "m": 32,"g": "cloudflare"}, {"from": "2a0a:4cc0::::::", "to": "2a0a:4cc0:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "g": "netcup"} ] } \ No newline at end of file diff --git a/img/captcha.png b/img/captcha.png new file mode 100644 index 0000000000000000000000000000000000000000..6e43e46b9398dcd2ce6643325e4fcffc6b4cd08f GIT binary patch literal 2705 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 b%Kil1QB_LDXVw4u(g92iEf7`u?)Ux+Dn!D~ literal 0 HcmV?d00001 diff --git a/img/stages.png b/img/stages.png index 0ac0c9e63f0e2fb3795cf0bd10fff93b9f0df13c..4d2621f7a7008c399283d728209c79182e7ab3ad 100644 GIT binary patch delta 3938 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 g%iii7L=XFmGw(FsKK2eO=-5$Rg4