RU AdList JS Fixes

try to take over the world!

07.06.2018 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         RU AdList JS Fixes
// @namespace    ruadlist_js_fixes
// @version      20180607.0
// @description  try to take over the world!
// @author       lainverse & dimisa
// @supportURL   https://greatest.deepsurf.us/en/scripts/19993-ru-adlist-js-fixes/feedback
// @match        *://*/*
// @exclude      *://auth.wi-fi.ru/*
// @exclude      *://*.alfabank.ru/*
// @exclude      *://alfabank.ru/*
// @exclude      *://*.unicreditbanking.net/*
// @exclude      *://unicreditbanking.net/*
// @exclude      *://*.telegram.org/*
// @exclude      *://telegram.org/*
// @grant        unsafeWindow
// @grant        window.close
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    let win = (unsafeWindow || window);

    // MooTools are crazy enough to replace standard browser object window.Document: https://mootools.net/core
    // Occasionally their code runs before my script on some domains and causes all kinds of havoc.
    let _Document = Object.getPrototypeOf(HTMLDocument);
    let _Element = Object.getPrototypeOf(HTMLElement);
    // dTree 2.05 in some cases replaces Node object
    let _Node = Object.getPrototypeOf(_Element);

    // http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
    //let isOpera = (!!window.opr && !!window.opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0,
    //let isChrome = !!window.chrome && !!window.chrome.webstore,
    let isSafari =
        Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
        (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window.safari || window.safari.pushNotification);
    let isFirefox = 'InstallTrigger' in win;
    let inIFrame = (win.self !== win.top);
    let _getAttribute = _Element.prototype.getAttribute,
        _setAttribute = _Element.prototype.setAttribute,
        _removeAttribute = _Element.prototype.removeAttribute,
        _de = document.documentElement,
        _appendChild = _Document.prototype.appendChild.bind(_de),
        _removeChild = _Document.prototype.removeChild.bind(_de),
        _createElement = _Document.prototype.createElement.bind(document);

    if (isFirefox && // Exit on image pages in Fx
        document.constructor.prototype.toString() === '[object ImageDocumentPrototype]')
        return;

    // NodeList iterator polyfill (mostly for Safari)
    // https://jakearchibald.com/2014/iterators-gonna-iterate/
    if (!NodeList.prototype[Symbol.iterator])
        NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

    // Wrapper to run scripts designed to override objects available to other scripts
    // Required in old versions of Firefox (<58) or when running with Greasemonkey
    let skipLander = true;
    try {
        skipLander = !(isFirefox && ('StopIteration' in win || GM.info.scriptHandler === 'Greasemonkey'));
    } catch(ignore){}
    let batchLand = [];
    let batchPrepend = [];
    let _APIString = 'let win = window, _Document = Object.getPrototypeOf(HTMLDocument), _Element = Object.getPrototypeOf(HTMLElement), _Node = Object.getPrototypeOf(_Element);';
    let landScript = (f, pre) => {
        let script = _createElement('script');
        script.textContent = `(()=>{${_APIString}${(
            (pre.length > 0 ? pre.join(';') : '')
        )};(${f.join(')();(')})();})();`;
        _appendChild(script);
        _removeChild(script);
    };
    let scriptLander = f => f();
    if (!skipLander) {
        scriptLander = (func, ...prepend) => {
            prepend.forEach(
                x => batchPrepend.includes(x) ? null : batchPrepend.push(x)
            );
            batchLand.push(func);
        };
        document.addEventListener(
            'DOMContentLoaded', () => void (scriptLander = (f, ...prep) => landScript([f], prep)), false
        );
    }

    function nullTools(opts) {
        let nt = this;
        opts = opts || {};
        let log = (...args) => opts.log && console.log(...args);
        let warn = (...args) => console.warn(...args);
        let trace = (...args) => (opts.log || opts.trace) && warn(...args);

        nt.destroy = function(o, destroy) {
            if (!opts.destroy && !destroy && o instanceof Object)
                return;
            log('cleaning', o);
            try {
                for (let item in o) {
                    if (item instanceof Object)
                        nt.destroy(item);
                    delete o[item];
                }
            } catch (e) {
                log('Error in object destructor', e);
            }
        };

        nt.define = function(obj, prop, val, enumerable = true) {
            try {
                Object.defineProperty(
                    obj, prop, {
                        get: () => val,
                        set: v => {
                            if (v !== val) {
                                log(`set ${prop} of`, obj, 'to', v);
                                nt.destroy(v);
                            }
                            return true;
                        },
                        enumerable: enumerable
                    }
                );
            } catch (err) {
                console.log(`Unable to redefine "${prop}" in `, obj, err);
            }
        };
        nt.proxy = function(obj) {
            return new Proxy(
                obj, {
                    get: (t, p) => p in t ? t[p] : console.warn(`Missing ${p} in`, t),
                    set: (t, p, v) => {
                        if (v !== t[p]) {
                            log(`set ${p} of`, t, 'to', v);
                            nt.destroy(v);
                        }
                        return true
                    }
                }
            );
        };
        nt.func = (val, name = '', force_log = false) => (...args) => {
            (force_log ? warn : trace)(`call ${name}(`, ...args,`) return`, val);
            return val;
        };
    }

    // Debug function, lists all unusual window properties
    function getStrangeObjectsList() {
        console.warn('Strangers list start');
        let _ts = Function.prototype.toString;
        let _skip = 'frames/self/window/webkitStorageInfo'.split('/');
        for (let n in win) {
            let val = win[n];
            if (val && !_skip.includes(n) && (win !== window && val !== window[n] || win === window) &&
                (!(val instanceof Function) ||
                 val instanceof Function &&
                 !(new RegExp (`^function\\s(${n})?\\(\\)[\\s\\r\\n]*\\{[\\s\\r\\n]*\\[native\\scode\\][\\s\\r\\n]*\\}$`)).test(_ts.call(val))))
                console.log(`${n} =`, val);
        }
        console.warn('Strangers list end');
    }

    // Fake objects of advertisement networks to break their workflow
    scriptLander(
        function() {
            let nt = new nullTools();
            // Popular adblock detector
            if (!('fuckAdBlock' in win)) {
                let FuckAdBlock = function(options) {
                    let self = this;
                    self._options = {
                        checkOnLoad: false,
                        resetOnEnd: false,
                        checking: false
                    };
                    self.setOption = function(opt, val) {
                        if (val)
                            self._options[opt] = val;
                        else
                            Object.assign(self._options, opt);
                    };
                    if (options)
                        self.setOption(options);

                    self._var = { event: {} };
                    self.clearEvent = function() {
                        self._var.event.detected = [];
                        self._var.event.notDetected = [];
                    };
                    self.clearEvent();

                    self.on = function(detected, fun) {
                        self._var.event[detected?'detected':'notDetected'].push(fun);
                        return self;
                    };
                    self.onDetected = function(cb) {
                        return self.on(true, cb);
                    };
                    self.onNotDetected = function(cb) {
                        return self.on(false, cb);
                    };
                    self.emitEvent = function() {
                        for (let fun of self._var.event.notDetected)
                            fun();
                        if (self._options.resetOnEnd)
                            self.clearEvent();
                        return self;
                    };
                    self._creatBait = () => null;
                    self._destroyBait = () => null;
                    self._checkBait = function() {
                        setTimeout((() => self.emitEvent()), 1);
                    };
                    self.check = function() {
                        self._checkBait();
                        return true;
                    };

                    let callback = function() {
                        if (self._options.checkOnLoad)
                            setTimeout(self.check, 1);
                    };
                    window.addEventListener('load', callback, false);
                };
                nt.define(win, 'FuckAdBlock', FuckAdBlock);
                nt.define(win, 'fuckAdBlock', new FuckAdBlock({
                    checkOnLoad: true,
                    resetOnEnd: true
                }));
            }

            // CoinHive miner stub. Continuous 100% CPU load can easily kill some CPU with overheat.
            if (!('CoinHive' in win))
                if (location.hostname !== 'cnhv.co') {
                    // CoinHive stub for cases when it doesn't affect site functionality
                    let CoinHiveConstructor = function() {
                        console.warn('Fake CoinHive miner created.');
                        this.setThrottle = nt.func(null);
                        this.start = nt.func(null);
                        this.on = nt.func(null);
                        this.getTotalHashes = nt.func(0);
                    };
                    let CoinHiveStub = nt.proxy({
                        Anonymous: CoinHiveConstructor,
                        User: CoinHiveConstructor,
                        Token: CoinHiveConstructor,
                        JobThread: nt.func(null),
                        IF_EXCLUSIVE_TAB: false
                    });
                    nt.define(win, 'CoinHive', CoinHiveStub);
                } else {
                    // CoinHive wrapper to fool sites which expect it to actually work and return results
                    let CoinHiveObject;
                    Object.defineProperty(win, 'CoinHive', {
                        set: function(obj) {
                            if ('Token' in obj) {
                                console.log('[CoinHive] Token wrapper applied.');
                                let _Token = obj.Token.bind(obj);
                                obj.Token = function(siteKey, goal, params) {
                                    let _goal = goal;
                                    goal = goal > 256 ? 256 : goal;
                                    console.log(`[CoinHive] Original goal: ${_goal}, new smaller goal ${goal}.`);
                                    console.log(`With smaller goals server may return 'invalid_goal' error and stop working.`);
                                    let miner = _Token(siteKey, goal, params);
                                    miner.setThrottle(0.99);
                                    miner.setThrottle = () => null;
                                    let _start = miner.start.bind(miner);
                                    miner.start = function() {
                                        let res = _start(window.CoinHive.FORCE_EXCLUSIVE_TAB);
                                        return res;
                                    };
                                    let _getTotalHashes = miner.getTotalHashes;
                                    miner.getTotalHashes = function() {
                                        return Math.trunc(_getTotalHashes.call(this) / goal * _goal);
                                    };
                                    let __emit = miner._emit;
                                    miner._emit = function(state, props) {
                                        let _self = this;
                                        console.log('[CoinHive] state:', state, props);
                                        if (state === 'job')
                                            setTimeout(() => {
                                                _self.stop();
                                                _self._emit('accepted', { hashes: goal });
                                            }, 1000);
                                        return __emit.apply(_self, arguments);
                                    };
                                    let _on = miner.on.bind(miner);
                                    miner.on = function(type, callback) {
                                        if (type === 'accepted') {
                                            console.log('[CoinHive] "accepted" callback wrapper applied.');
                                            let _callback = callback;
                                            callback = function(params) {
                                                console.log('[CoinHive] "accepted" callback is called, imitating original goal being reached.');
                                                params.hashes = _goal;
                                                return _callback.apply(this, arguments);
                                            };
                                            miner.stop();
                                        }
                                        return _on(type, callback);
                                    };
                                    return miner;
                                };
                            }
                            CoinHiveObject = obj;
                        },
                        get: () => CoinHiveObject
                    });
                }

            // VideoJS player wrapper
            VideoJS: {
                let _videojs = win.videojs || void 0;
                Object.defineProperty(win, 'videojs', {
                    get: () => _videojs,
                    set: f => {
                        if (f === _videojs)
                            return true;
                        console.log('videojs =', f);
                        _videojs = new Proxy(f, {
                            apply: (tgt, ths, args) => {
                                console.log('videojs(', ...args, ')');
                                let params = args[1];
                                if (params) {
                                    if (params.hasAd)
                                        params.hasAd = false;
                                    if (params.plugins && params.plugins.vastClient)
                                        delete params.plugins.vastClient;
                                }
                                let res = tgt.apply(ths, args);
                                console.log('player = ', res);
                                return res;
                            }
                        });
                    }
                });
            }

            // piguiqproxy-like script loaded from random RU domains
            let fab_application_define = nt.func(null, 'fab_application.define', true);
            fab_application_define.amd = nt.func(null, 'fab_application.define.amd');
            let fab_application = {
                requirejs: nt.proxy(nt.func(null, 'fab_application.requirejs')),
                require: nt.proxy(nt.func(null, 'fab_application.require')),
                define: nt.proxy(fab_application_define)
            };
            nt.define(win, 'fab_application', nt.proxy(fab_application));

            // Yandex API (ADBTools, Metrika)
            let hostname = location.hostname;
            if (location.protocol === 'about:' ||
                // Thank you, Greasemonkey, now I have to check for this. -_-
                hostname.startsWith('google.') || hostname.includes('.google.') ||
                // Google likes to define odd global variables like Ya
                ((hostname.startsWith('yandex.') || hostname.includes('.yandex.')) &&
                 /^\/((yand)?search|images)/i.test(location.pathname) &&
                 !hostname.startsWith('news.')) ||
                // Also, Yandex uses their Ya object for a lot of things on their pages and
                // wrapping it may cause problems. It's better to skip it in some cases.
                hostname.endsWith('chatango.com') || hostname.endsWith('github.io') ||
                hostname.endsWith('grimtools.com'))
                return;

            let YaProps = new Set();
            function onAdvManager (Ya, rootProp, obj) {
                return new Proxy(obj, {
                    set: (tgt, prop, val) => {
                        if (prop === 'AdvManager') {
                            console.log(`Ya.${rootProp} = Ya.Context`);
                            nt.define(Ya, rootProp, Ya.Context);
                            YaProps.add(rootProp);
                        }
                        tgt[prop] = val;
                        return true;
                    },
                    get: (tgt, prop) => tgt[prop]
                });
            }
            let Rum = {};
            [
                '__timeMarks', '_timeMarks', '__deltaMarks', '_deltaMarks',
                '__defRes', '_defRes', '__defTimes', '_defTimes',
                'commonVars'
            ].forEach(name => void(Rum[name] = []));
            [
                'send', 'sendRaf', 'sendTimeMark', 'sendResTiming', 'sendTTI',
                'sendHeroElement', 'time', 'timeEnd', 'getTime', 'mark', 'init',
                'makeSubPage', 'observeDOMNode', 'isVisibilityChanged'
            ].forEach(name => void(Rum[name] = nt.func(null, `Ya.Rum.${name}`)));
            [
                'getSettings', 'getVarsList'
            ].forEach(name => void(Rum[name] = nt.func([], `Ya.Rum.${name}`)));
            [
                ['ajaxStart', 0], ['ajaxComplete', 0],
                ['enabled', true], ['_tti', null],
                ['vsChanged', false], ['vsStart', 'visible']
            ].forEach(([prop, val]) => void(Rum[prop] = val));
            Rum = nt.proxy(Rum);
            let Ya = new Proxy({}, {
                set: function(tgt, prop, val) {
                    if (val === tgt[prop])
                        return true;
                    if (prop === 'Rum') {
                        nt.define(tgt, prop, Rum);
                        YaProps.add(prop);
                        Object.assign(val, Rum);
                    }
                    if (YaProps.has(prop)) {
                        console.log(`Ya.${prop} \u2260`, val);
                        return true;
                    }
                    if (val instanceof Object && prop !== '__inline_params__')
                        val = onAdvManager(Ya, prop, val);
                    tgt[prop] = val;
                    console.log(`Ya.${prop} =`, val);
                    return true;
                },
                get: (tgt, prop) => tgt[prop]
            });
            let callWithParams = function(f) {
                f.call(this, Ya.__inline_params__ || {});
                Ya.__inline_params__ = null;
            };
            nt.define(Ya, 'callWithParams', callWithParams);
            nt.define(Ya, 'PerfCounters', nt.proxy({
                addCacheEvent: nt.func(null),
                getTime: nt.func(null),
                sendCacheEvents: nt.func(null),
                sendResTiming: nt.func(null),
                sendTimeMark: nt.func(null),
                __cacheEvents: []
            }));
            nt.define(Ya, '__isSent', true);
            nt.define(Ya, 'confirmUrl', '');
            nt.define(Ya, 'Direct', nt.proxy({
                insertInto: nt.func(null)
            }));
            nt.define(Ya, 'mediaCode', nt.proxy({
                create: function() {
                    if (inIFrame) {
                        console.log('Removed body of ad-frame.');
                        document.documentElement.removeChild(document.body);
                    }
                },
                moduleLoad: nt.func(null),
                setModule: nt.func(null)
            }));
            let extra = nt.proxy({
                extra: nt.proxy({ match: 0, confirm: '', src: '' }),
                id: 0, percent: 100, threshold: 1
            });
            nt.define(Ya, '_exp', nt.proxy({
                id: 0, coin: 0,
                choose: nt.func(extra),
                get: (prop) => extra.hasOwnProperty(prop) ? extra[prop] : null,
                getId: nt.func(0),
                defaultVersion: extra,
                getExtra: nt.func(extra.extra),
                getDefaultExtra: nt.func(extra.extra),
                versions: [extra]
            }));
            nt.define(Ya, 'c', nt.func(null));
            nt.define(Ya, 'ADBTools', function(){
                ['loadContext', 'testAdbStyle'].forEach(name => void(this[name] = nt.func(null)));
                this.getCurrentState = nt.func(true);
                return nt.proxy(this);
            });

            if (!location.hostname.includes('kinopoisk.ru')) {
                // kinopoisk uses adfox wrapped in Yandex code to display self-ads
                let adfoxCode = {};
                [
                    'create', 'reload', 'destroy',
                    'createAdaptive', 'moduleLoad',
                    'createScroll', 'pr'
                ].forEach(name => void(adfoxCode[name] = nt.func(null, `Ya.adfoxCode.${name}`)));
                nt.define(adfoxCode, 'xhrExperiment', nt.proxy({ isXhr: true, isControl: true }));
                nt.define(adfoxCode, 'isXhr', true);
                nt.define(Ya, 'adfoxCode', nt.proxy(adfoxCode));
            } else
                nt.define(win, 'abExperiments', nt.proxy([]));
            let managerForAdfox = {
                loaderVersion: 1,
                isCurrrencyExp: true,
                isReady: nt.func(true, 'Ya.headerBidding.managerForAdfox.isReady')
            };
            [
                'render', 'requestBids', 'requestBidForContainer',
                'getBidsForAdfoxByContainerId', 'addAdfoxCallback'
            ].forEach(name => void(managerForAdfox[name] = nt.func(null, `Ya.headerBidding.managerForAdfox.${name}`)));
            let headerBidding = nt.proxy({
                setSettings: opts => {
                    if (!(opts && opts.adUnits))
                        return null;
                    let ids = [];
                    for (let unit of opts.adUnits)
                        ids.push(unit.code);
                    createStyle(`#${ids.join(', #')} { display: none !important }`);
                },
                managerForAdfox: nt.proxy(managerForAdfox)
            });
            nt.define(Ya, 'headerBidding', headerBidding);

            let AdvManager = function() {
                [
                    'renderDirect',
                    'getBid', 'releaseBid',
                    'getSkipToken',
                    'getAdSessionId'
                ].forEach(name => void(this[name] = nt.func(null, `Ya.AdvManager.${name}`)));
                this.render = function(o) {
                    if (!o.renderTo)
                        return;
                    let placeholder = document.getElementById(o.renderTo);
                    let parent = placeholder.parentNode;
                    placeholder.style = 'display:none!important';
                    parent.style = (parent.getAttribute('style')||'') + 'height:auto!important';
                    // fix for Yandex TV pages
                    if (location.hostname.startsWith('tv.yandex.')) {
                        let sibling = placeholder.previousSibling;
                        if (sibling && sibling.classList && sibling.classList.contains('tv-spin'))
                            sibling.style.display = 'none';
                    }
                };
                return nt.proxy(this);
            };
            nt.define(Ya, 'Context', nt.proxy({
                __longExperiment: null,
                _callbacks: nt.proxy([]),
                _asyncModeOn: true,
                _init: nt.func(null),
                isAllowedRepeatAds: nt.func(null),
                AdvManager: new AdvManager()
            }));
            let Metrika = function() {
                [
                    'reachGoal', 'replacePhones',
                    'trackLinks', 'userParams',
                    'hit', 'params', 'clickmap'
                ].forEach(name => void(this[name] = nt.func(null, `Ya.Metrika.${name}`)));
                this.id = 0;
                return nt.proxy(this);
            };
            Metrika.counters = () => Ya._metrika.counters;
            nt.define(Ya, 'Metrika', Metrika);
            let counter = new Ya.Metrika();
            nt.define(Ya, '_metrika', nt.proxy({
                counter: counter,
                counters: [counter],
                hitParam: {},
                counterNum: 0,
                hitId: 0,
                v: 1
            }));
            nt.define(Ya, '_globalMetrikaHitId', 0);
            counter = {};
            [
                'cpHitDelayed','wHitDelayed',
                'hitDelayed','makeReq',
                'redir','hit','log','cp','w'
            ].forEach(name => void(counter[name] = nt.func(null, `Ya.counter.${name}`)));
            [
                'stringifyParams','_getVars',
                'getUid','getUrl','getHash'
            ].forEach(name => void(counter[name] = nt.func('')));
            nt.define(Ya, 'counter', nt.proxy(counter));
            nt.define(Ya, 'jserrors', []);
            nt.define(Ya, 'onerror', nt.func(null, 'Ya.onerror'));
            if (win.Ya) {
                console.log('Found existing Ya object:', win.Ya);
                for (let prop in win.Ya)
                    Ya[prop] = win.Ya[prop];
            }
            for (let prop in Ya)
                if (prop !== '__inline_params__')
                    YaProps.add(prop);
            nt.define(win, 'Ya', Ya);
            // Yandex.Metrika callbacks
            let yandex_metrika_callbacks = [];
            document.addEventListener(
                'DOMContentLoaded', () => {
                    yandex_metrika_callbacks.forEach((f) => f && f.call(window));
                    yandex_metrika_callbacks.length = 0;
                    yandex_metrika_callbacks.push = (f) => setTimeout(f, 0);
                }, false
            );
            nt.define(win, 'yandex_metrika_callbacks', yandex_metrika_callbacks);
        }, nullTools, createStyle
    );

    // Creates and return protected style (unless protection is manually disabled).
    // Protected style will re-add itself on removal and remaind enabled on attempt to disable it.
    function createStyle(rules, props, skip_protect) {
        props = props || {};
        props.type = 'text/css';

        function _protect(style) {
            if (skip_protect)
                return;

            Object.defineProperty(style, 'sheet', {
                value: null,
                enumerable: true
            });
            Object.defineProperty(style, 'disabled', {
                get: () => true, //pretend to be disabled
                set: () => undefined,
                enumerable: true
            });
            (new MutationObserver(
                (ms) => _removeChild(ms[0].target)
            )).observe(style, { childList: true });
        }


        function _create() {
            let style = _appendChild(_createElement('style'));
            Object.assign(style, props);

            function insertRules(rule) {
                if (rule.forEach)
                    rule.forEach(insertRules);
                else try {
                    style.sheet.insertRule(rule, 0);
                } catch (e) {
                    console.error(e);
                }
            }

            insertRules(rules);
            _protect(style);

            return style;
        }

        let style = _create();
        if (skip_protect)
            return style;

        (new MutationObserver(
            function(ms) {
                let m, node;
                let createStyleInANewThread = resolve => setTimeout(
                    resolve => resolve(_create()),
                    0, resolve
                );
                let setStyle = st => void(style = st);
                for (m of ms) for (node of m.removedNodes)
                    if (node === style)
                        (new Promise(createStyleInANewThread))
                            .then(setStyle);
            }
        )).observe(_de, { childList: true });

        return style;
    }

    // Simplistic WebSocket wrapper for troublesome browsers and
    // disable 'onerror' handler for scripts from blacklisted sources
    scriptLander(() => {
        let masks = [],
            isBlocked = url => masks.some(mask => mask.test(url));
        for (let filter of [// blacklist
            '||185.87.50.147^',
            '||10root25.website^', '||24video.xxx^',
            '||adlabs.ru^', '||adspayformymortgage.win^', '||aviabay.ru^',
            '||bgrndi.com^', '||brokeloy.com^',
            '||cnamerutor.ru^',
            '||directadvert.ru^', '||docfilms.info^', '||dreadfula.ru^',
            '||et-code.ru^', '||etcodes.com^',
            '||franecki.net^', '||film-doma.ru^',
            '||free-torrent.org^', '||free-torrent.pw^',
            '||free-torrents.org^', '||free-torrents.pw^',
            '||game-torrent.info^', '||gocdn.ru^',
            '||hdkinoshka.com^', '||hghit.com^', '||hindcine.net^',
            '||joyreactor.cc^', '||pornreactor.cc^', '||reactor.cc^',
            '||kiev.ua^', '||kinotochka.net^',
            '||kinott.com^', '||kinott.ru^', '||kuveres.com^',
            '||lepubs.com^', '||luxadv.com^', '||luxup.ru^', '||luxupcdna.com^',
            '||mail.ru^', '||marketgid.com^', '||mebablo.com^', '||mixadvert.com^', '||mxtads.com^',
            '||nickhel.com^',
            '||oconner.biz^', '||oconner.link^', '||octoclick.net^', '||octozoon.org^',
            '||pkpojhc.com^',
            '||psma01.com^', '||psma02.com^', '||psma03.com^',
            '||recreativ.ru^', '||redtram.com^', '||regpole.com^',
            '||rootmedia.ws^', '||ruttwind.com^', '||rutvind.com^',
            '||skidl.ru^',
            '||torvind.com^', '||traffic-media.co^', '||trafmag.com^', '||ttarget.ru^',
            '||webadvert-gid.ru^', '||webadvertgid.ru^',
            '||xxuhter.ru^',
            '||yuiout.online^',
            '||zoom-film.ru^'])
            masks.push(new RegExp(
                filter.replace(/([\\/[\].*+?(){}$])/g, '\\$1')
                .replace(/\^(?!$)/g,'\\.?[^\\w%._-]')
                .replace(/\^$/,'\\.?([^\\w%._-]|$)')
                .replace(/^\|\|/,'^(ws|http)s?:\\/+([^/.]+\\.)*'),
                'i'));

        let _createElement = _Document.prototype.createElement,
            _addEventListener = _Element.prototype.addEventListener,
            _removeEventListener = _Element.prototype.removeEventListener;

        let scriptMap = new WeakMap();
        scriptMap.isBlocked = isBlocked;
        let onErrorWrapper = {
            set: function(val) {
                if (scriptMap.has(this)) {
                    _removeEventListener.call(this, 'error', scriptMap.get(this).wrp, false);
                    scriptMap.delete(this);
                }
                if (!val || typeof val !== 'function')
                    return val;

                scriptMap.set(this, {
                    org: val,
                    wrp: function(...args) {
                        if (scriptMap.isBlocked(this.src))
                            console.log('[WSI] Blocked "onerror" callback from', this.src);
                        else
                            scriptMap.get(this).org.apply(this, args);
                    }
                });
                _addEventListener.call(this, 'error', scriptMap.get(this).wrp, false);
            },
            get: function() {
                return scriptMap.has(this) ? scriptMap.get(this).org : null;
            },
            enumerable: true
        };
        _Document.prototype.createElement = function createElement(...args) {
            let el = _createElement.apply(this, args);

            // Custom onerror handler to block execution of callback if script source is blacklisted
            if (el instanceof HTMLScriptElement)
                Object.defineProperty(el, 'onerror', onErrorWrapper);
            // Block popular method to open a new window in Google Chrome by dispatching a custom click
            // event on a newly created anchor with _blank target. Untrusted events must not open a new window.
            if (el instanceof HTMLAnchorElement)
                el.addEventListener('click', e => {
                    if (!e.isTrusted && e.target && !e.target.parentNode && e.target.target[0] === '_') {
                        e.stopPropagation();
                        e.preventDefault();
                        console.log('Blocked untrusted click on parentless anchor:', e.target);
                    }
                }, false);

            return el;
        };
        let doc_proto = Object.getPrototypeOf(document);
        if (doc_proto && doc_proto.createElement !== _Document.prototype.createElement)
            doc_proto.createElement = _Document.prototype.createElement;
        // Simplistic WebSocket wrapper for Maxthon and Firefox before v58
        WSWrap: {
            if (/Maxthon/.test(navigator.appVersion) ||
                'InstallTrigger' in win && 'StopIteration' in win) {
                let _ws = Object.getOwnPropertyDescriptor(win, 'WebSocket');
                if (!_ws)
                    break WSWrap;
                _ws.value = new Proxy(_ws.value, {
                    construct: (ws, args) => {
                        if (isBlocked(args[0])) {
                            console.log('[WSI] Blocked WS connection:', args[0]);
                            return {};
                        }
                        return new ws(...args);
                    }
                });
                Object.defineProperty(win, 'WebSocket', _ws);
            }
        }
    });

    if (!isFirefox) {
        // scripts for non-Firefox browsers
        // https://greatest.deepsurf.us/scripts/14720-it-s-not-important
        {
            let imptt = /((display|(margin|padding)(-top|-bottom)?)\s*:[^;!]*)!\s*important/ig,
                ret_b = (a,b) => b,
                _toLowerCase = String.prototype.toLowerCase,
                protectedNodes = new WeakSet(),
                log = false;

            let logger = function() {
                if (log)
                    console.log('Some page elements became a bit less important.');
                log = false;
            };

            let unimportanter = function(node) {
                let style = (node.nodeType === _Node.ELEMENT_NODE) ?
                    _getAttribute.call(node, 'style') : null;

                if (!style || !imptt.test(style) || node.style.display === 'none' ||
                    (node.src && node.src.slice(0,17) === 'chrome-extension:')) // Web of Trust IFRAME and similar
                    return false; // get out if we have nothing to do here

                protectedNodes.add(node);
                _setAttribute.call(node, 'style',
                                   style.replace(imptt, ret_b));
                log = true;
            };

            (new MutationObserver(
                function(mutations) {
                    setTimeout(
                        function(ms) {
                            let m, node;
                            for (m of ms) for (node of m.addedNodes)
                                unimportanter(node);
                            logger();
                        }, 0, mutations
                    );
                }
            )).observe(document, {
                childList : true,
                subtree : true
            });

            _Element.prototype.setAttribute = function setAttribute(name, value) {
                "[native code]";
                let replaced = value;
                if (_toLowerCase.call(name) === 'style' && protectedNodes.has(this))
                    replaced = value.replace(imptt, ret_b);
                log = (replaced !== value);
                logger();
                return _setAttribute.call(this, name, replaced);
            };

            win.addEventListener (
                'load', () => {
                    for (let imp of document.querySelectorAll('[style*="!"]'))
                        unimportanter(imp);
                    logger();
                }, false
            );
        }

        // Naive ABP Style protector
        {
            let _querySelector = _Document.prototype.querySelector.bind(document);
            let _removeChild = _Node.prototype.removeChild;
            let _appendChild = _Node.prototype.appendChild;
            let createShadow = () => _createElement('shadow');
            // Prevent adding fake content entry point
            _Node.prototype.appendChild = function(child) {
                if (this instanceof ShadowRoot &&
                    child instanceof HTMLContentElement)
                    return _appendChild.call(this, createShadow());
                return _appendChild.apply(this, arguments);
            };
            {
                let _shadowSelector = ShadowRoot.prototype.querySelector;
                let _innerHTML = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'innerHTML');
                let _parentNode = Object.getOwnPropertyDescriptor(_Node.prototype, 'parentNode');
                if (_innerHTML && _parentNode) {
                    let _set = _innerHTML.set;
                    let _getParent = _parentNode.get;
                    _innerHTML.configurable = false;
                    _innerHTML.set = function() {
                        _set.apply(this, arguments);
                        let content = _shadowSelector.call(this, 'content');
                        if (content) {
                            let parent = _getParent.call(content);
                            _removeChild.call(parent, content);
                            _appendChild.call(parent, createShadow());
                        }
                    };
                }
                Object.defineProperty(ShadowRoot.prototype, 'innerHTML', _innerHTML);
            }
            // Locate and apply extra protection to a style on top of what ABP does
            let style;
            (new Promise(
                function(resolve, reject) {
                    let getStyle = () => _querySelector('::shadow style');
                    style = getStyle();
                    if (style)
                        return resolve(style);
                    let intv = setInterval(
                        function() {
                            style = getStyle();
                            if (!style)
                                return;
                            intv = clearInterval(intv);
                            return resolve(style);
                        }, 0
                    );
                    document.addEventListener(
                        'DOMContentLoaded', () => {
                            if (intv)
                                clearInterval(intv);
                            style = getStyle();
                            return style ? resolve(style) : reject();
                        }, false
                    );
                }
            )).then(
                function(style) {
                    let emptyArr = [],
                        nullStr = {
                            get: () => '',
                            set: () => undefined
                        };
                    let shadow = style.parentNode;
                    Object.defineProperties(shadow, {
                        childElementCount: { value: 0 },
                        styleSheets: { value: emptyArr },
                        firstChild: { value: null },
                        firstElementChild: { value: null },
                        lastChild: { value: null },
                        lastElementChild: { value: null },
                        childNodes: { value: emptyArr },
                        children: { value: emptyArr },
                        innerHTML: { value: nullStr },
                    });
                    Object.defineProperties(style, {
                        innerHTML: { value: nullStr },
                        textContent: { value: nullStr },
                        ownerDocument: { value: null },
                        parentNode: {value: null },
                        previousElementSibling: { value: null },
                        previousSibling: { value: null },
                        disabled: { get: () => true, set: () => null }
                    });
                    Object.defineProperties(style.sheet, {
                        deleteRule: { value: () => null },
                        disabled: { get: () => true, set: () => null },
                        cssRules: { value: emptyArr },
                        rules: { value: emptyArr }
                    });
                }
            ).catch(()=>null);
            _Node.prototype.removeChild = function(child) {
                if (child === style)
                    return;
                return _removeChild.apply(this, arguments);
            };
        }
    }
    if (/^https?:\/\/(mail\.yandex\.|music\.yandex\.|news\.yandex\.|(www\.)?yandex\.[^/]+\/(yand)?search[/?])/i.test(win.location.href) ||
        /^https?:\/\/tv\.yandex\./i.test(win.location.href)) {
        // https://greatest.deepsurf.us/en/scripts/809-no-yandex-ads
        let yadWord = /Яндекс.Директ/i,
            adWords = /Реклама|Ad/i;
        let _querySelector = document.querySelector.bind(document),
            _querySelectorAll = document.querySelectorAll.bind(document),
            _getAttribute = _Element.prototype.getAttribute,
            _setAttribute = _Element.prototype.setAttribute;
        // Function to attach an observer to monitor dynamic changes on the page
        let pageUpdateObserver = (func, obj, params) => {
            if (obj)
                (new MutationObserver(func))
                    .observe(obj, (params || { childList:true, subtree:true }));
        };
        // Short name for parentNode.removeChild and setAttribute style to display:none
        let remove = (node) => {
            if (!node || !node.parentNode)
                return false;
            console.log('Removed node.');
            node.parentNode.removeChild(node);
        };
        let hide = (node) => {
            if (!node)
                return false;
            console.log('Hid node.');
            _setAttribute.call(node, 'style', 'display:none!important');
        };
        // Yandex search ads in Google Chrome
        if ('attachShadow' in _Element.prototype) {
            let _attachShadow = _Element.prototype.attachShadow;
            _Element.prototype.attachShadow = function() {
                let node = this,
                    root = _attachShadow.apply(node, arguments);
                pageUpdateObserver(
                    (ms) => {
                        for (let m of ms) if (m.addedNodes.length)
                            if (adWords.test(root.textContent))
                                remove(node.closest('.serp-item'));
                    }, root
                );
                return root;
            };
        }
        // prevent/defuse adblock detector
        setInterval(()=>{
            localStorage.ic = '';
            localStorage._mt__data = '';
        },100);
        let _doc_proto = ('cookie' in _Document.prototype) ? _Document.prototype : Object.getPrototypeOf(document);
        let _cookie = Object.getOwnPropertyDescriptor(_doc_proto, 'cookie');
        if (_cookie) {
            let _set_cookie = Function.prototype.call.bind(_cookie.set);
            _cookie.set = function(value) {
                if (/^(mda=|yp=|ys=|yabs-|__)/.test(value))
                    // remove value, set expired
                    if (!value.startsWith('yp=')) {
                        value = value.replace(/^([^=]+=)[^;]+/,'$1').replace(/(expires=)[\w\s\d,]+/,'$1Thu, 01 Jan 1970 00');
                        console.log('expire cookie', value.match(/^[^=]+/)[0]);
                    } else {
                        let parts = value.split(';');
                        let values = parts[0].split('#').filter(part => /\.sp\./.test(part));
                        if (values.length)
                            values[0] = values[0].replace(/^yp=/, '');
                        parts[0] = `yp=${values.join('#')}`;
                        value = parts.join(';');
                        console.log(`set cookie ${parts[0]}`);
                    }
                return _set_cookie(this, value);
            };
            Object.defineProperty(_doc_proto, 'cookie', _cookie);
        }
        // other ads
        document.addEventListener(
            'DOMContentLoaded', () => {
                {
                    // Generic ads removal and fixes
                    let node = _querySelector('.serp-header');
                    if (node)
                        node.style.marginTop = '0';
                    for (node of _querySelectorAll(
                        '.serp-adv__head + .serp-item,'+
                        '#adbanner,'+
                        '.serp-adv,'+
                        '.b-spec-adv,'+
                        'div[class*="serp-adv__"]:not(.serp-adv__found):not(.serp-adv__displayed)'
                    )) remove(node);
                }
                // Search ads
                function removeSearchAds() {
                    for (let node of _querySelectorAll('.serp-item'))
                        if (_getAttribute.call(node, 'role') === 'complementary' ||
                            adWords.test((node.querySelector('.label')||{}).textContent))
                            hide(node);
                }
                // News ads
                function removeNewsAds() {
                    let node, block, items, mask, classes,
                        masks = [
                            { class: '.ads__wrapper', regex: /[^,]*?,[^,]*?\.ads__wrapper/ },
                            { class: '.ads__pool', regex: /[^,]*?,[^,]*?\.ads__pool/ }
                        ];
                    for (node of _querySelectorAll('style[nonce]')) {
                        classes = node.innerText.replace(/\{[^}]+\}+/ig, '|').split('|');
                        for (block of classes) for (mask of masks)
                            if (block.includes(mask.class)) {
                                block = block.match(mask.regex)[0];
                                items = _querySelectorAll(block);
                                for (item of items)
                                    remove(items[0]);
                            }
                    }
                }
                // Music ads
                function removeMusicAds() {
                    for (let node of _querySelectorAll('.ads-block'))
                        remove(node);
                }
                // Mail ads
                function removeMailAds() {
                    let slice = Array.prototype.slice,
                        nodes = slice.call(_querySelectorAll('.ns-view-folders')),
                        node, len, cls;

                    for (node of nodes)
                        if (!len || len > node.classList.length)
                            len = node.classList.length;

                    node = nodes.pop();
                    while (node) {
                        if (node.classList.length > len)
                            for (cls of slice.call(node.classList))
                                if (cls.indexOf('-') === -1) {
                                    remove(node);
                                    break;
                                }
                        node = nodes.pop();
                    }
                }
                // News fixes
                function removePageAdsClass() {
                    if (document.body.classList.contains("b-page_ads_yes")) {
                        document.body.classList.remove("b-page_ads_yes");
                        console.log('Page ads class removed.');
                    }
                }
                // TV fixes
                function removeTVAds() {
                    for (let node of _querySelectorAll('div[class^="_"][data-reactid] > div'))
                        if (yadWord.test(node.textContent) || node.querySelector('iframe:not([src])')) {
                            if (node.offsetWidth) {
                                let pad = document.createElement('div');
                                _setAttribute.call(pad, 'style', `width:${node.offsetWidth}px`);
                                node.parentNode.appendChild(pad);
                            }
                            remove(node);
                        }
                }

                if (location.hostname.startsWith('mail.')) {
                    pageUpdateObserver(
                        function(ms, o) {
                            let aside = _querySelector('.mail-Layout-Aside');
                            if (aside) {
                                o.disconnect();
                                pageUpdateObserver(removeMailAds, aside);
                            }
                        }, document.body
                    );
                    removeMailAds();
                } else if (location.hostname.startsWith('music.')) {
                    pageUpdateObserver(removeMusicAds, _querySelector('.sidebar'));
                    removeMusicAds();
                } else if (location.hostname.startsWith('news.')) {
                    pageUpdateObserver(removeNewsAds, document.body);
                    pageUpdateObserver(removePageAdsClass, document.body, { attributes:true, attributesFilter:['class'] });
                    removeNewsAds();
                    removePageAdsClass();
                } else if (location.hostname.startsWith('tv.')) {
                    pageUpdateObserver(removeTVAds, document.body);
                    removeTVAds();
                } else {
                    pageUpdateObserver(removeSearchAds, _querySelector('.main__content'));
                    removeSearchAds();
                }
            }
        );
    }

    // Yandex Link Tracking
    if (/^https?:\/\/([^.]+\.)*yandex\.[^/]+/i.test(win.location.href)) {
        // remove banner on the start page
        scriptLander(() => {
            let nt = new nullTools({log: false, trace: true});
            let AwapsJsonAPI_Json = function(...args) {
                console.log('>> new AwapsJsonAPI.Json(', ...args, ')');
            };
            [
                'setID', 'addImageContent',
                'sendCounts', 'expand', 'refreshAd'
            ].forEach(name => void(AwapsJsonAPI_Json.prototype[name] = nt.proxy(nt.func(null, `AwapsJsonAPI.Json.${name}`))));
            AwapsJsonAPI_Json.prototype.checkBannerVisibility = nt.proxy(nt.func(true, 'AwapsJsonAPI.Json.checkBannerVisibility'));
            AwapsJsonAPI_Json.prototype.addIframeContent = nt.proxy(function(...args) {
                try {
                    let frame = args[1][0].parentNode;
                    frame.parentNode.removeChild(frame);
                    console.log(`Removed banner placeholder.`);
                } catch(ignore) {
                    console.log(`Can't locate frame object to remove.`);
                }
            });
            AwapsJsonAPI_Json.prototype.getHTML = nt.proxy(nt.func('', 'AwapsJsonAPI.Json.getHTML'));
            AwapsJsonAPI_Json.prototype = nt.proxy(AwapsJsonAPI_Json.prototype);
            AwapsJsonAPI_Json = nt.proxy(AwapsJsonAPI_Json);
            if ('AwapsJsonAPI' in win) {
                console.log('Oops! AwapsJsonAPI already defined.');
                let f = win.AwapsJsonAPI.Json;
                win.AwapsJsonAPI.Json = AwapsJsonAPI_Json;
                if (f && f.prototype)
                    f.prototype = AwapsJsonAPI_Json.prototype;
            } else
                nt.define(win, 'AwapsJsonAPI', nt.proxy({
                    Json: AwapsJsonAPI_Json
                }));

            let home = win.home || {};
            let parseExport = x => {
                if (!x)
                    return x;
                // remove banner placeholder
                if (x.banner && x.banner.cls) {
                    let _parent = `.${x.banner.cls.banner__parent}`;
                    document.addEventListener('DOMContentLoaded', () => {
                        for (let banner of document.querySelectorAll(_parent)) {
                            _setAttribute.call(banner, 'style', 'display:none!important');
                            console.log('Hid banner placeholder.');
                        }
                    }, false);
                }

                // remove banner data and some other stuff
                delete x.banner;
                delete x.consistency;
                delete x['i-bannerid'];
                delete x['i-counter'];
                delete x['ga-counter'];
                delete x['promo-curtain'];

                return x;
            };
            let home_export = parseExport(home.export);
            Object.defineProperty(home, 'export', {
                get: () => home_export,
                set: x => {
                    home_export = parseExport(x);
                }
            });
            nt.define(win, 'home', home);
        }, nullTools, '_setAttribute = _Element.prototype.setAttribute');

        if ('attachShadow' in _Element.prototype) {
            let fakeRoot = () => ({
                firstChild: null,
                appendChild: ()=>null,
                querySelector: ()=>null,
                querySelectorAll: ()=>null
            });
            _Element.prototype.createShadowRoot = fakeRoot;
            let shadows = new WeakMap();
            let _attachShadow = Object.getOwnPropertyDescriptor(_Element.prototype, 'attachShadow');
            _attachShadow.value = function() {
                return shadows.set(this, fakeRoot()).get(this);
            };
            Object.defineProperty(_Element.prototype, 'attachShadow', _attachShadow);
            let _shadowRoot = Object.getOwnPropertyDescriptor(_Element.prototype, 'shadowRoot');
            _shadowRoot.set = () => null;
            _shadowRoot.get = function() {
                return shadows.has(this) ? shadows.get(this) : void 0;
            };
            Object.defineProperty(_Element.prototype, 'shadowRoot', _shadowRoot);
        }
        // Partially based on https://greatest.deepsurf.us/en/scripts/22737-remove-yandex-redirect
        let selectors = (
            'A[onmousedown*="/jsredir"],'+
            'A[data-vdir-href],'+
            'A[data-counter]'
        );
        let removeTrackingAttributes = function(link) {
            link.removeAttribute('onmousedown');
            if (link.hasAttribute('data-vdir-href')) {
                link.removeAttribute('data-vdir-href');
                link.removeAttribute('data-orig-href');
            }
            if (link.hasAttribute('data-counter')) {
                link.removeAttribute('data-counter');
                link.removeAttribute('data-bem');
            }
        };
        let removeTracking = function(scope) {
            for (let link of scope.querySelectorAll(selectors))
                removeTrackingAttributes(link);
        };
        document.addEventListener('DOMContentLoaded', (e) => removeTracking(e.target));
        (new MutationObserver(
            function(ms) {
                let m, node;
                for (m of ms) for (node of m.addedNodes)
                    if (node instanceof HTMLAnchorElement && node.matches(selectors))
                        removeTrackingAttributes(node);
                    else
                        removeTracking(node);
            }
        )).observe(_de, { childList: true, subtree: true });
    }

    // https://greatest.deepsurf.us/en/scripts/21937-moonwalk-hdgo-kodik-fix v0.8 (adapted)
    document.addEventListener(
        'DOMContentLoaded', function() {
            function log (name) {
                console.log(`Player FIX: Detected ${name} player in ${location.href}`);
            }
            function removeVast (data) {
                if (data && (data.vast || data.reserve_vast || data.vast_button)) {
                    console.log('Removed:\ndata.vast', data.vast, '\ndata.reserve_vast', data.reserve_vast, '\ndata.vast_button', data.vast_button);
                    delete data.vast;
                    delete data.reserve_vast;
                    delete data.vast_button;
                    if (data.chain) {
                        let need = [],
                            drop = [],
                            links = data.chain.split('.');
                        for (let link of links)
                            if (!/^vast_|_vast_|_vast$/.test(link))
                                need.push(link);
                            else
                                drop.push(link);
                        console.log('Dropped from the chain:', ...drop);
                        data.chain = need.join('.');
                    }
                }
                return data;
            }
            if (win.video_balancer !== void 0 && win.event_callback !== void 0) {
                log('Moonwalk');
                if (video_balancer.adv_loader)
                    removeVast(video_balancer.adv_loader.options);
                if ('_mw_adb' in win)
                    Object.defineProperty(win, '_mw_adb', {
                        get: () => false,
                        set: () => true
                    });
            } else if (win.stat_url !== undefined && win.is_html5 !== undefined && win.is_wp8 !== undefined) {
                log('HDGo');
                document.body.onclick = null;
                let tmp = document.querySelector('#swtf');
                if (tmp)
                    tmp.style.display = 'none';
                if (win.banner_second !== void 0)
                    win.banner_second = 0;
                if (win.$banner_ads !== void 0)
                    win.$banner_ads = false;
                if (win.$new_ads !== void 0)
                    win.$new_ads = false;
                if (win.createCookie !== void 0)
                    win.createCookie('popup', 'true', '999');
                if (win.canRunAds !== void 0 && win.canRunAds !== true)
                    win.canRunAds = true;
            } else if (win.startKodikPlayer !== void 0) {
                log('Kodik');
                let _ajax = win.$.ajax;
                win.$.ajax = (params, ...args) => {
                    if (params.success) {
                        let _s = params.success;
                        params.success = (data, ...args) => _s(removeVast(data), ...args);
                    }
                    return _ajax(params, ...args);
                }
            } else if (win.getnextepisode && win.uppodEvent) {
                log('Share-Serials.net');
                scriptLander(
                    function() {
                        let _setInterval = win.setInterval,
                            _setTimeout = win.setTimeout;
                        win.setInterval = function(func) {
                            if (func instanceof Function && func.toString().indexOf('_delay') > -1) {
                                let intv = _setInterval.call(
                                    this, function() {
                                        _setTimeout.call(
                                            this, function(intv) {
                                                clearInterval(intv);
                                                let timer = document.querySelector('#timer');
                                                if (timer)
                                                    timer.click();
                                            }, 100, intv);
                                        func.call(this);
                                    }, 5
                                );

                                return intv;
                            }
                            return _setInterval.apply(this, arguments);
                        };
                        win.setTimeout = function(func) {
                            if (func instanceof Function && func.toString().indexOf('adv_showed') > -1)
                                return _setTimeout.call(this, func, 0);
                            return _setTimeout.apply(this, arguments);
                        };
                    }
                );
            } else if ('ADC' in win) {
                log('vjs-creatives plugin in');
                let replacer = (obj) => {
                    for (let name in obj)
                        if (obj[name] instanceof Function)
                            obj[name] = () => null;
                };
                replacer(win.ADC);
                replacer(win.currentAdSlot);
            }
            UberVK: {
                if (!inIFrame)
                    break UberVK;
                let oddNames = 'HD' in win &&
                    !Object.getOwnPropertyNames(win).every(n => !n.startsWith('_0x'));
                if (!oddNames)
                    break UberVK;
                log('UberVK');
                XMLHttpRequest.prototype.open = () => {
                    throw 404;
                };
            }
        }, false
    );

    // piguiqproxy.com / zmctrack.net circumvention prevention
    scriptLander(
        () => {
            let knownRoots = new WeakSet();
            function wrapRequest(root) {
                if (knownRoots.has(root))
                    return;
                knownRoots.add(root);
                let _proto = void 0;
                try {
                    _proto = root.XMLHttpRequest.prototype;
                } catch(ignore) {
                    return;
                };
                let _open = _proto.open;
                // blacklist of third-party domains requests to which are ignored
                let blacklist = /[/.@](amgload\.net|dsn-fishki\.ru|kingoablc\.com|piguiqproxy\.com|rcdn\.pro|zmctrack\.net)[:/]/i;
                // blacklist of domains where all third-party requests are ignored
                let ondomains = /(^|[/.@])oane\.ws($|[:/])/i;
                // highly suspicious URLs
                let suspicious = /^https?:\/\/(csp-)?[a-z0-9]{6}\.ru\//i;
                let on_get_ban = /^https?:\/\/(csp-)?[a-z0-9]{6}\.ru\/([a-z0-9/]{45,}|[a-z0-9]{8,}|ad\/banner\/.+)$/i;
                let on_post_ban = /^https?:\/\/(csp-)?[a-z0-9]{6}\.ru\/([a-z0-9]{6,})$/i;

                let requestStopList = new WeakSet();

                _proto.open = function(method, url) {
                    if (method === 'GET' &&
                        (blacklist.test(url) || on_get_ban.test(url) ||
                         ondomains.test(location.hostname) && !ondomains.test(url)) ||
                        method === 'POST' && on_post_ban.test(url)) {
                        requestStopList.add(this);
                        console.log('Blocked request: ', url);
                        return;
                    }
                    if (suspicious.test(url))
                        console.warn('Suspicious request: ', url);
                    return _open.apply(this, arguments);
                };
                ['send', 'setRequestHeader', 'getAllResponseHeaders'].forEach(
                    name => {
                        let func = _proto[name];
                        _proto[name] = function(...args) {
                            return requestStopList.has(this) ? null : func.apply(this, args);
                        }
                    }
                );
            }

            wrapRequest(win);

            let _contentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
            let _get_contentWindow = _contentWindow.get;
            _contentWindow.get = function() {
                let _cw = _get_contentWindow.apply(this, arguments);
                if (_cw)
                    wrapRequest(_cw);
                return _cw;
            };
            Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', _contentWindow);
        }
    );

    // === Helper functions ===

    // function to search and remove nodes by content
    // selector - standard CSS selector to define set of nodes to check
    // words - regular expression to check content of the suspicious nodes
    // params - object with multiple extra parameters:
    //   .log - display log in the console
    //   .hide - set display to none instead of removing from the page
    //   .parent - parent node to remove if content is found in the child node
    //   .siblings - number of simling nodes to remove (excluding text nodes)
    let scRemove = (node) => node.parentNode.removeChild(node);
    let scHide = function(node) {
        let style = _getAttribute.call(node, 'style') || '',
            hide = ';display:none!important;';
        if (style.indexOf(hide) < 0)
            _setAttribute.call(node, 'style', style + hide);
    };

    function scissors (selector, words, scope, params) {
        let logger = (...args) => { if (params.log) console.log(...args) };
        if (!scope.contains(document.body))
            logger('[s] scope', scope);
        let remFunc = (params.hide ? scHide : scRemove),
            iterFunc = (params.siblings > 0 ? 'nextElementSibling' : 'previousElementSibling'),
            toRemove = [],
            siblings;
        for (let node of scope.querySelectorAll(selector)) {
            // drill up to a parent node if specified, break if not found
            if (params.parent) {
                let old = node;
                node = node.closest(params.parent);
                if (node === null || node.contains(scope)) {
                    logger('[s] went out of scope with', old);
                    continue;
                }
            }
            logger('[s] processing', node);
            if (toRemove.includes(node))
                continue;
            if (words.test(node.innerHTML)) {
                // skip node if already marked for removal
                logger('[s] marked for removal');
                toRemove.push(node);
                // add multiple nodes if defined more than one sibling
                siblings = Math.abs(params.siblings) || 0;
                while (siblings) {
                    node = node[iterFunc];
                    if (!node) break; // can't go any further - exit
                    logger('[s] adding sibling node', node);
                    toRemove.push(node);
                    siblings -= 1;
                }
            }
        }
        let toSkip = [];
        for (let node of toRemove)
            if (!toRemove.every(other => other === node || !node.contains(other)))
                toSkip.push(node);
        if (toRemove.length)
            logger(`[s] proceeding with ${params.hide?'hide':'removal'} of`, toRemove, `skip`, toSkip);
        for (let node of toRemove) if (!toSkip.includes(node))
            remFunc(node);
    }

    // function to perform multiple checks if ads inserted with a delay
    // by default does 30 checks withing a 3 seconds unless nonstop mode specified
    // also does 1 extra check when a page completely loads
    // selector and words - passed dow to scissors
    // params - object with multiple extra parameters:
    //   .log - display log in the console
    //   .root - selector to narrow down scope to scan;
    //   .observe - if true then check will be performed continuously;
    // Other parameters passed down to scissors.
    function gardener(selector, words, params) {
        let logger = (...args) => { if(params.log) console.log(...args) };
        params = params || {};
        logger(`[gardener] selector: '${selector}' detector: ${words} options: ${JSON.stringify(params)}`);
        let scope;
        let globalScope = [_de];
        let domLoaded = false;
        let getScope = root => root ? _de.querySelectorAll(root) : globalScope;
        let onevent = e => {
            logger(`[gardener] cleanup on ${Object.getPrototypeOf(e)} "${e.type}"`);
            for (let node of scope)
                scissors(selector, words, node, params);
        };
        let repeater = n => {
            if (!domLoaded && n) {
                setTimeout(repeater, 500, n - 1);
                scope = getScope(params.root);
                if (!scope) // exit if the root element is not present on the page
                    return 0;
                onevent({type: 'Repeater'});
            }
        };
        repeater(20);
        document.addEventListener(
            'DOMContentLoaded', (e) => {
                domLoaded = true;
                // narrow down scope to a specific element
                scope = getScope(params.root);
                if (!scope) // exit if the root element is not present on the page
                    return 0;
                logger('[g] scope', scope);
                // add observe mode if required
                if (params.observe) {
                    let params = { childList:true, subtree: true };
                    let observer = new MutationObserver(
                        function(ms) {
                            for (let m of ms)
                                if (m.addedNodes.length)
                                    onevent(m);
                        }
                    );
                    for (let node of scope)
                        observer.observe(node, params);
                    logger('[g] observer enabled');
                }
                onevent(e);
            }, false);
        // wait for a full page load to do one extra cut
        win.addEventListener('load', onevent, false);
    }

    // wrap popular methods to open a new tab to catch specific behaviours
    function createWindowOpenWrapper(openFunc) {
        let _createElement = _Document.prototype.createElement,
            _appendChild = _Element.prototype.appendChild,
            fakeNative = (f) => (f.toString = () => `function ${f.name}() { [native code] }`);

        let nt = new nullTools();
        fakeNative(openFunc);

        let parser = _createElement.call(document, 'a');
        let openWhitelist = (url, parent) => {
            parser.href = url;
            return parser.hostname === 'www.imdb.com' || parser.hostname === 'www.kinopoisk.ru' ||
                parent.hostname === 'radikal.ru' && url === void 0;
        };

        let redefineOpen = (root) => {
            if ('open' in root) {
                let _open = root.open.bind(root);
                nt.define(root, 'open', (...args) => {
                    if (openWhitelist(args[0], location)) {
                        console.log('Whitelisted popup:', ...args);
                        return _open(...args);
                    }
                    return openFunc(...args);
                });
            }
        };
        redefineOpen(win);

        function createElement() {
            '[native code]';
            let el = _createElement.apply(this, arguments);
            // redefine window.open in first-party frames
            if (el instanceof HTMLIFrameElement || el instanceof HTMLObjectElement)
                el.addEventListener('load', (e) => {
                    try {
                        redefineOpen(e.target.contentWindow);
                    } catch(ignore) {}
                }, false);
            return el;
        }
        fakeNative(createElement);

        let redefineCreateElement = (obj) => {
            for (let root of [obj.document, _Document.prototype]) if ('createElement' in root)
                nt.define(root, 'createElement', createElement);
        };
        redefineCreateElement(win);

        // wrap window.open in newly added first-party frames
        _Element.prototype.appendChild = function appendChild() {
            '[native code]';
            let el = _appendChild.apply(this, arguments);
            if (el instanceof HTMLIFrameElement)
                try {
                    redefineOpen(el.contentWindow);
                    redefineCreateElement(el.contentWindow);
                } catch(ignore) {}
            return el;
        };
        fakeNative(_Element.prototype.appendChild);
    }

    // Function to catch and block various methods to open a new window with 3rd-party content.
    // Some advertisement networks went way past simple window.open call to circumvent default popup protection.
    // This funciton blocks window.open, ability to restore original window.open from an IFRAME object,
    // ability to perform an untrusted (not initiated by user) click on a link, click on a link without a parent
    // node or simply a link with piece of javascript code in the HREF attribute.
    function preventPopups() {
        // call sandbox-me if in iframe and not whitelisted
        if (inIFrame) {
            win.top.postMessage({ name: 'sandbox-me', href: win.location.href }, '*');
            return;
        }

        scriptLander(() => {
            let nt = new nullTools({log:true});
            let open = (...args) => {
                '[native code]';
                console.warn('Site attempted to open a new window', ...args);
                return {
                    document: nt.proxy({
                        write: nt.func({}, 'write'),
                        writeln: nt.func({}, 'writeln')
                    }),
                    location: nt.proxy({})
                };
            };

            createWindowOpenWrapper(open);

            console.log('Popup prevention enabled.');
        }, nullTools, createWindowOpenWrapper);
    }

    // Helper function to close background tab if site opens itself in a new tab and then
    // loads a 3rd-party page in the background one (thus performing background redirect).
    function preventPopunders() {
        // create "close_me" event to call high-level window.close()
        let eventName = `close_me_${Math.random().toString(36).substr(2)}`;
        let callClose = () => {
            console.log('close call');
            window.close();
        };
        window.addEventListener(eventName, callClose, true);

        scriptLander(() => {
            // get host of a provided URL with help of an anchor object
            // unfortunately new URL(url, window.location) generates wrong URL in some cases
            let parseURL = document.createElement('A');
            let getHost = url => {
                parseURL.href = url;
                return parseURL.hostname
            };
            // site went to a new tab and attempts to unload
            // call for high-level close through event
            let closeWindow = () => window.dispatchEvent(new CustomEvent(eventName, {}));
            // check is URL local or goes to different site
            let isLocal = (url) => {
                if (url === location.pathname || url === location.href)
                    return true; // URL points to current pathname or full address
                let host = getHost(url);
                let site = location.hostname;
                return host !== '' && // URLs with unusual protocol may have empty 'host'
                    (site === host || site.endsWith(`.${host}`) || host.endsWith(`.${site}`));
            };

            let _open = window.open.bind(window);
            let open = (...args) => {
                '[native code]';
                let url = args[0];
                if (url && isLocal(url))
                    window.addEventListener('beforeunload', closeWindow, true);
                return _open(...args);
            };

            createWindowOpenWrapper(open);

            console.log("Background redirect prevention enabled.");
        }, `let eventName="${eventName}"`, nullTools, createWindowOpenWrapper);
    }

    // Mix between check for popups and popunders
    // Significantly more agressive than both and can't be used as universal solution
    function preventPopMix() {
        if (inIFrame) {
            win.top.postMessage({ name: 'sandbox-me', href: win.location.href }, '*');
            return;
        }

        // create "close_me" event to call high-level window.close()
        let eventName = `close_me_${Math.random().toString(36).substr(2)}`;
        let callClose = () => {
            console.log('close call');
            window.close();
        };
        window.addEventListener(eventName, callClose, true);

        scriptLander(() => {
            let _open = window.open,
                parseURL = document.createElement('A');
            // get host of a provided URL with help of an anchor object
            // unfortunately new URL(url, window.location) generates wrong URL in some cases
            let getHost = (url) => {
                parseURL.href = url;
                return parseURL.host;
            };
            // site went to a new tab and attempts to unload
            // call for high-level close through event
            let closeWindow = () => {
                _open(window.location,'_self');
                window.dispatchEvent(new CustomEvent(eventName, {}));
            };
            // check is URL local or goes to different site
            function isLocal(url) {
                let loc = window.location;
                if (url === loc.pathname || url === loc.href)
                    return true; // URL points to current pathname or full address
                let host = getHost(url),
                    site = loc.host;
                if (host === '')
                    return false; // URLs with unusual protocol may have empty 'host'
                if (host.length > site.length)
                    [site, host] = [host, site];
                return site.includes(host, site.length - host.length);
            }

            // add check for redirect for 5 seconds, then disable it
            function checkRedirect() {
                window.addEventListener('beforeunload', closeWindow, true);
                setTimeout(closeWindow=>window.removeEventListener('beforeunload', closeWindow, true), 5000, closeWindow);
            }

            function open(url, name) {
                '[native code]';
                if (url && isLocal(url) && (!name || name === '_blank')) {
                    console.warn('Suspicious local new window', arguments);
                    checkRedirect();
                    return _open.apply(this, arguments);
                }
                console.warn('Blocked attempt to open a new window', arguments);
                return {
                    document: {
                        write: () => {},
                        writeln: () => {}
                    }
                };
            }

            function clickHandler(e) {
                let link = e.target,
                    url = link.href||'';
                if (e.targetParentNode && e.isTrusted || link.target !== '_blank') {
                    console.log('Link', link, 'were created dinamically, but looks fine.');
                    return true;
                }
                if (isLocal(url) && link.target === '_blank') {
                    console.log('Suspicious local link', link);
                    checkRedirect();
                    return;
                }
                console.log('Blocked suspicious click on a link', link);
                e.stopPropagation();
                e.preventDefault();
            }

            createWindowOpenWrapper(open, clickHandler);

            console.log("Mixed popups prevention enabled.");
        }, `let eventName="${eventName}"`, createWindowOpenWrapper);
    }
    // External listener for case when site known to open popups were loaded in iframe
    // It will sandbox any iframe which will send message 'forbid.popups' (preventPopups sends it)
    // Some sites replace frame's window.location with data-url to run in clean context
    if (!inIFrame) window.addEventListener(
        'message', function(e) {
            if (!e.data || e.data.name !== 'sandbox-me' || !e.data.href)
                return;
            let src = e.data.href;
            for (let frame of document.querySelectorAll('iframe'))
                if (frame.contentWindow === e.source) {
                    if (frame.hasAttribute('sandbox')) {
                        if (!frame.sandbox.contains('allow-popups'))
                            return; // exit frame since it's already sandboxed and popups are blocked
                        // remove allow-popups if frame already sandboxed
                        frame.sandbox.remove('allow-popups');
                    } else
                        // set sandbox mode for troublesome frame and allow scripts, forms and a few other actions
                        // technically allowing both scripts and same-origin allows removal of the sandbox attribute,
                        // but to apply content must be reloaded and this script will re-apply it in the result
                        frame.setAttribute('sandbox','allow-forms allow-scripts allow-presentation allow-top-navigation allow-same-origin');
                    console.log('Disallowed popups from iframe', frame);

                    // reload frame content to apply restrictions
                    if (!src) {
                        src = frame.src;
                        console.log('Unable to get current iframe location, reloading from src', src);
                    } else
                        console.log('Reloading iframe with URL', src);
                    frame.src = 'about:blank';
                    frame.src = src;
                }
        }, false
    );

    function selectiveEval() {
        scriptLander(() => {
            let nt = new nullTools();
            let _eval = win.eval.bind(window);
            nt.define(win, 'eval', function(...args) {
                if (/_0x|location\s*?=|location.href\s*?=|location.assign\(|open\(/i.test(args[0])) {
                    console.log(`Skipped eval of ${args[0].slice(0, 512)}\u2026`);
                    return null;
                }
                return _eval(...args);
            });
        }, nullTools);
    }

    // === Scripts for specific domains ===

    let scripts = {};
    // prevent popups and redirects block
    // Popups
    scripts.preventPopups = {
        other: [
            'biqle.ru',
            'chaturbate.com',
            'dfiles.ru',
            'eporner.eu',
            'hentaiz.org',
            'mirrorcreator.com',
            'online-multy.ru',
            'radikal.ru', 'rumedia.ws',
            'thepiratebay.org',
            'unionpeer.com',
            'zippyshare.com'
        ],
        now: preventPopups
    };
    // Popunders (background redirect)
    scripts.preventPopunders = {
        other: [
            'lostfilm-online.ru',
            'mediafire.com', 'megapeer.org', 'megapeer.ru',
            'perfectgirls.net'
        ],
        now: preventPopunders
    };
    // PopMix (both types of popups encountered on site)
    scripts['openload.co'] = {
        other: ['oload.tv', 'oload.info'],
        now: () => {
            let nt = new nullTools();
            nt.define(win, 'CNight', win.CoinHive);
            if (location.pathname.startsWith('/embed/')) {
                nt.define(win, 'BetterJsPop', {
                    add: ((a, b) => console.warn('BetterJsPop.add', a, b)),
                    config: ((o) => console.warn('BetterJsPop.config', o)),
                    Browser: { isChrome: true }
                });
                nt.define(win, 'isSandboxed', nt.func(null));
                nt.define(win, 'adblock', false);
                nt.define(win, 'adblock2', false);
            } else preventPopMix();
        }
    };
    scripts['turbobit.net'] = preventPopMix;

    scripts['tapochek.net'] = () => {
        // workaround for moradu.com/apu.php load error handler script, not sure which ad network is this
        let _appendChild = Object.getOwnPropertyDescriptor(_Node.prototype, 'appendChild');
        let _appendChild_value = _appendChild.value;
        _appendChild.value = function appendChild(node) {
            if (this === win.document.body)
                if ((node instanceof HTMLScriptElement || node instanceof HTMLStyleElement) &&
                    /^https?:\/\/[0-9a-f]{15}\.com\/\d+(\/|\.css)$/.test(node.src) ||
                    node instanceof HTMLDivElement && node.style.zIndex > 900000 &&
                    node.style.backgroundImage.includes('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'))
                    throw '...eenope!';
            return _appendChild_value.apply(this, arguments);
        };
        Object.defineProperty(_Node.prototype, 'appendChild', _appendChild);

        // disable window focus tricks and changing location
        let focusHandlerName = /\WfocusAchieved\(/
        let _toString = Function.prototype.toString;
        let _setInterval = win.setInterval;
        win.setInterval = (...args) => {
            if (args.length && focusHandlerName.test(_toString.call(args[0]))) {
                console.log('skip setInterval for', ...args);
                return -1;
            }
            return _setInterval(...args);
        };
        let _addEventListener = win.addEventListener;
        win.addEventListener = function(...args) {
            if (args.length && args[0] === 'focus' && focusHandlerName.test(_toString.call(args[1]))) {
                console.log('skip addEventListener for', ...args);
                return void 0;
            }
            return _addEventListener.apply(this, args);
        };

        // generic popup prevention
        preventPopups();
    };

    scripts['rustorka.com'] = {
        other: ['rustorka.lib'],
        now: () => scriptLander(() => {
            let crumbler = () => {
                // crumble suspicious cookies
                let base = '=; expires=Thu, 01 Jan 1970 00:00:01 UTC; Max-Age=-99999999; path=/';
                console.log('cookies', document.cookie);
                for (let name of ['adblock', 'gophp', '_692293176245', '_692293176246'])
                    document.cookie = `${name}${base}`;
                for (let name of ['st2', 'st3']) {
                    document.cookie = `${name}${base}forum`;
                    document.cookie = `${name}${base}forum/`;
                }
                console.log('cookies', document.cookie);
            };
            document.addEventListener('DOMContentLoaded', crumbler, false);
            crumbler();

            let nt = new nullTools({trace: true});
            nt.define(win, 'syka', false);
            nt.define(win, '_692293176244', location.href);
            [
                'MTLuxup', 'MTAdSniper', 'MTutarg', 'MTUAatar', 'MTcityAds', 'MTmxMark',
                'MTmxMark2', 'MTmdnt', 'MTrfDumedia', 'MXsmTDS', 'MTritorno', 'MTadvice',
                'cyka', 'MTAdTraff', 'MTExebid', 'MXsockFound'
            ].forEach(name => nt.define(win, name, nt.func(null, name)));
            let _eval_def = Object.getOwnPropertyDescriptor(win, 'eval');
            if (!_eval_def)
                return;
            let _eval_val = _eval_def.value;
            _eval_def.value = (...args) => {
                if (args[0] && args[0].includes('antiadblock'))
                    return console.log('Anti-AdBlock script may run another day, but not today.');
                return _eval_val.apply(this, args);
            };
            Object.defineProperty(win, 'eval', _eval_def);
            win.open = (...args) => {
                console.warn(`Site attempted to open "${args[0]}" in a new window.`);
                location.replace(location.href);
                return null;
            };
            window.addEventListener('DOMContentLoaded', () => {
                let link = void 0;
                document.body.addEventListener('mousedown', e => {
                    link = e.target.closest('a, select, #fancybox-title-wrap');
                }, false);
                let _open = window.open.bind(window);
                let _getAttribute = _Element.prototype.getAttribute;
                win.open = (...args) => {
                    let url = args[0];
                    if (link instanceof HTMLAnchorElement) {
                        // third-party post links
                        let href = _getAttribute.call(link, 'href');
                        if (link.classList.contains('postLink') &&
                            !link.matches(`a[href*="${location.hostname}"]`) &&
                            (href === url || link.href === url))
                            return _open(...args);
                        // onclick # links
                        if (href === '#' && /window\.open/.test(_getAttribute.call(link, 'onclick')))
                            return _open(...args);
                        // force local links to load in the current window
                        if (href[0] === '/' || href.startsWith('./') || href.includes(`//${location.hostname}/`))
                            location.assign(href);
                    }
                    // list of image hostings under upload picture button (new comment)
                    if (link instanceof HTMLSelectElement &&
                        !url.includes(location.hostname) &&
                        link.value === url)
                        return _open(...args);
                    // open screenshot in a new window
                    if (link instanceof HTMLSpanElement &&
                        link.id === 'fancybox-title-wrap')
                        return _open(...args);
                    // looks like tabunder
                    if (link === null && url === location.href)
                        location.replace(url); // reload current page
                    // other cases
                    console.warn(`Site attempted to open "${url}" in a new window. Source: `, link);
                    return {};
                };
            }, true);
        }, nullTools)
    };

    // other
    scripts['1tv.ru'] = () => scriptLander(() => {
        let nt = new nullTools();
        nt.define(win, 'EUMPAntiblockConfig', nt.proxy({url: '//www.1tv.ru/favicon.ico'}));
        let disablePlugins = {
            'antiblock': false,
            'stat1tv': false
        };
        let _EUMPConfig = void 0;
        let _EUMPConfig_set = x => {
            if (x.plugins) {
                x.plugins = x.plugins.filter(plugin => (plugin in disablePlugins) ? !(disablePlugins[plugin] = true) : true);
                console.warn(`Player plugins: active [${x.plugins}], disabled [${Object.keys(disablePlugins).filter(x => disablePlugins[x])}]`);
            }
            _EUMPConfig = x;
        };
        if ('EUMPConfig' in win)
            _EUMPConfig_set(win.EUMPConfig);
        Object.defineProperty(win, 'EUMPConfig', {
            enumerable: true,
            get: () => _EUMPConfig,
            set: _EUMPConfig_set
        });
    }, nullTools);

    scripts['2picsun.ru'] = {
        other: [
            'pics2sun.ru', '3pics-img.ru'
        ],
        now: () => {
            Object.defineProperty(navigator, 'userAgent', {value: 'googlebot'});
        }
    };

    scripts['4pda.ru'] = {
        now: () => {
            // https://greatest.deepsurf.us/en/scripts/14470-4pda-unbrender
            let isForum = location.pathname.startsWith('/forum/'),
                remove = node => (node && node.parentNode.removeChild(node)),
                hide = node => (node && (node.style.display = 'none'));

            // save links to non-overridden functions to use later
            let protectedElems;
            // protect/hide changed attributes in case site attempt to restore them
            function styleProtector(eventMode) {
                let _toLowerCase = String.prototype.toLowerCase,
                    isStyleText = (t) => (_toLowerCase.call(t) === 'style'),
                    protectedElems = new WeakMap();
                function protoOverride(element, functionName, isStyleCheck, returnIfProtected) {
                    let originalFunction = element.prototype[functionName];
                    element.prototype[functionName] = function wrapper() {
                        if (protectedElems.has(this) && isStyleCheck(arguments[0]))
                            return returnIfProtected(this, arguments);
                        return originalFunction.apply(this, arguments);
                    };
                }
                protoOverride(Element, 'removeAttribute', isStyleText, () => undefined);
                protoOverride(Element, 'hasAttribute', isStyleText, (_this) => protectedElems.get(_this) !== null);
                protoOverride(Element, 'setAttribute', isStyleText, (_this, args) => protectedElems.set(_this, args[1]));
                protoOverride(Element, 'getAttribute', isStyleText, (_this) => protectedElems.get(_this));
                if (!eventMode)
                    return protectedElems;
                let e = document.createEvent('Event');
                e.initEvent('protoOverride', false, false);
                window.protectedElems = protectedElems;
                window.dispatchEvent(e);
            }
            if (!isFirefox)
                protectedElems = styleProtector(false);
            else {
                let script = document.createElement('script');
                script.textContent = `(${styleProtector.toString()})(true);`;
                window.addEventListener(
                    'protoOverride', function protoOverrideCallback() {
                        if (win.protectedElems) {
                            protectedElems = win.protectedElems;
                            delete win.protectedElems;
                        }
                        document.removeEventListener('protoOverride', protoOverrideCallback, true);
                    }, true
                );
                _appendChild(script);
                _removeChild(script);
            }

            // clean a page
            window.addEventListener(
                'DOMContentLoaded', function() {
                    let width = () => window.innerWidth || _de.clientWidth || document.body.clientWidth || 0;
                    let height = () => window.innerHeight || _de.clientHeight || document.body.clientHeight || 0;

                    HeaderAds: {
                        // hide ads above HEADER
                        let header = document.querySelector('.h-frame');
                        if (header)
                            header = header.parentNode;
                        for (let itm of header.parentNode.children)
                            if (itm !== header)
                                hide(itm);
                            else break;
                    }

                    if (isForum) {
                        let itm = document.querySelector('#logostrip');
                        if (itm)
                            remove(itm.parentNode.nextSibling);
                        // clear background in the download frame
                        if (location.pathname.startsWith('/forum/dl/')) {
                            let setBackground = node => _setAttribute.call(
                                node,
                                'style', (_getAttribute.call(node, 'style') || '') +
                                ';background-color:#4ebaf6!important'
                            );
                            setBackground(document.body);
                            for (let itm of document.querySelectorAll('body > div'))
                                if (!itm.querySelector('.dw-fdwlink, .content') && !itm.classList.contains('footer'))
                                    remove(itm);
                                else
                                    setBackground(itm);
                        }
                        // exist from DOMContentLoaded since the rest is not for forum
                        return;
                    }

                    FixNavMenu: {
                        // restore DevDB link in the navigation
                        let itm = document.querySelector('#nav li a[href$="/devdb/"]')
                        if (itm) itm.closest('li').style.display = 'block';
                        // hide ad link from the navigation
                        hide(document.querySelector('#nav li a[data-dotrack]'));
                    }
                    SidebarAds: {
                        // remove ads from sidebar
                        let aside = document.querySelectorAll('section[id] > :first-child + :last-child');
                        if (!aside.length)
                            break SidebarAds;
                        let post;
                        for (let side of aside)
                            for (let itm of Array.from(side.children)) {
                                post = itm.classList.contains('post');
                                if (itm.querySelector('iframe') && !post)
                                    remove(itm);
                                if (itm.querySelector('script, a[target="_blank"] > img') && !post || !itm.children.length)
                                    hide(itm);
                            }
                    }

                    document.body.setAttribute('style', (document.body.getAttribute('style')||'')+';background-color:#E6E7E9!important');

                    let extra = 'background-image:none!important;background-color:transparent!important',
                        fakeStyles = new WeakMap(),
                        styleProxy = {
                            get: (target, prop) => fakeStyles.get(target)[prop] || target[prop],
                            set: function(target, prop, value) {
                                let fakeStyle = fakeStyles.get(target);
                                ((prop in fakeStyle) ? fakeStyle : target)[prop] = value;
                                return true;
                            }
                        };
                    for (let itm of document.querySelectorAll('[id]:not(A), A')) {
                        if (!(itm.offsetWidth > 0.95 * width() &&
                              itm.offsetHeight > 0.85 * height()))
                            continue;
                        if (itm.tagName !== 'A') {
                            fakeStyles.set(itm.style, {
                                'backgroundImage': itm.style.backgroundImage,
                                'backgroundColor': itm.style.backgroundColor
                            });

                            try {
                                Object.defineProperty(itm, 'style', {
                                    value: new Proxy(itm.style, styleProxy),
                                    enumerable: true
                                });
                            } catch (e) {
                                console.log('Unable to protect style property.', e);
                            }

                            if (protectedElems)
                                protectedElems.set(itm, _getAttribute.call(itm, 'style'));

                            _setAttribute.call(itm, 'style', `${(_getAttribute.call(itm, 'style') || '')};${extra}`);
                        }
                        if (itm.tagName === 'A') {
                            if (protectedElems)
                                protectedElems.set(itm, _getAttribute.call(itm, 'style'));
                            _setAttribute.call(itm, 'style', 'display:none!important');
                        }
                    }
                }
            );
        }
    };

    scripts['adhands.ru'] = () => scriptLander(() => {
        let nt = new nullTools();
        try {
            let _adv;
            Object.defineProperty(win, 'adv', {
                get: () => _adv,
                set: (v) => {
                    console.log('Blocked advert on adhands.ru.');
                    nt.define(v, 'advert', '');
                    _adv = v;
                }
            });
        } catch (ignore) {
            if (!win.adv)
                console.log('Unable to locate advert on adhands.ru.');
            else {
                console.log('Blocked advert on adhands.ru.');
                nt.define(win.adv, 'advert', '');
            }
        }
    }, nullTools);

    scripts['all-episodes.tv'] = () => {
        let nt = new nullTools();
        nt.define(win, 'perX1', 2);
        createStyle('#advtss, #ad3, a[href*="/ad.admitad.com/"] { display:none!important }');
    };

    scripts['allhentai.ru'] = () => {
        selectiveEval();
        preventPopups();
        scriptLander(() => {
            let _onerror = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onerror');
            if (!_onerror)
                return;
            _onerror.set = (...args) => console.log(args[0].toString());
            Object.defineProperty(HTMLElement.prototype, 'onerror', _onerror);
        });
    };

    scripts['allmovie.pro'] = {
        other: ['rufilmtv.org'],
        dom: function() {
            // pretend to be Android to make site use different played for ads
            if (isSafari)
                return;
            Object.defineProperty(navigator, 'userAgent', {
                get: function(){
                    return 'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19';
                },
                enumerable: true
            });
        }
    };

    scripts['anidub-online.ru'] = {
        other: ['anime.anidub.com', 'online.anidub.com'],
        dom: function() {
            if (win.ogonekstart1)
                win.ogonekstart1 = () => console.log("Fire in the hole!");
        },
        now: () => createStyle([
            '.background {background: none!important;}',
            '.background > script + div,'+
            '.background > script ~ div:not([id]):not([class]) + div[id][class]'+
            '{display:none!important}'
        ])
    };

    scripts['drive2.ru'] = () => {
        gardener('.c-block:not([data-metrika="recomm"]),.o-grid__item', />Реклама<\//i);
        scriptLander(() => {
            let _d2 = void 0;
            Object.defineProperty(win, 'd2', {
                get: () => _d2,
                set: o => {
                    _d2 = new Proxy(o, {
                        set: (tgt, prop, val) => {
                            if (['brandingRender', 'dvReveal', '__dv'].includes(prop))
                                val = () => null;
                            tgt[prop] = val;
                        }
                    });
                }
            });
        });
    };

    scripts['fishki.net'] = () => {
        scriptLander(() => {
            let nt = new nullTools();
            let fishki = {};
            nt.define(fishki, 'adv', nt.proxy({
                afterAdblockCheck: nt.func(null),
                refreshFloat: nt.func(null)
            }));
            nt.define(fishki, 'is_adblock', false);
            nt.define(win, 'fishki', fishki);
        }, nullTools);
        gardener('.drag_list > .drag_element, .list-view > .paddingtop15, .post-wrap', /543769|Новости\sпартнеров|Полезная\sреклама/);
    };

    scripts['gidonline.club'] = () => createStyle('.tray > div[style] {display: none!important}');

    scripts['hdgo.cc'] = {
        other: ['46.30.43.38', 'couber.be'],
        now: () => (new MutationObserver(
            (ms) => {
                let m, node;
                for (m of ms) for (node of m.addedNodes)
                    if (node.tagName instanceof HTMLScriptElement && _getAttribute.call(node, 'onerror') !== null)
                        node.removeAttribute('onerror');
            }
        )).observe(document.documentElement, { childList:true, subtree: true })
    };

    scripts['gismeteo.ru'] = {
        other: ['gismeteo.ua'],
        now: () => gardener('div > script', /AdvManager/i, { observe: true, parent: 'div' })
    };

    scripts['hdrezka.ag'] = () => {
        Object.defineProperty(win, 'ab', { value: false, enumerable: true });
        gardener('div[id][onclick][onmouseup][onmousedown]', /onmouseout/i);
    };

    scripts['hideip.me'] = {
        now: () => scriptLander(() => {
            let _innerHTML = Object.getOwnPropertyDescriptor(_Element.prototype, 'innerHTML');
            let _set_innerHTML = _innerHTML.set;
            let _innerText = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerText');
            let _get_innerText = _innerText.get;
            let div = document.createElement('div');
            _innerHTML.set = function(...args) {
                _set_innerHTML.call(div, args[0].replace('i','a'));
                if (args[0] && /[рp][еe]кл/.test(_get_innerText.call(div))||
                    /(\d\d\d?\.){3}\d\d\d?:\d/.test(_get_innerText.call(this)) ) {
                    console.log('Anti-Adblock killed.');
                    return true;
                }
                _set_innerHTML.apply(this, args);
            };
            Object.defineProperty(_Element.prototype, 'innerHTML', _innerHTML);
            Object.defineProperty(win, 'adblock', {
                get: () => false,
                set: () => null,
                enumerable: true
            });
            let _$ = {};
            let _$_map = new WeakMap();
            let _gOPD = Object.getOwnPropertyDescriptor(Object, 'getOwnPropertyDescriptor');
            let _val_gOPD = _gOPD.value;
            _gOPD.value = function(...args) {
                let _res = _val_gOPD.apply(this, args);
                if (args[0] instanceof Window && (args[1] === '$' || args[1] === 'jQuery')) {
                    delete _res.get;
                    delete _res.set;
                    _res.value = win[args[1]];
                }
                return _res;
            };
            Object.defineProperty(Object, 'getOwnPropertyDescriptor', _gOPD);
            let getJQWrap = (n) => {
                let name = n;
                return {
                    enumerable: true,
                    get: () => _$[name],
                    set: x => {
                        if (_$_map.has(x)) {
                            _$[name] = _$_map.get(x);
                            return true;
                        }
                        if (x === _$.$ || x === _$.jQuery) {
                            _$[name] = x;
                            return true;
                        }
                        _$[name] = new Proxy(x, {
                            apply: (t, o, args) => {
                                let _res = t.apply(o, args);
                                if (_$_map.has(_res.is))
                                    _res.is = _$_map.get(_res.is);
                                else {
                                    let _is = _res.is;
                                    _res.is = function(...args) {
                                        if (args[0] === ':hidden')
                                            return false;
                                        return _is.apply(this, args);
                                    };
                                    _$_map.set(_is, _res.is);
                                }
                                return _res;
                            }
                        });
                        _$_map.set(x, _$[name]);
                        return true;
                    }
                };
            };
            Object.defineProperty(win, '$', getJQWrap('$'));
            Object.defineProperty(win, 'jQuery', getJQWrap('jQuery'));
            let _dP = Object.defineProperty;
            Object.defineProperty = function(...args) {
                if (args[0] instanceof Window && (args[1] === '$' || args[1] === 'jQuery'))
                    return void 0;
                return _dP.apply(this, args);
            };
        })
    };

    scripts['igra-prestoloff.cx'] = () => scriptLander(() => {
        let nt = new nullTools();
        /*jslint evil: true */ // yes, evil, I know
        let _write = document.write.bind(document);
        /*jslint evil: false */
        nt.define(document, 'write', t => {
            let id = t.match(/jwplayer\("(\w+)"\)/i);
            if (id && id[1])
                return _write(`<div id="${id[1]}"></div>${t}`);
            return _write('');
        });
    });

    scripts['imageban.ru'] = {
        now: () => {
            Object.defineProperty(win, 'V7x1J', { get: () => null });
            //preventPopunders();
        }
    };

    scripts['kinopoisk.ru'] = {
        now: () => {
            // set no-branding body style
            createStyle('body:not(#id) { background: #d5d5d5 url(/images/noBrandBg.jpg) 50% 0 no-repeat !important }');
        },
        dom: () => {
            (style => style ? style.parentNode.removeChild(style) : console.log('Unable to locate branding style.')
            )(_de.querySelector('#branding-style'));
        }
    };

    scripts['korrespondent.net'] = {
        now: () => scriptLander(() => {
            let nt = new nullTools();
            nt.define(win, 'holder', function(id) {
                let div = document.getElementById(id);
                if (!div)
                    return;
                if (div.parentNode.classList.contains('col__sidebar')) {
                    div.parentNode.appendChild(div);
                    div.style.height = '300px';
                }
            });
        }, nullTools),
        dom: () => {
            for (let frame of document.querySelectorAll('.unit-side-informer > iframe'))
                frame.parentNode.style.width = '1px';
        }
    };

    scripts['mail.ru'] = () => scriptLander(() => {
        let nt = new nullTools();
        // Trick to prevent mail.ru from removing 3rd-party styles
        nt.define(Object.prototype, 'restoreVisibility', nt.func(null), false);
        // Disable some of their counters
        nt.define(win, 'rb_counter', nt.func(null, 'rb_counter'));
        if (location.hostname !== 'e.mail.ru')
            nt.define(win, 'createRadar', nt.func(nt.func(null, 'aRadar'), 'createRadar'));
        else
            nt.define(win, 'aRadar', nt.func(null, 'aRadar'));

        // Disable page scrambler on mail.ru to let extensions easily block ads there
        function defineLocator(root) {
            let _locator;
            let fishnet = {
                apply: (target, thisArg, args) => {
                    console.log(`locator.${target._name}(${JSON.stringify(args).slice(1,-1)})`);
                    return target.apply(thisArg, args);
                }
            };

            function wrapLocator(locator) {
                if ('setup' in locator) {
                    let _setup = locator.setup;
                    locator.setup = function(o) {
                        if ('enable' in o) {
                            o.enable = false;
                            console.log('Disable mimic mode.');
                        }
                        if ('links' in o) {
                            o.links = [];
                            console.log('Call with empty list of sheets.');
                        }
                        return _setup.call(this, o);
                    };
                    locator.insertSheet = () => console.log('Ignore insertSheet.');
                    locator.wrap = () => console.log('Ignore wrap.');
                }
                try {
                    let names = [];
                    for (let name in locator)
                        if (locator[name] instanceof Function) {
                            locator[name]._name = name;
                            locator[name] = new Proxy(locator[name], fishnet);
                            names.push(name);
                        }
                    console.log(`[locator] wrapped properties: ${names.join(', ')}`);
                } catch(e) {
                    console.log(e);
                }
                _locator = locator;
            }

            if ('locator' in root && root.locator) {
                console.log('Found existing "locator" object. :|');
                _locator = root.locator;
                wrapLocator(root.locator);
            }

            let loc_desc = Object.getOwnPropertyDescriptor(root, 'locator');
            if (!loc_desc || loc_desc.set !== wrapLocator)
                try {
                    Object.defineProperty(root, 'locator', {
                        set: wrapLocator,
                        get: () => _locator
                    });
                } catch (err) {
                    console.log('Unable to redefine "locator" object!!!', err);
                }
        }

        function defineDetector(mr) {
            let __ = mr._ || {};

            if ('HONEYPOT' in __) {
                console.log('Disarming existing detector instance. :|', JSON.stringify(__));
                nt.define(__, 'HONEYPOT', '.honeypot_fake_class_to_miss');
                nt.define(__, 'STUCK_IN_POT', false);
            }

            __ = new Proxy(__, {
                get: (t, p) => t[p],
                set: (t, p, v) => {
                    console.log(`mr._.${p} =`, v);
                    if (['HONEYPOT', 'STUCK_IN_POT'].includes(p))
                        console.log('Not changed.');
                    t[p] = v; // setter in nt.define will prevent this when needed
                    return true;
                }
            });
            Object.defineProperty(mr, '_', {
                enumerable: true,
                value: __
            });
        }

        if (location.hostname === 'e.mail.ru')
            defineLocator(win);
        else
            try {
                let _mr;
                Object.defineProperty(win, 'mr', {
                    enumerable: true,
                    get: () => _mr,
                    set: (v) => {
                        if (v === _mr)
                            return true;
                        console.log('Trapped new "mr" object.');
                        defineLocator(v.mimic ? v.mimic : v);
                        defineDetector(v);
                        _mr = v;
                    }
                });
                if (!('mr' in win))
                    throw 'Wat!?';
            } catch (e) {
                console.log('Found existing "mr" object.', e instanceof TypeError ? '' : e);
                defineLocator(win.mr);
                defineDetector(win.mr);
            }
    }, nullTools);

    scripts['megogo.net'] = {
        now: () => {
            let nt = new nullTools();
            nt.define(win, 'adBlock', false);
            nt.define(win, 'showAdBlockMessage', nt.func(null));
        }
    };

    scripts['naruto-base.su'] = () => gardener('div[id^="entryID"],.block', /href="http.*?target="_blank"/i);

    scripts['overclockers.ru'] = {
        now: () => scriptLander(() => {
            let _innerHTML = Object.getOwnPropertyDescriptor(_Element.prototype, 'innerHTML');
            let _set_innerHTML = _innerHTML.set;
            _innerHTML.set = function() {
                if (this === document.body) {
                    console.log('Anti-Adblock killed.');
                    return true;
                }
                _set_innerHTML.apply(this, arguments);
            };
            Object.defineProperty(_Element.prototype, 'innerHTML', _innerHTML);
        })
    };
    scripts['forums.overclockers.ru'] = {
        now: () => {
            createStyle('.needblock {position: fixed; left: -10000px}');
            Object.defineProperty(win, 'adblck', {
                get: () => 'no',
                set: () => undefined,
                enumerable: true
            });
        }
    };

    scripts['pb.wtf'] = {
        other: ['piratbit.org', 'piratbit.ru'],
        now: () => {
            // line above topic content and images in the slider in the header
            gardener(
                'a[href^="/exit/"], a[href^="/fxt/"], a[href$="=="]',
                /img|Реклама|center/i,
                { root: '.release-navbar,#page_content', observe: true, parent: 'div,tr' }
            );
            // ads in comments
            gardener('img[data-name="PiraBo"]', /./i, {root:'#main_content .table', observe:true, parent:'tr'});
        }
    };

    scripts['pikabu.ru'] = () => gardener('.story', /story__author[^>]+>ads</i, {root: '.inner_wrap', observe: true});

    scripts['peka2.tv'] = () => {
        let bodyClass = 'body--branding';
        let checkNode = node => {
            for (let className of node.classList)
                if (className.includes('banner') || className === bodyClass) {
                    _removeAttribute.call(node, 'style');
                    node.classList.remove(className);
                    for (let attr of Array.from(node.attributes))
                        if (attr.name.startsWith('advert'))
                            _removeAttribute.call(node, attr.name);
                }
        };
        (new MutationObserver(ms => {
            let m, node;
            for (m of ms) for (node of m.addedNodes)
                if (node instanceof HTMLElement)
                    checkNode(node);
        })).observe(_de, {childList: true, subtree: true});
        (new MutationObserver(ms => {
            for (let m of ms)
                checkNode(m.target);
        })).observe(_de, {attributes: true, subtree: true, attributeFilter: ['class']});
    };

    scripts['qrz.ru'] = {
        now: () => {
            let nt = new nullTools();
            nt.define(win, 'ab', false);
            nt.define(win, 'tryMessage', nt.func(null));
        }
    };

    scripts['razlozhi.ru'] = {
        now: () => {
            for (let func of ['createShadowRoot', 'attachShadow'])
                if (func in _Element.prototype)
                    _Element.prototype[func] = function(){
                        return this.cloneNode();
                    };
        }
    };

    scripts['rbc.ru'] = {
        dom: () => {
            let _preventDefault = Event.prototype.preventDefault;
            Event.prototype.preventDefault = function preventDefault() {
                let t = this.target;
                if (t instanceof HTMLAnchorElement || t.closest('A'))
                    throw new Error('an.yandex redirect prevention');
                return _preventDefault.call(this);
            };

            function cleaner(nodes) {
                for (let node of nodes) {
                    if (!node.classList || !node.classList.contains('js-yandex-counter'))
                        continue;
                    node.classList.remove('js-yandex-counter');
                    node.removeAttribute('data-yandex-name');
                    node.removeAttribute('data-yandex-params');
                }
            }
            cleaner(_de.querySelectorAll('.js-yandex-counter'));

            (new MutationObserver(
                ms => {
                    for (let m of ms) cleaner(m.addedNodes);
                }
            )).observe(_de, {childList: true, subtree: true});
        }
    };

    scripts['rp5.ru'] = {
        other: ['rp5.by', 'rp5.kz', 'rp5.ua'],
        now: () => gardener('div[id][class]', /\?AdvertMgmt=|adsbygoogle/, { root: '#content-wrapper', log: true })
    };

    scripts['rutube.ru'] = () => scriptLander(() => {
        let _parse = JSON.parse.bind(JSON);
        let _skip_enabled = false;
        JSON.parse = (...args) => {
            let res = _parse(...args),
                log = false;
            if (!res)
                return res;
            // parse player configuration
            if ('appearance' in res || 'video_balancer' in res) {
                log = true;
                if ('appearance' in res) {
                    res.appearance.forbid_seek = false;
                    res.appearance.forbid_timeline_preview = false;
                }
                _skip_enabled = !!res.remove_unseekable_blocks;
                res.advert = [];
                for (let limit of res.limits)
                    limit.limit = 0;
                res.yast = null;
                res.yast_live_online = null;
                Object.defineProperty(res, 'stat', {
                    get: () => [],
                    set: () => true,
                    enumerable: true
                });
            }

            // parse video configuration
            if ('video_url' in res) {
                log = true;
                if ('cuepoints' in res && !_skip_enabled)
                    for (let point of res.cuepoints) {
                        point.is_pause = false;
                        point.show_navigation = true;
                        point.forbid_seek = false;
                    }
            }

            if (log)
                console.log('[rutube]', res);
            return res;
        };
    });

    scripts['simpsonsua.com.ua'] = () => scriptLander(() => {
        let _addEventListener = Object.getPrototypeOf(HTMLDocument).prototype.addEventListener;
        document.addEventListener = function(event, callback) {
            if (event === 'DOMContentLoaded' && callback.toString().includes('show_warning'))
                return;
            return _addEventListener.apply(this, arguments);
        };
    });

    scripts['spaces.ru'] = () => {
        gardener('div:not(.f-c_fll) > a[href*="spaces.ru/?Cl="]', /./, { parent: 'div' });
        gardener('.js-banner_rotator', /./, { parent: '.widgets-group' });
    };

    scripts['spam-club.blogspot.co.uk'] = () => {
        let _clientHeight = Object.getOwnPropertyDescriptor(_Element.prototype, 'clientHeight'),
            _clientWidth = Object.getOwnPropertyDescriptor(_Element.prototype, 'clientWidth');
        let wrapGetter = (getter) => {
            let _getter = getter;
            return function() {
                let _size = _getter.apply(this, arguments);
                return _size ? _size : 1;
            };
        };
        _clientHeight.get = wrapGetter(_clientHeight.get);
        _clientWidth.get = wrapGetter(_clientWidth.get);
        Object.defineProperty(_Element.prototype, 'clientHeight', _clientHeight);
        Object.defineProperty(_Element.prototype, 'clientWidth', _clientWidth);
        let _onload = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'onload'),
            _set_onload = _onload.set;
        _onload.set = function() {
            if (this instanceof HTMLImageElement)
                return true;
            _set_onload.apply(this, arguments);
        };
        Object.defineProperty(HTMLElement.prototype, 'onload', _onload);
    };

    scripts['sport-express.ru'] = () => gardener('.js-relap__item',/>Реклама\s+<\//, {root:'.container', observe: true});

    scripts['sports.ru'] = {
        now: () => {
            gardener('.aside-news-list__item', /aside-news-list__advert/i, {root:'.columns-layout__left', observe: true});
            gardener('.material-list__item', /Реклама/i, {root:'.columns-layout', observe: true});
            // extra functionality: shows/hides panel at the top depending on scroll direction
            createStyle([
                '.user-panel__fixed { transition: top 0.2s ease-in-out!important; }',
                '.user-panel-up { top: -40px!important }'
            ], {id: 'userPanelSlide'}, false);
        },
        dom: () => {
            (function lookForPanel() {
                let panel = document.querySelector('.user-panel__fixed');
                if (!panel)
                    setTimeout(lookForPanel, 100);
                else
                    window.addEventListener(
                        'wheel', function(e) {
                            if (e.deltaY > 0 && !panel.classList.contains('user-panel-up'))
                                panel.classList.add('user-panel-up');
                            else if (e.deltaY < 0 && panel.classList.contains('user-panel-up'))
                                panel.classList.remove('user-panel-up');
                        }, false
                    );
            })();
        }
    };

    scripts['stealthz.ru'] = {
        dom: () => {
            // skip timeout
            let $ = document.querySelector.bind(document);
            let [timer_1, timer_2] = [$('#timer_1'), $('#timer_2')];
            if (!timer_1 || !timer_2)
                return;
            timer_1.style.display = 'none';
            timer_2.style.display = 'block';
        }
    };

    scripts['yap.ru'] = {
        other: ['yaplakal.com'],
        now: () => {
            gardener('form > table[id^="p_row_"]:nth-of-type(2)', /member1438|Administration/);
            gardener('.icon-comments', /member1438|Administration|\/go\/\?http/, {parent:'tr', siblings:-2});
        }
    };

    scripts['rambler.ru'] = {
        other: ['championat.com','gazeta.ru','media.eagleplatform.com','lenta.ru', 'quto.ru'],
        now: () => scriptLander(() => {
            // Prevent autoplay
            if (!('EaglePlayer' in win)) {
                let _EaglePlayer = void 0;
                Object.defineProperty(win, 'EaglePlayer', {
                    enumerable: true,
                    get: () => _EaglePlayer,
                    set: x => {
                        if (x === _EaglePlayer)
                            return true;
                        _EaglePlayer = x;
                        let _init = void 0;
                        Object.defineProperty(_EaglePlayer.prototype, 'init', {
                            enumerable: true,
                            set: x => void(_init = x),
                            get: function() {
                                let _self = this;
                                if (_self.options)
                                    Object.defineProperty(this.options, 'autoplay', {
                                        enumerable: true,
                                        get: () => false,
                                        set: () => null
                                    });
                                if (_self.options && _self.options.el && inIFrame) {
                                    let addListener = (player) => {
                                        console.log('Attached autostop');
                                        player.addEventListener('canplay', e => {
                                            e.target.play = () => console.log('Applied autostop.');
                                            setTimeout(player => {
                                                delete player.play;
                                                console.log('Detached autostop');
                                            }, 1500, e.target);
                                        }, false);
                                    };
                                    (new MutationObserver(ms => {
                                        let m, node;
                                        for (m of ms) for (node of m.addedNodes)
                                            if (node instanceof HTMLVideoElement)
                                                addListener(node);
                                    })).observe(this.options.el, { childList: true, subtree: true });
                                }
                                return _init;
                            }
                        });
                    }
                });
                let _setAttribute = _Element.prototype.setAttribute;
                let isAutoplay = /^autoplay$/i;
                _Element.prototype.setAttribute = function setAttribute(name) {
                    if (!this._stopped && isAutoplay.test(name)) {
                        console.log('Prevented assigning autoplay attribute.');
                        return null;
                    }
                    return _setAttribute.apply(this, arguments);
                };
            } else if (inIFrame) {
                let _setAttribute = _Element.prototype.setAttribute;
                let isAutoplay = /^autoplay$/i;
                _Element.prototype.setAttribute = function setAttribute(name) {
                    if (!this._stopped && isAutoplay.test(name)) {
                        console.log('Prevented assigning autoplay attribute.');
                        this._stopped = true;
                        this.play = () => {
                            console.log('Prevented attempt to force-start playback.');
                            delete this.play;
                        };
                        return null;
                    }
                    return _setAttribute.apply(this, arguments);
                };
            }
            if (location.hostname.endsWith('.media.eagleplatform.com'))
                return;
            let CSSRuleProto = 'cssText' in CSSRule.prototype ? CSSRule.prototype : CSSStyleRule.prototype;
            let _cssText = Object.getOwnPropertyDescriptor(CSSRuleProto, 'cssText');
            let _cssText_get = _cssText.get;
            _cssText.configurable = false;
            _cssText.get = function() {
                let cssText = _cssText_get.call(this);
                if (cssText.includes('content:')) {
                    console.log('Blocked access to suspicious cssText:', cssText.slice(0,60), '\u2026', cssText.length);
                    return null;
                }
                return cssText;
            };
            Object.defineProperty(CSSRuleProto, 'cssText', _cssText);
            // fake global Adf object
            let nt = new nullTools();
            let Adf_banner = {};
            [
                'reloadssp', 'sspScroll',
                'sspRich', 'ssp'
            ].forEach(name => void(Adf_banner[name] = nt.proxy(() => new Promise(r => r({status: true})))));
            nt.define(win, 'Adf', nt.proxy({
                banner: nt.proxy(Adf_banner)
            }));
            // extra script to remove partner news on gazeta.ru
            if (!location.hostname.includes('gazeta.ru'))
                return;
            (new MutationObserver(
                (ms) => {
                    let m, node, header;
                    for (m of ms) for (node of m.addedNodes)
                        if (node instanceof HTMLDivElement && node.matches('.sausage')) {
                            header = node.querySelector('.sausage-header');
                            if (header && /новости\s+партн[её]ров/i.test(header.textContent))
                                node.style.display = 'none';
                        }
                }
            )).observe(document.documentElement, { childList:true, subtree: true });
        }, `let inIFrame = ${inIFrame}`, nullTools)
    };

    scripts['reactor.cc'] = {
        other: ['joyreactor.cc', 'pornreactor.cc'],
        now: () => {
            selectiveEval();
            scriptLander(() => {
                let nt = new nullTools();
                win.open = function(){
                    throw new Error('Redirect prevention.');
                };
                nt.define(win, 'Worker', function(){});
                nt.define(win, 'JRCH', win.CoinHive);
            }, nullTools);
        },
        click: function(e) {
            let node = e.target;
            if (node.nodeType === _Node.ELEMENT_NODE &&
                node.style.position === 'absolute' &&
                node.style.zIndex > 0)
                node.parentNode.removeChild(node);
        },
        dom: function() {
            let words = new RegExp(
                'блокировщик рекламы'
                .split('')
                .map(function(e){
                    return e+'[\u200b\u200c\u200d]*';
                })
                .join('')
                .replace(' ', '\\s*')
                .replace(/[аоре]/g, function(e){
                    return ['[аa]','[оo]','[рp]','[еe]']['аоре'.indexOf(e)];
                }),
                'i'),
                can;
            function deeper(spider) {
                for (let child of spider.childNodes)
                    if (words.test(child.innerText))
                        if (child.offsetHeight >= 750)
                            deeper(child);
                        else
                            can.push(child);
            }
            function probe() {
                can = [];
                deeper(document.body);
                for (let spider of can)
                    _setAttribute.call(spider, 'style', 'background:none!important');
            }
            (new MutationObserver(probe))
                .observe(document, { childList:true, subtree:true });
        }
    };

    scripts['auto.ru'] = () => {
        let words = /Реклама|Яндекс.Директ|yandex_ad_/;
        let userAdsListAds = (
            '.listing-list > .listing-item,'+
            '.listing-item_type_fixed.listing-item'
        );
        let catalogAds = (
            'div[class*="layout_catalog-inline"],'+
            'div[class$="layout_horizontal"]'
        );
        let otherAds = (
            '.advt_auto,'+
            '.sidebar-block,'+
            '.pager-listing + div[class],'+
            '.card > div[class][style],'+
            '.sidebar > div[class],'+
            '.main-page__section + div[class],'+
            '.listing > tbody'
        );
        gardener(userAdsListAds, words, {root:'.listing-wrap', observe:true});
        gardener(catalogAds, words, {root:'.catalog__page,.content__wrapper', observe:true});
        gardener(otherAds, words);
    };

    scripts['rsload.net'] = {
        load: () => {
            let dis = document.querySelector('label[class*="cb-disable"]');
            if (dis)
                dis.click();
        },
        click: e => {
            let t = e.target;
            if (t && t.href && (/:\/\/\d+\.\d+\.\d+\.\d+\//.test(t.href)))
                t.href = t.href.replace('://','://rsload.net:rsload.net@');
        }
    };

    let domain;
    // add alternative domain names if present and wrap functions into objects
    for (let name in scripts) {
        if (scripts[name] instanceof Function)
            scripts[name] = { now: scripts[name] };
        for (domain of (scripts[name].other||[])) {
            if (domain in scripts)
                console.log('Error in scripts list. Script for', name, 'replaced script for', domain);
            scripts[domain] = scripts[name];
        }
        delete scripts[name].other;
    }
    // look for current domain in the list and run appropriate code
    domain = document.domain;
    while (domain.indexOf('.') > -1) {
        if (domain in scripts) for (let when in scripts[domain])
            switch(when) {
                case 'now':
                    scripts[domain][when]();
                    break;
                case 'dom':
                    document.addEventListener('DOMContentLoaded', scripts[domain][when], false);
                    break;
                default:
                    document.addEventListener (when, scripts[domain][when], false);
            }
        domain = domain.slice(domain.indexOf('.') + 1);
    }

    // Batch script lander
    if (!skipLander)
        landScript(batchLand, batchPrepend);

    { // JS Fixes Tools Menu
        let openOptions = function() {
            let ovl = _createElement('div'),
                inner = _createElement('div');
            ovl.style = (
                'position: fixed;'+
                'top:0; left:0;'+
                'bottom: 0; right: 0;'+
                'background: rgba(0,0,0,0.85);'+
                'z-index: 2147483647;'+
                'padding: 5em'
            );
            inner.style = (
                'background: whitesmoke;'+
                'font-size: 10pt;'+
                'color: black;'+
                'padding: 1em'
            );
            inner.textContent = 'JS Fixes Tools';
            inner.appendChild(_createElement('br'));
            inner.appendChild(_createElement('br'));
            ovl.addEventListener(
                'click', function(e) {
                    if (e.target === ovl) {
                        ovl.parentNode.removeChild(ovl);
                        e.preventDefault();
                    }
                    e.stopPropagation();
                }, false
            );

            let sObjBtn = _createElement('button');
            sObjBtn.onclick = getStrangeObjectsList;
            sObjBtn.textContent = 'Print (in console) list of unusual window properties';
            inner.appendChild(_createElement('br'));
            inner.appendChild(sObjBtn);

            document.body.appendChild(ovl);
            ovl.appendChild(inner);
        };

        // monitor keys pressed for Ctrl+Alt+Shift+J > s > f code
        let opPos = 0, opKey = ['KeyJ','KeyS','KeyF'];
        document.addEventListener(
            'keydown', function(e) {
                if ((e.code === opKey[opPos] || e.location) &&
                    (!!opPos || e.altKey && e.ctrlKey && e.shiftKey)) {
                    opPos += e.location ? 0 : 1;
                    e.stopPropagation();
                    e.preventDefault();
                } else
                    opPos = 0;
                if (opPos === opKey.length) {
                    opPos = 0;
                    openOptions();
                }
            }, false
        );
    }
})();