RU AdList JS Fixes

try to take over the world!

Устаревшая версия за 05.02.2020. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         RU AdList JS Fixes
// @namespace    ruadlist_js_fixes
// @version      20200205.1
// @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/*
// @exclude      *://trust.yandex.ru/*
// @exclude      *://diehard.yandex.ru/*
// @exclude      *://beru.ru/*
// @exclude      *://*.ingress.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_listValues
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @grant        window.close
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const 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.
    const _Document = Object.getPrototypeOf(HTMLDocument.prototype);
    const _Element = Object.getPrototypeOf(HTMLElement.prototype);
    // dTree 2.05 in some cases replaces Node object
    const _Node = Object.getPrototypeOf(_Element);

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

    let skipLander = true;
    try {
        skipLander = !(isFirefox && ('StopIteration' in win || GM.info.scriptHandler === 'Greasemonkey'));
    } catch(ignore){}

    const _console = {};
    (_console.initConsole = () => {
        for (let name in win.console) _console[name] = console[name];
        _console._trace = _console.trace;
        _console.trace = (...args) => {
            if (!skipLander)
                return _console.warn(...args);
            _console.groupCollapsed(...args);
            _console._trace('Stack trace.');
            _console.groupEnd();
        };
        Object.freeze(_console);
        Object.defineProperty(win.console, 'clear', { value: () => null });
    })();

    const jsf = (function () {
        const opts = {};
        let getValue = (a, b) => b, setValue = () => null, listValues = () => [];
        try {
            [getValue, setValue, listValues] = [GM_getValue, GM_setValue, GM_listValues];
        } catch (ignore) {};
        // defaults
        opts.Lang = 'eng';
        opts.CoinHiveStub = true;
        opts.AbortExecutionStatistics = false;
        opts.AccessStatistics = false;
        // load actual values
        for (let name of listValues())
            opts[name] = getValue(name, opts[name]);
        const checkName = name => {
            if (!opts.hasOwnProperty(name))
                throw new Error('Attempt to access missing option value.');
            return true;
        };
        return new Proxy(opts, {
            get: (opts, name) => {
                if (name === 'toString')
                    return () => JSON.stringify(opts);
                if (checkName(name))
                    return opts[name];
            },
            set: (opts, name, value) => {
                if (checkName(name)) {
                    opts[name] = value;
                    setValue(name, value);
                }
                return true;
            }
        });
    })();

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

    // NodeList and HTMLCollection iterator polyfill
    // required for old versions of Safari and Chrome 49 (last available for WinXP)
    // https://jakearchibald.com/2014/iterators-gonna-iterate/
    if (!NodeList.prototype[Symbol.iterator])
        NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    if (!HTMLCollection.prototype[Symbol.iterator])
        HTMLCollection.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
    const batchLand = [];
    const batchPrepend = new Set();
    const _APIString = skipLander ? '' : `const win = window, _console = {}, isFirefox = ${isFirefox}, inIFrame = ${inIFrame},` +
          `_document = win.document, _Document = Object.getPrototypeOf(HTMLDocument.prototype), _Element = Object.getPrototypeOf(HTMLElement.prototype),` +
          `_Node = Object.getPrototypeOf(_Element), jsf = ${jsf.toString()}, _de = _document.documentElement, _appendChild = _Document.appendChild.bind(_de),` +
          `_removeChild = _Document.removeChild.bind(_de), _createElement = _Document.createElement.bind(_document), skipLander = ${skipLander};` +
          `(${_console.initConsole.toString()})();`
    const landScript = (f, pre) => {
        let script = _createElement('script');
        script.textContent = `(()=>{${_APIString}${[...pre].join(';')};(${f.join(')();(')})();})();`;
        _appendChild(script);
        _removeChild(script);
    };
    let scriptLander = f => f();
    if (!skipLander) {
        scriptLander = (func, ...prepend) => {
            prepend.forEach(x => batchPrepend.add(x));
            batchLand.push(func);
        };
        _document.addEventListener(
            'DOMContentLoaded', () => void (scriptLander = (f, ...prep) => landScript([f], prep)), false
        );
    }

    function nullTools(opts) {
        const nt = this;
        opts = opts || {};

        function Stats(){
            const me = this,
                  updated = new Map(),
                  logged = new Map();

            function printUpdated() {
                const prepared = [...updated].map(x => {
                    const [prop, dir] = x;
                    logged.set(prop, logged.get(prop) | dir);
                    return `${prop} (${dir ^ (me.GET | me.SET) ? (dir & me.GET ? 'R' : 'W') : 'R/W'})`
                }).sort();
                updated.clear();
                _console.log(`Accessed properties:\n * ${prepared.join('\n * ')}`);
            };

            let logLock;
            me.set = async (prop, dir) => {
                !(logged.get(prop) & dir) && updated.set(prop, updated.get(prop) | dir);
                logLock = (logLock > 0 || !updated.size) ? logLock : setInterval(() => {
                    printUpdated();
                    logLock = clearInterval(logLock);
                }, 2500);
            };
        };
        Stats.prototype.GET = 1;
        Stats.prototype.SET = 2;
        const stats = new Stats();

        const log = async (...args) => opts.log && _console.log(...args);
        const objectAlike = x => (x !== null) && typeof x === 'object' || typeof x === 'function';
        const parsePath = path => {
            let root = win;
            let chain = path.split('.');
            let link = chain.shift();
            for (; chain.length > 0; link = chain.shift()) {
                if (!objectAlike(root[link]))
                    break;
                root = root[link];
            }
            return [root, link, chain];
        }

        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(path, val, other = {}) {
            other.enumerable = other.enumerable || false;
            const [obj, prop, remainder] = parsePath(path);
            if (remainder.length) {
                _console.warn(`Unable to resolve ${remainder.join('.')} in ${path}`);
                return;
            }
            nt.defineOn(obj, prop, val, path, other);
        };

        nt.defineOn = function(obj, prop, val, path, other = {}) {
            const {enumerable =  true, trace: force_log = false} = other;
            path = path || ((obj === win) ? prop : `?.${prop}`);
            if (path[path.length-1] === '.')
                path = `${path}${prop}`;
            const desc = Object.getOwnPropertyDescriptor(obj, prop);
            if (desc !== undefined && !desc.configurable) {
                _console.warn(`Unable to redefine not configurable ${prop} in`, obj);
                return;
            }
            Object.defineProperty(
                obj, prop, {
                    get () {
                        (force_log || jsf.AccessStatistics || opts.trace) && stats.set(path, stats.GET);
                        return val;
                    },
                    set (v) {
                        (force_log || jsf.AccessStatistics || opts.trace) && stats.set(path, stats.SET);
                        if (v !== val) {
                            log(`set ${prop} of`, obj, 'to', v);
                            nt.destroy(v);
                        }
                    },
                    enumerable: enumerable
                }
            );
        };

        nt.proxy = function(obj, missingFuncParentName, missingFuncValue) {
            return new Proxy(
                obj, {
                    get: (t, p) => {
                        if (p in t)
                            return t[p];
                        if (typeof p === 'symbol') {
                            if (p === Symbol.toPrimitive)
                                t[p] = function(hint) {
                                    if (hint === 'string')
                                        return Object.prototype.toString.call(this);
                                    return `[missing toPrimitive] ${name} ${hint}`;
                                };
                            else if (p === Symbol.toStringTag)
                                t[p] = () => 'Object'
                            else {
                                t[p] = undefined;
                                _console.trace('Missing', p, 'in', missingFuncParentName || '?', '>>', t[p]);
                            }
                            return t[p];
                        }
                        if (missingFuncParentName) {
                            t[p] = nt.func(missingFuncValue, `${missingFuncParentName}.${p}`);
                            return t[p];
                        }
                        _console.trace('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) => nt.proxy(function() {
            (force_log || (opts.log || opts.trace)) && _console.trace(`call ${name}(`, ...arguments, `) return`, val);
            return val;
        });
    }
    nullTools.toString = new Proxy(nullTools.toString, {
        apply: (...args) => Reflect.apply(...args) + ' let nt = new nullTools();'
    });
    let nt = new nullTools();

    // 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;
    }

    // aborts currently running script with ReferenceError on specific property access
    // in case of inline script also checks if it contains specific pattern in it

    function abortExecutionModule() {
        let map = new Map(),
            cnt = new Map(),
            stack = 0;
        const printCounters = (cnts) => {
            let s = '';
            for (let cntr of cnts)
                s += `\n * ${cntr[0]}: ${cntr[1]}`;
            return s;
        };
        const logger = id => {
            if (!jsf.AbortExecutionStatistics)
                return;
            let { path, mode } = map.get(id);
            let prop = `${path} ${mode.toString().replace('Symbol','')}`;
            cnt.set(prop, (cnt.get(prop) || 0) + 1);
            stack++;
            setTimeout(() => {
                stack--;
                if (!stack)
                    console.log('Abort execution counters:', printCounters(Array.from(cnt.entries())));
            }, 1000);
        };
        const objectAlike = x => (x !== null) && typeof x === 'object' || typeof x === 'function';
        const onAccess = {
            Get: Symbol('Read'),
            Set: Symbol('Write'),
            All: Symbol('Access'),
            InlineScript: Symbol('InlineScript')
        };
        Object.freeze(onAccess);

        return [onAccess, function abortExecution(mode, path, conf = {}) {
            let root = conf.root || win;
            let chain = path.split('.');
            let postponed = false;
            const postpone = (link, chain) => {
                postponed = true;
                let _val = undefined;
                try {
                    Object.defineProperty(root, link, {
                        get () { return _val; },
                        set (val) {
                            _val = val;
                            conf.root = val;
                            conf.fullPath = conf.fullPath || path;
                            abortExecution(mode, chain.join('.'), conf);
                        }
                    });
                } catch (e) {
                    _console.warn(`Unable to set postpone point at ${link} in ${path}\n`, e);
                }
            };
            while (chain.length > 1) {
                let link = chain.shift();
                if (!objectAlike(root[link])) {
                    if (conf.breakOnMissing)
                        break;
                    postpone(link, chain);
                    break;
                }
                root = root[link];
            }
            path = conf.fullPath || path;
            if (postponed) {
                if (conf.breakOnMissing)
                    _console.log(`Unable to locate "${path}", abort anchor skipped.`);
                return;
            }
            const target = chain[0];
            const message = `${target} is not defined`
            const des = Object.getOwnPropertyDescriptor(root, target);
            if (des && des.get !== undefined) return;
            const id = Math.random().toString(36).substr(2);
            map.set(id, { path: path, mode: mode });
            win.addEventListener('error', e => {
                if (e.error && e.error.message === message)
                    e.stopImmediatePropagation();
            }, false);
            const get = Symbol('get');
            const set = Symbol('set');

            const removeOwnFootprint = e => (e.stack = e.stack.split('\n').filter(x => !x.includes('-extension://')).join('\n'), e);
            const check = (mode === onAccess.InlineScript) ?
                  () => {
                      const script = _document.currentScript;
                      if (script && script.src === '' &&
                          (!conf.pattern || conf.pattern.test(script.textContent))) {
                          logger(id);
                          throw removeOwnFootprint(new ReferenceError(message));
                      }
                  } : io => {
                      if (io === set && mode === onAccess.Get ||
                          io === get && mode === onAccess.Set)
                          return;
                      logger(id);
                      throw removeOwnFootprint(new ReferenceError(message));
                  };

            let _val = root[target];
            Object.defineProperty(root, target, {
                get () {
                    check(get);
                    return _val;
                },
                set (v) {
                    check(set);
                    _val = v;
                }
            });
        }]
    };
    abortExecutionModule.toString = function() {
        return Function.prototype.toString.call(this) +
            ' const [onAccess, abortExecution] = abortExecutionModule();';
    };
    const [onAccess, abortExecution] = abortExecutionModule();

    // Fake objects of advertisement networks to break their workflow
    // Popular adblock detector
    function deployFABStub(root) {
        if (!('fuckAdBlock' in root)) {
            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);
                };
                root.addEventListener('load', callback, false);
            };
            nt.defineOn(root, 'FuckAdBlock', FuckAdBlock);
            nt.defineOn(root, 'fuckAdBlock', new FuckAdBlock({
                checkOnLoad: true,
                resetOnEnd: true
            }));
        }
    }
    // new version of fAB adapting to fake API
    // scriptLander(() => deployFABStub(win), nullTools); // so it's disabled by default for now

    scriptLander(() => {
        // CoinHive miner stub. Continuous 100% CPU load can easily kill some CPU with overheat.
        if (!('CoinHive' in win) && jsf.CoinHiveStub)
            if (location.hostname !== 'cnhv.co') {
                // CoinHive stub for cases when it doesn't affect site functionality
                const CoinHiveConstructor = function() {
                    _console.log('Fake CoinHive miner created.');
                    this.setThrottle = nt.func(null, 'CoinHive.setThrottle');
                    this.start = nt.func(null, 'CoinHive.start');
                    this.on = nt.func(null, 'CoinHive.on');
                    this.getHashesPerSecond = nt.func(Infinity, 'CoinHive.getHashesPerSecond');
                    this.getTotalHashes = nt.func(Infinity, 'CoinHive.getTotalHashes');
                    this.getAcceptedHashes = nt.func(Infinity, 'CoinHive.getAcceptedHashes');
                };
                nt.define('CoinHive', nt.proxy({
                    Anonymous: CoinHiveConstructor,
                    User: CoinHiveConstructor,
                    Token: CoinHiveConstructor,
                    JobThread: nt.func(null, 'CoinHive.JobThread'),
                    Res: nt.func(null, 'CoinHive.Res'),
                    IF_EXCLUSIVE_TAB: false,
                    CONFIG: nt.proxy({})
                }));
            } 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 || undefined;
            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 = Reflect.apply(tgt, ths, args);
                            if (res) {
                                if (params && params.autoplay && res.on && res.off) {
                                    let autoplayHandler = () => {
                                        setTimeout(() => {
                                            let button = _querySelector('.vjs-play-control.vjs-playing');
                                            if (button)
                                                button.click();
                                        }, 0);
                                        _console.log('videojs autoplay prevention');
                                        res.off('playing', autoplayHandler);
                                    };
                                    res.on('playing', autoplayHandler);
                                }
                                if (res.seed)
                                    res.seed = () => null;
                            }
                            _console.log('player =', res);
                            return res;
                        }
                    });
                }
            });
        }

        // Set a little trap for BodyClick ads
        abortExecution(onAccess.Set, '__BC_domain');

        // Yandex API (ADBTools, Metrika)
        let hostname = location.hostname;
        if (// Thank you, Greasemonkey, now I have to check for this. -_-
            location.protocol === 'about:' ||
            // Google likes to define odd global variables like Ya
            hostname.startsWith('google.') || hostname.includes('.google.') ||
            // 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.startsWith('yandex.') || hostname.includes('.yandex.')) &&
             /^\/((yand)?search|images)/i.test(location.pathname) && !hostname.startsWith('news.')) ||
            // Also skip on these following sites since they use
            // code minification which generated global Ya variable.
            hostname.endsWith('chatango.com') || hostname.endsWith('github.io') ||
            hostname.endsWith('grimtools.com') || hostname.endsWith('poeplanner.com'))
            return;

        let YaProps = new Set();
        function setObfuscatedProperty(Ya, rootProp, obj, name) {
            if (YaProps.has(rootProp))
                return;
            _console.trace(`Ya.${rootProp} = Ya.${name}`);
            nt.defineOn(Ya, rootProp, Ya[name], 'Ya.');
            YaProps.add(rootProp);
            for (let prop in obj)
                delete obj[prop];
            for (let prop in Ya[name])
                obj[prop] = Ya[name][prop];
        }
        function onObfuscatedProperty (Ya, rootProp, obj) {
            if ('AdvManager' in obj || 'AdvManagerStatic' in obj || 'isAllowedRepeatAds' in obj) {
                setObfuscatedProperty(Ya, rootProp, obj, 'Context');
                return Ya.Context;
            }
            if ('create' in obj && 'createAdaptive' in obj && 'createScroll' in obj) {
                setObfuscatedProperty(Ya, rootProp, obj, 'adfoxCode');
                return Ya.adfoxCode;
            }
            return new Proxy(obj, {
                set: (tgt, prop, val) => {
                    if (prop === 'AdvManager' || prop === 'isAllowedRepeatAds') {
                        setObfuscatedProperty(Ya, rootProp, obj, 'Context');
                        return true;
                    }
                    if (prop === 'create' && 'createAdaptive' in obj && 'createScroll' in obj ||
                        prop === 'createScroll' && 'create' in obj && 'createAdaptive' in obj ||
                        prop === 'createAdaptive' && 'create' in obj && 'createScroll' in obj) {
                        setObfuscatedProperty(Ya, rootProp, obj, 'adfoxCode');
                        return true;
                    }
                    tgt[prop] = val;
                    return true;
                },
                get: (tgt, prop) => {
                    if (prop === 'AdvManager' && !(prop in tgt)) {
                        _console.trace(`Injected missing ${prop} in Ya.${rootProp}.`);
                        tgt[prop] = Ya.Context[prop];
                    }
                    return tgt[prop];
                }
            });
        }
        let Rum = {};
        [
            '__timeMarks', '_timeMarks', '__deltaMarks', '_deltaMarks',
            '__defRes', '_defRes', '__defTimes', '_defTimes', '_vars',
            'commonVars'
        ].forEach(name => void(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, 'Ya.Rum', null);
        let Ya = new Proxy({}, {
            set: function(tgt, prop, val) {
                if (val === tgt[prop])
                    return true;
                if (prop === 'Rum') {
                    nt.defineOn(tgt, prop, Rum, 'Ya.');
                    YaProps.add(prop);
                    Object.assign(val, Rum);
                }
                if (YaProps.has(prop)) {
                    _console.log(`Ya.${prop} \u2260`, val);
                    return true;
                }
                if (typeof val === 'object' && prop !== '__inline_params__' && !('length' in val))
                    val = onObfuscatedProperty(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.defineOn(Ya, 'callWithParams', callWithParams, 'Ya.');
        nt.defineOn(Ya, 'PerfCounters', nt.proxy({
            __cacheEvents: []
        }, 'Ya.PerfCounters', null), 'Ya.');
        nt.defineOn(Ya, '__isSent', true, 'Ya.');
        nt.defineOn(Ya, 'confirmUrl', '', 'Ya.');
        nt.defineOn(Ya, 'Direct', nt.proxy({}, 'Ya.Direct', null), 'Ya.');
        nt.defineOn(Ya, 'mediaCode', nt.proxy({
            create: function() {
                if (inIFrame) {
                    _console.log('Removed body of ad-frame.');
                    _document.documentElement.removeChild(_document.body);
                }
            }
        }, 'Ya.mediaCode', null), 'Ya.');
        let extra = nt.proxy({
            extra: nt.proxy({ match: 0, confirm: '', src: '' }),
            id: 0, percent: 100, threshold: 1
        });
        nt.defineOn(Ya, '_exp', nt.proxy({
            id: 0, coin: 0,
            choose: nt.func(extra, 'Ya._exp.choose'),
            get: (prop) => extra.hasOwnProperty(prop) ? extra[prop] : null,
            getId: nt.func(0, 'Ya._exp.getId'),
            defaultVersion: extra,
            getExtra: nt.func(extra.extra, 'Ya._exp.getExtra'),
            getDefaultExtra: nt.func(extra.extra, 'Ya._exp.getDefaultExtra'),
            versions: [extra]
        }), 'Ya.');
        nt.defineOn(Ya, 'c', nt.func(null, 'Ya.c'), 'Ya.');
        nt.defineOn(Ya, 'ADBTools', function(){
            this.getCurrentState = nt.func(true, 'Ya.ADBTools().getCurrentState');
            return nt.proxy(this, 'Ya.ADBTools', null);
        }, 'Ya.');
        nt.defineOn(Ya, 'AdDetector', nt.proxy({}, 'Ya.AdDetector', null), 'Ya.');
        let definePr = o => {
            Object.defineProperty(o, 'pr', {
                get: () => Math.floor(Math.random() * 1e6) + 1,
                set: () => true
            });
        };
        let adfoxCode = {
            forcedDirectLoadingExp: nt.proxy({ isLoadingTurnedOn: false, isExp: false }),
            isLoadingTurnedOn: false,
            xhrExperiment: nt.proxy({ isXhr: true, isControl: true }),
            matchidManager: nt.proxy({}, 'Ya.adfoxCode.matchidManager', null),
            _: []
        };
        definePr(adfoxCode);
        [
            'clearSession', 'create', 'createAdaptive', 'createScroll',
            'destroy', 'moduleLoad', 'reload', 'setModule'
        ].forEach(name => void(adfoxCode[name] = nt.func(null, `Ya.adfoxCode.${name}`)));
        nt.defineOn(Ya, 'adfoxCode', nt.proxy(adfoxCode, 'Ya.adfoxCode', null), 'Ya.');
        let managerForAdfox = {
            loaderVersion: 1,
            isCurrrencyExp: true,
            isReady: nt.func(true, 'Ya.headerBidding.managerForAdfox.isReady'),
            getRequestTimeout: nt.func(300 + Math.floor(Math.random()*100), 'Ya.headerBidding.managerForAdfox.getRequestTimeout')
        };
        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 }`);
            },
            pushAdUnits: nt.func(null, 'Ya.headerBidding.pushAdUnits'),
            managerForAdfox: nt.proxy(managerForAdfox, 'Ya.headerBidding.managerForAdfox', null)
        });
        definePr(headerBidding);
        nt.defineOn(Ya, 'headerBidding', headerBidding, 'Ya.');

        let AdvManager = function() {
            this.render = function(o) {
                if (!o.renderTo)
                    return;
                let placeholder = _document.getElementById(o.renderTo);
                if (!placeholder)
                    return _console.warn('Ya.AdvManager.render call w/o placeholder', o);
                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';
                }
            };
            this.constructor = Object;
            return nt.proxy(this, 'Ya.AdvManager', null);
        };
        nt.defineOn(Ya, 'Context', new Proxy({
            __longExperiment: null,
            _callbacks: nt.proxy([]),
            _asyncModeOn: true,
            _init: nt.func(null, 'Ya.Context._init'),
            _load_callbacks: nt.proxy([]),
            performanceStorage: nt.proxy({}),
            processCallbacks: nt.func(null, 'Ya.Context.processCallbacks'),
            isAllowedRepeatAds: nt.func(null, 'Ya.Context.isAllowedRepeatAds'),
            isNewLoader: nt.func(false, 'Ya.Context.isNewLoader'),
            getItem: nt.func(null, 'Ya.Context.getItem'),
            AdvManager: new AdvManager(),
            AdvManagerStatic: new AdvManager()
        }, {
            get: (ctx, prop) => {
                if (prop in ctx)
                    return ctx[prop];
                _console.trace(`Ya.Context.${prop} = Ya.Context.AdvManager`);
                ctx[prop] = ctx.AdvManager;
                return ctx[prop];
            },
            set: () => true
        }), 'Ya.');
        let Metrika = function Metrika(x) {
            this._ecommerce = '';
            if (x && 'id' in x)
                this.id = x.id;
            else
                this.id = 0;
            return nt.proxy(this, 'Ya.Metrika', null);
        };
        Metrika.counters = () => Ya._metrika.counters;
        nt.defineOn(Ya, 'Metrika', Metrika, 'Ya.');
        nt.defineOn(Ya, 'Metrika2', Metrika, 'Ya.');
        let counter = new Metrika();
        nt.defineOn(Ya, '_metrika', nt.proxy({
            counter: counter,
            counters: [counter],
            hitParam: {},
            counterNum: 0,
            hitId: 0,
            v: 1,
            i: 0,
            _globalMetrikaHitId: 0,
            getCounters: null,
            dataLayer: null,
            f1: null
        }), 'Ya.');
        nt.defineOn(Ya, '_globalMetrikaHitId', 0, 'Ya.');
        counter = {};
        [
            'stringifyParams','_getVars',
            'getUid','getUrl','getHash'
        ].forEach(name => void(counter[name] = nt.func('', `Ya.counter.${name}`)));
        nt.defineOn(Ya, 'counter', nt.proxy(counter, 'Ya.counter', null), 'Ya.');
        nt.defineOn(Ya, 'jserrors', [], 'Ya.');
        nt.defineOn(Ya, 'onerror', nt.func(null, 'Ya.onerror'), 'Ya.');
        let error_on_access = false;
        if ('Ya' in win)
            try {
                _console.log('Found existing Ya object:', win.Ya);
                for (let prop in win.Ya)
                    Ya[prop] = win.Ya[prop];
            } catch(ignore) {
                error_on_access = true;
            }
        if (!error_on_access && // some people don't know how to export only necessary stuff into global context
            location.hostname !== 'material.io') { // so, here is an exception for one of such cases
            for (let prop in Ya)
                if (prop !== '__inline_params__')
                    YaProps.add(prop);
            nt.define('Ya', Ya);
        } else
            _console.warn('Looks like window.Ya blocked with error-on-access scriptlet.');
        // 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('yandex_metrika_callbacks', yandex_metrika_callbacks);
    }, nullTools, createStyle, abortExecutionModule);

    if (!isFirefox) {
        // scripts for non-Firefox browsers
        // https://greatest.deepsurf.us/scripts/14720-it-s-not-important
        unimptt: {
            // BigInt were implemented in Chrome 67 which also support
            // proper user styles and doesn't need this fix anymore.
            if ((isChrome || isOpera) && 'BigInt' in win)
                break unimptt;

            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(node, 'style') : null;

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

                protectedNodes.add(node);
                _setAttribute(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
            });

            const _apply = Reflect.apply;
            _Element.setAttribute = new Proxy(_Element.setAttribute, {
                apply: (fun, that, args) => {
                    if (args[0] && _toLowerCase.call(args[0]) === 'style' && protectedNodes.has(that)) {
                        let value = args[1].replace(imptt, ret_b);
                        log = (args[1] !== value);
                        logger();
                        args[1] = value;
                    }
                    return _apply(fun, that, args);
                }
            });

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

        // Naive ABP Style protector
        if ('ShadowRoot' in win) {
            let _removeChild = Function.prototype.call.bind(_Node.removeChild);
            let _appendChild = Function.prototype.call.bind(_Node.appendChild);
            let createShadow = () => _createElement('shadow');
            // Prevent adding fake content entry point
            let _apply = Reflect.apply;
            _Node.appendChild = new Proxy(_Node.appendChild, {
                apply: (fun, that, args) => {
                    if (that instanceof ShadowRoot &&
                        args[0] instanceof HTMLContentElement)
                        args[1] = createShadow();
                    return _apply(fun, that, args);
                }
            });
            {
                let _shadowSelector = Function.prototype.call.bind(ShadowRoot.prototype.querySelector);
                let _innerHTML = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'innerHTML');
                let _parentNode = Object.getOwnPropertyDescriptor(_Node, 'parentNode');
                if (_innerHTML && _parentNode) {
                    let _set = Function.prototype.call.bind(_innerHTML.set);
                    let _getParent = Function.prototype.call.bind(_parentNode.get);
                    _innerHTML.configurable = false;
                    _innerHTML.set = function() {
                        _set(this, ...arguments);
                        let content = _shadowSelector(this, 'content');
                        if (content) {
                            let parent = _getParent(content);
                            _removeChild(parent, content);
                            _appendChild(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.removeChild = new Proxy(_Node.removeChild, {
                apply: (fun, that, args) => {
                    if (args[0] === style)
                        return;
                    return _apply(fun, that, args);
                }
            });
        }
    }

    if (/^https?:\/\/(mail\.yandex\.|music\.yandex\.|(www\.)?yandex\.[^/]+\/(yand)?search[/?])/i.test(win.location.href) ||
        /^https?:\/\/(tv\.yandex|yandexsport)\./i.test(win.location.href)) {
        // https://greatest.deepsurf.us/en/scripts/809-no-yandex-ads
        const yadWord = /Яндекс.Директ/i,
              adWords = /Реклама|Ad/i;
        // 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(node, 'style', 'display:none!important');
        };
        // Yandex Mail ads
        if (location.hostname.startsWith('mail.')) {
            let wrap = vl => {
                if (!vl)
                    return vl;
                _console.log('Daria =', vl);
                nt.defineOn(vl, 'AdBlock', nt.proxy({
                    detect: nt.func(new Promise(() => null), 'Daria.AdBlock.detect'),
                    enabled: false
                }), 'Daria.');
                nt.defineOn(vl, 'AdvPresenter', nt.proxy({
                    _config: nt.proxy({
                        banner: false,
                        done: false,
                        line: false
                    })
                }), 'Daria.');
                if (vl.Config) {
                    delete vl.Config.adBlockDetector;
                    delete vl.Config['adv-url'];
                    delete vl.Config.cryprox;
                    if (vl.Config.features) {
                        delete vl.Config.features.web_adloader_with_cookie_cache;
                        delete vl.Config.features.web_ads;
                        delete vl.Config.features.web_ads_mute;
                    }
                    vl.Config.mayHaveAdv = false;
                }
                return vl;
            };
            let _Daria = wrap(win.Daria);
            if (_Daria)
                _console.log('Wrapped already existing object "Daria".');
            Object.defineProperty(win, 'Daria', {
                get: () => _Daria,
                set: vl => {
                    if (vl === _Daria)
                        return;
                    _Daria = wrap(vl);
                }
            });
        }
        // detector and ads on Yandex Music
        if (location.hostname.startsWith('music.')) {
            nt.define('tryPay', nt.func(null, 'tryPay'));
            nt.define('Object.prototype.initMegabannerAPI', nt.func(null, 'initMegabannerAPI'));
            nt.define('Object.prototype.mediaAd', undefined);
            nt.define('Object.prototype.detect', () => new Promise(() => null));
            nt.define('Object.prototype.loadContext', () => new Promise(r => r()));
            nt.define('Object.prototype.antiAdbSetup', nt.func(null, 'ya.music.antiAdbSetup'));
        }
        // prevent/defuse adblock detector and cleanup localStorage
        for (let name in localStorage)
            if (name.startsWith('videoplayer-ad-session-') ||
                ['ic', 'yu', 'ludca', 'test'].includes(name))
                localStorage.removeItem(name);
        nt.define('localStorage._mt__data', '');
        nt.define('localStorage.yandexJSPlayerApiSavedSingleVideoSessionWatchedTimeSinceAd', Math.random() * 1000);
        // cookie cleaner
        let yp_keepCookieParts = /\.(sp|ygo|ygu)\./; // ygo = city id; ygu = detect city automatically
        let _doc_proto = ('cookie' in _Document) ? _Document : 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-|__|bltsr=)/.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.trace('expire cookie', value.match(/^[^=]+/)[0]);
                    } else {
                        let parts = value.split(';');
                        let values = parts[0].split('#').filter(part => yp_keepCookieParts.test(part));
                        if (values.length)
                            values[0] = values[0].replace(/^yp=/, '');
                        let res = `yp=${values.join('#')}`;
                        _console.trace(`set cookie ${res}, dropped ${parts[0].replace(res,'')}`);
                        parts[0] = res;
                        value = parts.join(';');
                    }
                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(node, 'role') === 'complementary' ||
                            adWords.test((node.querySelector('.label')||{}).textContent))
                            hide(node);
                }
                // Music ads
                function removeMusicAds() {
                    for (let node of _querySelectorAll('.ads-block'))
                        remove(node);
                }
                // 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(pad, 'style', `width:${node.offsetWidth}px`);
                                node.parentNode.appendChild(pad);
                            }
                            remove(node);
                        }
                }

                if (location.hostname.startsWith('music.')) {
                    pageUpdateObserver(removeMusicAds, _querySelector('.sidebar'));
                    removeMusicAds();
                } else if (location.hostname.startsWith('tv.')) {
                    pageUpdateObserver(removeTVAds, _document.body);
                    removeTVAds();
                } else if (!location.hostname.startsWith('mail.')) {
                    pageUpdateObserver(removeSearchAds, _querySelector('.main__content'));
                    removeSearchAds();
                }
            }
        );
    }

    // Yandex Raven stub (some monitoring sub-system)
    function yandexRavenStub() {
        let nt = new nullTools({ log: true });
        nt.define('Raven', nt.proxy({
            context: f => f(),
            config: nt.func(
                nt.proxy(
                    {}, 'Raven.config',
                    nt.proxy({}, 'Raven.config()..', null)
                ), 'Raven.config')
        }, 'Raven', null));
    }

    // Generic Yandex Scripts
    if (/^https?:\/\/([^.]+\.)*yandex(sport)?\.[^/]+/i.test(win.location.href)) {
        // remove banner on the start page
        // ads on afisha.yandex.ru, however it looks like selectiveEval isn't perfect
        // since eval could be called in scope to access properties of that scope and
        // such calls with it active break functionality on metrika.yandex.ru
        scriptLander(() => {
            if (win.location.hostname === 'afisha.yandex.ru')
                selectiveEval(/AdvManagerStatic/);
            selectiveCookies();
            let nt = new nullTools({log: false, trace: true});
            let AwapsJsonAPI_Json = function(...args) {
                _console.log('>> new AwapsJsonAPI.Json(', ...args, ')');
            };
            [
                'setID', 'addImageContent', 'sendCounts',
                'drawBanner', 'bannerIsInvisible', 'expand', 'refreshAd'
            ].forEach(name => void(AwapsJsonAPI_Json.prototype[name] = nt.func(null, `AwapsJsonAPI.Json.${name}`)));
            AwapsJsonAPI_Json.prototype.checkBannerVisibility = 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.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('AwapsJsonAPI', nt.proxy({
                    Json: AwapsJsonAPI_Json
                }));

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

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

                // remove parts of ga-counter (complete removal break "ТВ Онлайн")
                if (x['ga-counter'] && x['ga-counter'].data) {
                    x['ga-counter'].data.id = 0;
                    delete x['ga-counter'].data.ether;
                    delete x['ga-counter'].data.iframeSrc;
                    delete x['ga-counter'].data.iframeSrcEx;
                }

                // remove adblock detector parameters and clean up detector cookie
                if ('adb' in x) {
                    let cookie = x.adb.data ? x.adb.data.cookie : undefined;
                    if (cookie) {
                        selectiveCookies(cookie);
                        x.adb.data.adb = 0;
                    }
                    delete x.adb;
                }

                return x;
            };
            // Yandex banner on main page and some other things
            let _home = win.home,
                _home_set = !!_home;
            Object.defineProperty(win, 'home', {
                get: () => _home,
                set: vl => {
                    if (!_home_set && vl === _home)
                        return;
                    _home_set = false;
                    _console.log('home =', vl);
                    let _home_export = parseExport(vl.export);
                    Object.defineProperty(vl, 'export', {
                        get: () => _home_export,
                        set: vl => {
                            _home_export = parseExport(vl);
                        }
                    });
                    _home = vl;
                }
            });
            // adblock circumvention on some Yandex domains
            yandexRavenStub();
            // yandex.ru/news/ and yandex.ru/sport/
            abortExecution(onAccess.Get, 'yaads.adRenderedCount');
            let AdvertPartner = nt.func(false, 'AdvertPartner');
            nt.defineOn(AdvertPartner, 'defaultProps', {}, 'AdvertPartner.');
            nt.defineOn(AdvertPartner, 'contextTypes', [], 'AdvertPartner.');
            nt.define('Object.prototype.AdvertPartner', AdvertPartner);
            // ads in videoplayer
            if (location.pathname.startsWith('/embed/')) {
                let _Sandbox = undefined;
                const _apply = Reflect.apply,
                      _define = Object.defineProperty;
                _define(win, 'Sandbox', {
                    get: () => _Sandbox,
                    set: vl => {
                        if (vl && vl !== _Sandbox) {
                            let _decl = vl.decl,
                                _init = vl.init;
                            _define(vl, 'init', {
                                get: () => _init,
                                set: vi => {
                                    _init = new Proxy(vi, {
                                        apply (fun, that, args) {
                                            let cfg = args[0];
                                            if ('ad_config_json' in cfg)
                                                cfg.ad_config_json = '{}';
                                            if ('ad_genre_json' in cfg)
                                                cfg.ad_config_json = '[]';
                                            if ('ad_genre_json_hash' in cfg)
                                                cfg.ad_genre_json_hash = '{ad_genre_json_hash}';
                                            if ('with_ad_insertion' in cfg)
                                                cfg.with_ad_insertion = 'false';
                                            if ('tracking_events' in cfg)
                                                cfg.tracking_events = {};
                                            return _apply(fun, that, args);
                                        }
                                    });
                                }
                            });
                            _define(vl, 'decl', {
                                get: () => _decl,
                                set: vd => {
                                    _decl = new Proxy(vd, {
                                        apply (fun, that, args) {
                                            let cfg = args[0];
                                            if ('_getAdConfig' in cfg)
                                                cfg._getAdConfig = new Proxy(cfg._getAdConfig, {
                                                    apply (fun, that, args) {
                                                        let res = _apply(fun, that, args);
                                                        if (res.hasPreroll)
                                                            res.hasPreroll = false;
                                                        return res;
                                                    }
                                                });
                                            return _apply(fun, that, args);
                                        }
                                    });
                                }
                            });
                        }
                        _Sandbox = vl;
                    }
                });
            }
            // abp detector cookie on yandex pogoda and afisha
            const _apply = Reflect.apply
            win.Element.prototype.getAttribute = new Proxy(win.Element.prototype.getAttribute, {
                apply (get, el, args) {
                    let res = _apply(get, el, args);
                    if (res && res.length > 20 && el instanceof HTMLBodyElement)
                        try {
                            let o = JSON.parse(res),
                                found = false, check;
                            for (let prop in o) {
                                check = 'param' in o[prop] || 'aabCookieName' in o[prop];
                                if (check || 'banners' in o[prop]) {
                                    found = true;
                                    if (check)
                                        selectiveCookies(o[prop].param || o[prop].aabCookieName);
                                    _console.log(el.tagName, o, 'removed', o[prop]);
                                    delete o[prop];
                                }
                            }
                            if (!found) _console.log(el.tagName, o);
                            res = JSON.stringify(o);
                        } catch(ignore) {}
                    return res;
                }
            });
            // hide a few ad placeholders on yandex.ry/sport/
            if (location.pathname.startsWith('/sport/'))
                createStyle('.sport-advert_type_card { display: none !important }');
        }, nullTools, yandexRavenStub, 'let _setAttribute = Function.prototype.call.bind(_Element.setAttribute)',
                     abortExecutionModule, selectiveCookies, selectiveEval);

        if ('attachShadow' in _Element) try {
            let fakeRoot = () => ({
                firstChild: null,
                appendChild: () => null,
                querySelector: () => null,
                querySelectorAll: () => null
            });
            _Element.createShadowRoot = fakeRoot;
            let shadows = new WeakMap();
            let _attachShadow = Object.getOwnPropertyDescriptor(_Element, 'attachShadow');
            _attachShadow.value = function() {
                return shadows.set(this, fakeRoot()).get(this);
            };
            Object.defineProperty(_Element, 'attachShadow', _attachShadow);
            let _shadowRoot = Object.getOwnPropertyDescriptor(_Element, 'shadowRoot');
            _shadowRoot.set = () => null;
            _shadowRoot.get = function() {
                return shadows.has(this) ? shadows.get(this) : undefined;
            };
            Object.defineProperty(_Element, 'shadowRoot', _shadowRoot);
        } catch(e) {
            _console.warn('Unable to wrap Element.prototype.attachShadow\n', e);
        }

        // Disable banner styleSheet (on main page)
        document.addEventListener('DOMContentLoaded', () => {
            for (let sheet of document.styleSheets)
                try {
                    for (let rule of sheet.cssRules)
                        if (rule.cssText.includes(' 728px 90px')) {
                            rule.parentStyleSheet.disabled = true;
                            _console.log('Disabled banner styleSheet:', rule.parentStyleSheet);
                        }
                } catch(ignore) {}
        }, false);

        // 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) {
            if (scope instanceof Element)
                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 });
    }

    // Based on https://greatest.deepsurf.us/en/scripts/21937-moonwalk-hdgo-kodik-fix v0.8
    PlayerFix: {
        let log = name => _console.log(`Player FIX: Detected ${name} player in ${location.href}`);
        function removeVast (data) {
            if (data && typeof data === 'object') {
                _console.log('Player configuration:', data);
                if (data.advert_script && data.advert_script !== '') {
                    _console.log('Set data.advert_script to empty string.');
                    data.advert_script = '';
                }
                let keys = Object.getOwnPropertyNames(data);
                let isVast = name => /vast|clickunder/.test(name);
                if (!keys.some(isVast))
                    return data;
                for (let key of keys)
                    if (typeof data[key] === 'object' && key !== 'links') {
                        _console.log(`Removed data.${key}:`, data[key]);
                        delete data[key];
                    }
                if (data.chain) {
                    let need = [],
                        drop = [],
                        links = data.chain.split('.');
                    for (let link of links)
                        if (!isVast(link))
                            need.push(link);
                        else
                            drop.push(link);
                    _console.log('Dropped from the chain:', ...drop);
                    data.chain = need.join('.');
                }
            }
            return data;
        }

        let _hasOwnProperty = win.Function.prototype.apply.bind(win.Object.prototype.hasOwnProperty);
        let _construct = win.Reflect.construct;
        _document.addEventListener(
            'DOMContentLoaded', function() {
                if ('video_balancer_options' in win && 'event_callback' in win) {
                    log('Moonwalk');
                    if (video_balancer_options.adv)
                        removeVast(video_balancer_options.adv);
                    if ('_mw_adb' in win)
                        Object.defineProperty(win, '_mw_adb', {
                            get: () => false,
                            set: () => true
                        });
                } else if (win.startKodikPlayer !== undefined) {
                    log('Kodik');
                    // skip attempt to block access to HD resolutions
                    let chainCall = new Proxy({}, { get: () => () => chainCall });
                    if ($ && $.prototype && $.prototype.addClass) {
                        let $addClass = $.prototype.addClass;
                        $.prototype.addClass = function (className) {
                            if (className === 'blocked')
                                return chainCall;
                            return $addClass.apply(this, arguments);
                        };
                    }
                    // remove ad links from the metadata
                    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,
                                _toString = Function.prototype.call.bind(Function.prototype.toString);
                            win.setInterval = function(func) {
                                if (func instanceof Function && _toString(func).includes('_delay')) {
                                    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 && _toString(func).includes('adv_showed'))
                                    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);
                } else if ('Playerjs' in win) {
                    log('Playerjs');
                    win.Playerjs = new Proxy(win.Playerjs, {
                        construct (fn, args) {
                            let params = args[0];
                            if (params && typeof params === 'object') {
                                delete params.preroll;
                                params = removeVast(params);
                                Object.defineProperty(params, 'hasOwnProperty', {
                                    value: function(...args) {
                                        let res = _hasOwnProperty(this, args);
                                        if (typeof args[0] === 'string' && args[0].startsWith('vast_') &&
                                            res && params[args[0]]) {
                                            _console.log(`Removed params.${args[0]}:`, params[args[0]]);
                                            delete params[args[0]];
                                            return false;
                                        }
                                        return res;
                                    },
                                    enumerable: false,
                                    configurable: true
                                });
                            }
                            return _construct(fn, args);
                        }
                    });
                }

                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
        );
    }

    // Applies wrapper function on the current page and all newly created same-origin iframes
    // This is used to prevent trick which allows to get fresh page API through newly created same-origin iframes
    function deepWrapAPI(wrapper) {
        let wrapped = new WeakSet();
        const log = (...args) => false && _console.log(...args),
              bindApply = fun => Function.prototype.apply.bind(fun),
              _apply = Reflect.apply;
        const _HTMLIFrameElement = HTMLIFrameElement.prototype,
              isIFrameElement = _HTMLIFrameElement.isPrototypeOf.bind(_HTMLIFrameElement);
        const _contentWindow = Object.getOwnPropertyDescriptor(_HTMLIFrameElement, 'contentWindow'),
              _get_contentWindow = bindApply(_contentWindow.get);

        function wrapAPI(root) {
            if (!root || wrapped.has(root))
                return;
            wrapped.add(root);
            try {
                wrapper(isIFrameElement(root) ? _get_contentWindow(root) : root);
                log('Wrapped API in', (root === win) ? "main window." : root);
            } catch(e) {
                log('Failed to wrap API in', (root === win) ? "main window." : root, '\n', e);
            }
        };

        // wrap API on contentWindow access
        const getter = { apply (get, that, args) {
            wrapAPI(that);
            return _apply(get, that, args);
        }};
        _contentWindow.get = new Proxy(_contentWindow.get, getter);
        Object.defineProperty(_HTMLIFrameElement, 'contentWindow', _contentWindow);
        // wrap API on contentDocument access
        const _contentDocument = Object.getOwnPropertyDescriptor(_HTMLIFrameElement, 'contentDocument');
        _contentDocument.get = new Proxy(_contentDocument.get, getter);
        Object.defineProperty(_HTMLIFrameElement, 'contentDocument', _contentDocument);

        // manual children objects traverser to avoid issues
        // with calling querySelectorAll on wrong types of objects
        const _nodeType = bindApply(Object.getOwnPropertyDescriptor(_Node, 'nodeType').get),
              _childNodes = bindApply(Object.getOwnPropertyDescriptor(_Node, 'childNodes').get),
              _ELEMENT_NODE = _Node.ELEMENT_NODE,
              _DOCUMENT_FRAGMENT_NODE = _Node.DOCUMENT_FRAGMENT_NODE
        const wrapFrames = root => {
            if (_nodeType(root) !== _ELEMENT_NODE && _nodeType(root) !== _DOCUMENT_FRAGMENT_NODE)
                return; // only process nodes which may contain an IFRAME or be one
            if (isIFrameElement(root)) {
                wrapAPI(root);
                return;
            }
            for (let child of _childNodes(root))
                wrapFrames(child);
        };

        // wrap API in a newly appended iframe objects
        _Node.appendChild = new Proxy(_Node.appendChild, {
            apply (fun, that, args) {
                let res = _apply(fun, that, args);
                wrapFrames(args[0]);
                return res;
            }
        });

        // wrap API in iframe objects created with innerHTML of element on page
        const _innerHTML = Object.getOwnPropertyDescriptor(_Element, 'innerHTML');
        _innerHTML.set = new Proxy(_innerHTML.set, {
            apply (set, that, args) {
                _apply(set, that, args);
                if (_document.contains(that))
                    wrapFrames(that);
            }
        });
        Object.defineProperty(_Element, 'innerHTML', _innerHTML);

        wrapAPI(win);
    }

    // piguiqproxy.com / zmctrack.net circumvention and onerror callback prevention
    scriptLander(
        () => {
            // onerror callback blacklist
            let masks = [],
                //blockAll = /(^|\.)(rutracker-org\.appspot\.com)$/,
                isBlocked = url => masks.some(mask => mask.test(url));// || blockAll.test(location.hostname);
            for (let filter of [// blacklist
                // global
                '/adv/www/',
                // adservers
                '||185.87.50.147^',
                '||10root25.website^', '||24video.xxx^',
                '||adlabs.ru^', '||adspayformymortgage.win^', '||amgload.net^', '||aviabay.ru^',
                '||bgrndi.com^', '||brokeloy.com^',
                '||cdnjs-aws.ru^','||cnamerutor.ru^',
                '||directadvert.ru^', '||docfilms.info^', '||dreadfula.ru^', '||dsn-fishki.ru^',
                '||et-cod.com^', '||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^',
                '||kinotochka.net^', '||kinott.com^', '||kinott.ru^',
                '||klcheck.com^', '||kuveres.com^',
                '||lepubs.com^', '||luxadv.com^', '||luxup.ru^', '||luxupcdna.com^',
                '||marketgid.com^', '||mebablo.com^', '||mixadvert.com^', '||mxtads.com^',
                '||nickhel.com^',
                '||oconner.biz^', '||oconner.link^', '||octoclick.net^', '||octozoon.org^',
                '||pigiuqproxy.com^', '||piguiqproxy.com^', '||pkpojhc.com^',
                '||psma01.com^', '||psma02.com^', '||psma03.com^',
                '||rcdn.pro^', '||recreativ.ru^', '||redtram.com^', '||regpole.com^',
                '||rootmedia.ws^', '||ruttwind.com^', '||rutvind.com^',
                '||skidl.ru^', '||smi2.net^', '||smcheck.org^',
                '||torvind.com^', '||traffic-media.co^', '||trafmag.com^', '||trustjs.net^', '||ttarget.ru^',
                '||u-dot-id-adtool.appspot.com^', '||utarget.ru^',
                '||webadvert-gid.ru^', '||webadvertgid.ru^',
                '||xxuhter.ru^',
                '||yuiout.online^',
                '||zmctrack.net^', '||zoom-film.ru^'])
                masks.push(new RegExp(
                    filter.replace(/([\\/[\].+?(){}$])/g, '\\$1')
                    .replace(/\*/g, '.*?')
                    .replace(/\^(?!$)/g,'\\.?[^\\w%._-]')
                    .replace(/\^$/,'\\.?([^\\w%._-]|$)')
                    .replace(/^\|\|/,'^(ws|http)s?:\\/+([^/.]+\\.)*?'),
                    'i'));
            // main script
            deepWrapAPI(root => {
                let _call = root.Function.prototype.call,
                    _defineProperty = root.Object.defineProperty,
                    _getOwnPropertyDescriptor = root.Object.getOwnPropertyDescriptor;
                onerror: {
                    // 'onerror' handler for scripts from blacklisted sources
                    let scriptMap = new WeakMap();
                    const _apply = root.Reflect.apply,
                          _HTMLScriptElement = root.HTMLScriptElement,
                          _HTMLImageElement = root.HTMLImageElement;
                    const _get_tagName = _call.bind(_getOwnPropertyDescriptor(root.Element.prototype, 'tagName').get),
                          _get_scr_src = _call.bind(_getOwnPropertyDescriptor(_HTMLScriptElement.prototype, 'src').get),
                          _get_img_src = _call.bind(_getOwnPropertyDescriptor(_HTMLImageElement.prototype, 'src').get);
                    const _get_src = node => {
                        if (node instanceof _HTMLScriptElement)
                            return _get_scr_src(node);
                        if (node instanceof _HTMLImageElement)
                            return _get_img_src(node);
                        return undefined
                    };
                    const _onerror = _getOwnPropertyDescriptor(root.HTMLElement.prototype, 'onerror'),
                          _set_onerror = _call.bind(_onerror.set);
                    _onerror.get = function() {
                        return scriptMap.get(this) || null;
                    };
                    _onerror.set = function(callback) {
                        if (typeof callback !== 'function') {
                            scriptMap.delete(this);
                            _set_onerror(this, callback);
                            return;
                        }
                        scriptMap.set(this, callback);
                        _set_onerror(this, function() {
                            let src = _get_src(this);
                            if (isBlocked(src)) {
                                _console.trace(`Blocked "onerror" callback from ${_get_tagName(this)}: ${src}`);
                                return;
                            }
                            _apply(scriptMap.get(this), this, arguments);
                        });
                    };
                    _defineProperty(root.HTMLElement.prototype, 'onerror', _onerror);
                }
                // Simplistic WebSocket wrapper for Maxthon and Firefox before v58
                WSWrap: { // once again seems required in Google Chrome and similar browsers due to zmctrack.net -_-
                    if (true /*/Maxthon/.test(navigator.appVersion) ||
                        'InstallTrigger' in win && 'StopIteration' in win*/) {
                        let _ws = _getOwnPropertyDescriptor(root, 'WebSocket');
                        if (!_ws)
                            break WSWrap;
                        _ws.value = new Proxy(_ws.value, {
                            construct: (ws, args) => {
                                if (isBlocked(args[0])) {
                                    _console.log('Blocked WS connection:', args[0]);
                                    return {};
                                }
                                return new ws(...args);
                            }
                        });
                        _defineProperty(root, 'WebSocket', _ws);
                    }
                }
                untrustedClick: {
                    // 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 new windows.
                    let _dispatchEvent = _call.bind(root.EventTarget.prototype.dispatchEvent);
                    root.EventTarget.prototype.dispatchEvent = function dispatchEvent(e) {
                        if (!e.isTrusted && e.type === 'click' && e.constructor.name === 'MouseEvent' &&
                            !this.parentNode && this.tagName === 'A' && this.target[0] === '_') {
                            _console.log('Blocked dispatching a click event on a parentless anchor:', this);
                            return;
                        }
                        return _dispatchEvent(this, ...arguments);
                    };
                }
                // XHR Wrapper
                let _proto = undefined;
                try {
                    _proto = root.XMLHttpRequest.prototype;
                } catch(ignore) {
                    return;
                };
                // blacklist of domains where all third-party requests are ignored
                const ondomains = /(^|[/.@])oane\.ws($|[:/])/i;
                // highly suspicious URLs
                const suspicious = /^(https?:)?\/\/(?!(rutube|shazoo|worldoftanks)\.ru[:/])(csp-)?([a-z0-9]{6}){1,2}\.ru\//i;
                const on_get_ban = /^(https?:)?\/\/(?!(rutube|shazoo|worldoftanks)\.ru[:/])(csp-)?([a-z0-9]{6}){1,2}\.ru\/([a-z0-9/]{40,}|[a-z0-9]{8,}|ad\/banner\/.+|show\/\?\d+=\d+&.+)$/i;
                const on_post_ban = /^(https?:)?\/\/(?!(rutube|shazoo|worldoftanks)\.ru[:/])(csp-)?([a-z0-9]{6}){1,2}\.ru\/([a-z0-9]{6,})$/i;
                const yandex_direct = /^(https?:)?\/\/([^.]+\.)??yandex(\.[a-z]{2,3}){1,2}\/((images|weather|sport)\/[a-z0-9/_-]{40,}|jstracer?|j?clck\/.*|set\/s\/rsya-tag-users\/data(\?.*)?|static\/main\.js(\?.*)?)$/i;
                const more_y_direct = /^(https?:)?\/\/((([^.]+\.)??(24smi\.org|(echo\.msk|drive2|kakprosto|liveinternet|razlozhi)\.ru)\/(.{290,}|[a-z0-9/_-]{100,}))|yastatic\.net\/.*?\/chunks\/promo\/.*)$/i;
                const whitelist = /^(https?:)?\/\/yandex\.ru\/yobject$/;
                const fabPatterns = /\/fuckadblock/i;

                const blockedUrls = new Set();
                function checkRequest(fname, method, url) {
                    let block = isBlocked(url) ||
                        ondomains.test(location.hostname) && !ondomains.test(url) ||
                        method !== 'POST' && on_get_ban.test(url) ||
                        method === 'POST' && on_post_ban.test(url) ||
                        yandex_direct.test(url) || more_y_direct.test(url);
                    let allow = block && whitelist.test(url) ||
                        // Fix for infinite load on Yandex Images: find image, open "other sizes and similar images" in a new tab, click on a preview of a similar image
                        (block && method === 'script.src' &&
                         root.location.pathname === '/images/search' && root.location.hostname.startsWith('yandex.') &&
                         url.startsWith('http') && url.includes('/images/')) || // Direct URLs are similar, but don't have protocol for some reason
                        (block && root.location.hostname === 'widgets.kinopoisk.ru' && url.includes('/static/main.js?')) ||
                        (block && !url.startsWith('http') && // drive2.ru hid a little CSS style in their requests which shows page content like this
                         (root.location.hostname === 'drive2.ru' || root.location.hostname.endsWith('.drive2.ru')));
                    if (allow) {
                        block = false;
                        _console.trace(`Allowed ${fname} ${method} request %o from %o`, url, root.location.href);
                    }
                    if (block) {
                        if (!blockedUrls.has(url)) // don't repeat log if the same URL were blocked more than once
                            _console.trace(`Blocked ${fname} ${method} request %o from %o`, url, root.location.href);
                        blockedUrls.add(url);
                        return true;
                    }
                    if (!allow && suspicious.test(url))
                        _console.trace(`Suspicious ${fname} ${method} request %o from %o`, url, root.location.href);
                    return false;
                }

                // workaround for a broken weather mini-map on Yandex
                let skip_xhr_check = false;
                if (root.location.hostname.startsWith('yandex.') &&
                    root.location.pathname.startsWith('/pogoda/') ||
                    root.location.hostname.endsWith('.kakprosto.ru'))
                    skip_xhr_check = true;

                let xhrStopList = new WeakSet();
                let _open = root.Function.prototype.apply.bind(_proto.open);
                _proto.open = function open() {
                    '[native code]';
                    return !skip_xhr_check && checkRequest('xhr', ...arguments) ?
                        (xhrStopList.add(this), undefined) : _open(this, arguments);
                };
                ['send', 'setRequestHeader', 'getAllResponseHeaders'].forEach(
                    name => {
                        let func = _proto[name];
                        _proto[name] = function(...args) {
                            return xhrStopList.has(this) ? null : func.apply(this, args);
                        };
                    }
                );
                // simulate readyState === 1 for blocked requests
                let _readyState = Object.getOwnPropertyDescriptor(_proto, 'readyState');
                let _get_readyState = root.Function.prototype.apply.bind(_readyState.get);
                _readyState.get = function() {
                    return xhrStopList.has(this) ? 1 : _get_readyState(this, arguments);
                }
                Object.defineProperty(_proto, 'readyState', _readyState);

                let _fetch = root.Function.prototype.apply.bind(root.fetch);
                root.fetch = function fetch() {
                    '[native code]';
                    let url = arguments[0];
                    let method = arguments[1] ? arguments[1].method : undefined;
                    if (arguments[0] instanceof Request) {
                        method = url.method;
                        url = url.url;
                    }
                    if (checkRequest('fetch', method, url))
                        return new Promise(() => null);
                    return _fetch(root, arguments);
                };

                let _script_src = Object.getOwnPropertyDescriptor(root.HTMLScriptElement.prototype, 'src');
                let _script_src_set = root.Function.prototype.apply.bind(_script_src.set);
                let _dispatchEvent = root.Function.prototype.call.bind(root.EventTarget.prototype.dispatchEvent);
                _script_src.set = function(src) {
                    if (fabPatterns.test(src)) {
                        _console.trace('Blocked set script.src request:', src);
                        deployFABStub(root);
                        setTimeout(() => {
                            let e = root.document.createEvent('Event');
                            e.initEvent('load', false, false);
                            _dispatchEvent(this, e);
                        }, 0);
                        return;
                    }
                    return checkRequest('set', 'script.src', src) || _script_src_set(this, arguments);
                };
                Object.defineProperty(root.HTMLScriptElement.prototype, 'src', _script_src);

                let adregain_pattern = /ggg==" alt="advertisement"/;
                if (root.self !== root.top) { // in IFrame
                    let _write = Function.prototype.call.bind(root.document.write);
                    root.document.write = function write(text, ...args) {
                        "[native code]";
                        if (adregain_pattern.test(text)) {
                            _console.log('Skipped AdRegain frame.');
                            return _write(this, '');
                        }
                        return _write(this, text, ...args);
                    };
                }
            });
        }, deepWrapAPI
    );

    // === 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)
    function scissors (selector, words, scope, params) {
        const logger = (...args) => { if (params.log) _console.log(...args) };
        const scHide = node => {
            let style = _getAttribute(node, 'style') || '',
                hide = ';display:none!important;';
            if (style.indexOf(hide) < 0)
                _setAttribute(node, 'style', style + hide);
        };

        if (!scope.contains(_document.body))
            logger('[s] scope', scope);
        let remFunc = (params.hide ? scHide : node => node.parentNode.removeChild(node)),
            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.createElement,
            _appendChild = _Element.appendChild,
            fakeNative = (f) => (f.toString = () => `function ${f.name}() { [native code] }`);

        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 === undefined;
        };

        let redefineOpen = (root) => {
            if ('open' in root) {
                let _open = root.open.bind(root);
                nt.defineOn(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]) if ('createElement' in root)
                nt.defineOn(root, 'createElement', createElement, 'Document.prototype.');
        };
        redefineCreateElement(win);

        // wrap window.open in newly added first-party frames
        _Element.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.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 open = (...args) => {
                '[native code]';
                _console.trace('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.trace('Suspicious local new window', ...arguments);
                    checkRedirect();
                    return _open.apply(this, arguments);
                }
                _console.trace('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
    );

    const evalPatternYandex = /{exports:{},id:r,loaded:!1}|containerId:(.|\r|\n)+params:/,
          evalPatternGeneric = /_0x|location\s*?=|location.href\s*?=|location.assign\(|open\(/i;
    function selectiveEval(...patterns) {
        if (patterns.length === 0)
            patterns.push(evalPatternGeneric);
        let _eval_def = Object.getOwnPropertyDescriptor(win, 'eval');
        if (!_eval_def || !_eval_def.value) {
            _console.warn('Unable to wrap window.eval:', _eval_def);
            return;
        }
        let _eval_val = _eval_def.value;
        _eval_def.value = function(...args) {
            if (patterns.some(pattern => pattern.test(args[0]))) {
                _console.trace(`Skipped eval of ${args[0].slice(0, 512)}\u2026`);
                return null;
            }
            try {
                return _eval_val.apply(this, args);
            } catch(e) {
                _console.error('Crash source:', args[0]);
                throw e;
            }
        };
        Object.defineProperty(win, 'eval', _eval_def);
    }
    selectiveEval.toString = new Proxy(selectiveEval.toString, {
        apply: (...args) => Reflect.apply(...args) + `const evalPatternYandex = ${evalPatternYandex}, evalPatternGeneric = ${evalPatternGeneric}`
    });

    // hides cookies by pattern and attempts to remove them if they already set
    // also prevents setting new versions of such cookies
    function selectiveCookies(scPattern = '', scPaths = []) {
        let patterns = scPattern.split('|');
        if (patterns[0] !== ';default') {
            // Google Analytics cookies
            patterns.push('_g(at?|id)|__utm[a-z]');
            // Yandex ABP detection cookies
            patterns.push('bltsr|blcrm');
        } else
            patterns.shift();
        let blacklist = new RegExp(`(^|;\\s?)(${patterns.join('|')})($|=)`);
        if (isFirefox && scPaths.length)
            scPaths = scPaths.concat(scPaths.map(path => `${path}/`));
        scPaths.push('/');
        let _doc_proto = ('cookie' in _Document) ? _Document : Object.getPrototypeOf(_document);
        let _cookie = Object.getOwnPropertyDescriptor(_doc_proto, 'cookie');
        if (_cookie) {
            let _set_cookie = Function.prototype.call.bind(_cookie.set);
            let _get_cookie = Function.prototype.call.bind(_cookie.get);
            let expireDate = 'Thu, 01 Jan 1970 00:00:01 UTC';
            let expireAge = '-99999999';
            let expireBase = `=;expires=${expireDate};Max-Age=${expireAge}`;
            let expireAttempted = {};
            // expire is called from cookie getter and doesn't know exact parameters used to set cookies present there
            // so, it will use path=/ by default if scPaths wasn't set and attempt to set cookies on all parent domains
            let expire = (cookie, that) => {
                let domain = that.location.hostname.split('.'),
                    name = cookie.replace(/=.*/,'');
                scPaths.forEach(path =>_set_cookie(that, `${name}${expireBase};path=${path}`));
                while (domain.length > 1) {
                    try {
                        scPaths.forEach(
                            path => _set_cookie(that, `${name}${expireBase};domain=${domain.join('.')};path=${path}`)
                        );
                    } catch(e) { _console.error(e); }
                    domain.shift();
                }
                expireAttempted[name] = true;
                _console.log('Removing existing cookie:', cookie);
            };
            // skip setting unwanted cookies
            _cookie.set = function(value) {
                if (blacklist.test(value)) {
                    _console.trace('Ignored cookie: %s', value);
                    // try to remove same cookie if it already exists using exact values from the set string
                    if (blacklist.test(_get_cookie(this))) {
                        let parts = value.split(/;\s?/),
                            name = parts[0].replace(/=.*/,''),
                            newParts = [`${name}=`, `expires=${expireDate}`, `Max-Age=${expireAge}`],
                            skip = [name, 'expires', 'Max-Age'];
                        for (let part of parts)
                            if (!skip.includes(part.replace(/=.*/,'')))
                                newParts.push(part);
                        try {
                            _set_cookie(this, newParts.join(';'));
                        } catch(e) { _console.error(e); }
                        _console.log('Removing existing cookie:', name);
                    }
                    return;
                }
                return _set_cookie(this, value);
            };
            // hide unwanted cookies from site
            _cookie.get = function() {
                let res = _get_cookie(this);
                if (blacklist.test(res)) {
                    let stack = [];
                    for (let cookie of res.split(/;\s?/))
                        if (!blacklist.test(cookie))
                            stack.push(cookie);
                        else {
                            let name = cookie.replace(/=.*/,'');
                            if (expireAttempted[name]) {
                                _console.log('Unable to expire:', cookie);
                                expireAttempted[name] = false;
                            }
                            if (!(name in expireAttempted))
                                expire(cookie, this);
                        }
                    res = stack.join('; ');
                }
                return res;
            };
            Object.defineProperty(_doc_proto, 'cookie', _cookie);
            _console.log('Active cookies:', win.document.cookie);
        }
    }

    // Locates a node with specific text in Russian
    // Uses table of substitutions for similar letters
    let selectNodeByTextContent = (()=> {
        let subs = {
            // english & greek
            'А': 'AΑ', 'В': 'BΒ', 'Г':'Γ',
            'Е': 'EΕ', 'З': '3',  'К':'KΚ',
            'М': 'MΜ', 'Н': 'HΗ', 'О':'OΟ',
            'П': 'Π',  'Р': 'PΡ', 'С':'C',
            'Т': 'T',  'Ф': 'Φ',  'Х':'XΧ'
        }
        let regExpBuilder = text => new RegExp(
            text.toUpperCase()
            .split('')
            .map(function(e){
                return `${e in subs ? `[${e}${subs[e]}]` : (e === ' ' ? '\\s+' : e)}[\u200b\u200c\u200d]*`;
            })
            .join(''),
            'i');
        let reMap = {};
        return (re, opts = { root: _document.body }) => {
            if (!re.test) {
                if (!reMap[re])
                    reMap[re] = regExpBuilder(re);
                re = reMap[re];
            }

            for (let child of opts.root.children)
                if (re.test(child.textContent)) {
                    if (opts.shallow)
                        return child;
                    opts.root = child;
                    return selectNodeByTextContent(re, opts) || child;
                }
        }
    })();

    // webpackJsonp filter
    function webpackJsonpFilter(blacklist, log = false) {
        let _apply = Reflect.apply;
        let _toString = Function.prototype.call.bind(Function.prototype.toString);
        function wrapPush(webpack) {
            let _push = webpack.push.bind(webpack);
            Object.defineProperty(webpack, 'push', {
                get: () => _push,
                set: vl => {
                    _push = new Proxy(vl, {
                        apply: (push, obj, args) => {
                            wrapper: {
                                if (!(args[0] instanceof Array))
                                    break wrapper;
                                let mainName;
                                if (args[0][2] instanceof Array && args[0][2][0] instanceof Array)
                                    mainName = args[0][2][0][0];
                                let funs = args[0][1];
                                if (!(funs instanceof Object && !(funs instanceof Array)))
                                    break wrapper;
                                for (let name in funs) {
                                    if (typeof funs[name] !== 'function')
                                        continue;
                                    if (blacklist.test(_toString(funs[name])) && name !== mainName) {
                                        let text = log ? _toString(funs[name]) : '';
                                        funs[name] = () => _console.log(`Skip webpack ${name}`, text);
                                    }
                                }
                            }
                            _console.log('webpack.push()');
                            return _apply(push, obj, args);
                        }
                    });
                    return true;
                }
            });
            return webpack
        }
        let _webpackJsonp = wrapPush([]);
        Object.defineProperty(win, 'webpackJsonp', {
            get: () => _webpackJsonp,
            set: vl => {
                if (vl === _webpackJsonp)
                    return;
                _console.log('new webpackJsonp', vl);
                _webpackJsonp = wrapPush(vl);
                return true;
            }
        });
    }

    // === 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, tapehub.tech, 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: () => {
            nt.define('CNight', win.CoinHive);
            if (location.pathname.startsWith('/embed/')) {
                nt.define('BetterJsPop', {
                    add: ((a, b) => _console.trace('BetterJsPop.add(%o, %o)', a, b)),
                    config: ((o) => _console.trace('BetterJsPop.config(%o)', o)),
                    Browser: { isChrome: true }
                });
                nt.define('isSandboxed', nt.func(null, 'isSandboxed'));
                nt.define('adblock', false);
                nt.define('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, 'appendChild');
        let _appendChild_value = _appendChild.value;
        _appendChild.value = function appendChild(node) {
            if (this === _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, 'appendChild', _appendChild);

        // disable window focus tricks and changing location
        let focusHandlerName = /\WfocusAchieved\(/
        let _toString = Function.prototype.call.bind(Function.prototype.toString);
        let _setInterval = win.setInterval;
        win.setInterval = (...args) => {
            if (args.length && focusHandlerName.test(_toString(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(args[1]))) {
                _console.log('skip addEventListener for', ...args);
                return undefined;
            }
            return _addEventListener.apply(this, args);
        };

        // generic popup prevention
        preventPopups();
    };

    scripts['tortuga.wtf'] = () => {
        nt.define('Object.prototype.hideab', undefined);
    };

    scripts['tv-kanali.online'] = () => {
        const _apply = Reflect.apply;
        win.setTimeout = new Proxy(win.setTimeout, {
            apply (fun, that, args) {
                if (args[0].name && args[0].name.includes('doAd'))
                    return;
                args[1] === 30000 && (args[1] = 100);
                return _apply(fun, that, args);
            }
        });
    };

    scripts['rustorka.com'] = {
        other: [
            'rustorka.innal.top, rustorka2.innal.top, rustorka3.innal.top',
            'rustorka4.innal.top, rustorka5.innal.top, rustorka.naylo.top',
            'rustorka.club, rustorka.lib, rustorka.net'
        ].join(', '),
        now: () => scriptLander(() => {
            selectiveEval(evalPatternGeneric, /antiadblock/);
            selectiveCookies('adblock|u_count|gophp|st2|st3', ['/forum']);
            abortExecution(onAccess.InlineScript, 'ads_script');
        }, selectiveEval, selectiveCookies, abortExecutionModule)
    };

    // = other ======================================================================================
    scripts['1tv.ru'] = {
        other: 'mediavitrina.ru',
        now: () => scriptLander(() => {
            nt.define('EUMPAntiblockConfig', nt.proxy({url: '//www.1tv.ru/favicon.ico'}));
            nt.define('Object.prototype.disableSeek', nt.func(undefined, 'disableSeek'));
            nt.define('preroll', false);

            let _EUMP = undefined;
            const _EUMP_set = x => {
                if (x === _EUMP)
                    return true;
                let _plugins = x.plugins;
                Object.defineProperty(x, 'plugins', {
                    enumerable: true,
                    get: () => { return _plugins; },
                    set: vl => {
                        if (vl === _plugins)
                            return true;
                        nt.defineOn(vl, 'antiblock', function(player, opts) {
                            const antiblock = nt.proxy({
                                opts: opts,
                                readyState: 'ready',
                                isEUMPPlugin: true,
                                detected: nt.func(false, 'antiblock.detected'),
                                currentWeight: nt.func(0, 'antiblock.currentWeight')
                            });
                            player.antiblock = antiblock;
                            return antiblock;
                        }, 'EUMP.plugins.');
                        _plugins = vl;
                        return true;
                    }
                });
                _EUMP = x;
                return true;
            };
            if ('EUMP' in win)
                _EUMP_set(win.EUMP);
            Object.defineProperty(win, 'EUMP', {
                enumerable: true,
                get: () => _EUMP,
                set: _EUMP_set
            });

            let _EUMPVGTRK = undefined;
            const _EUMPVGTRK_set = x => {
                if (x === _EUMPVGTRK)
                    return true;
                if (x && x.prototype) {
                    if ('generatePrerollUrls' in x.prototype)
                        nt.defineOn(x.prototype, 'generatePrerollUrls', nt.func(null, 'EUMPVGTRK.generatePrerollUrls'), 'EUMPVGTRK.prototype.', {enumerable: false});
                    if ('sendAdsEvent' in x.prototype)
                        nt.defineOn(x.prototype, 'sendAdsEvent', nt.func(null, 'EUMPVGTRK.sendAdsEvent'), 'EUMPVGTRK.prototype.', {enumerable: false});
                }
                _EUMPVGTRK = x;
                return true;
            }
            if ('EUMPVGTRK' in win)
                _EUMPVGTRK_set(win.EUMPVGTRK)
            Object.defineProperty(win, 'EUMPVGTRK', {
                enumerable: true,
                get: () => _EUMPVGTRK,
                set: _EUMPVGTRK_set
            })
        }, nullTools)
    };

    scripts['24smi.org'] = () => scriptLander(() => selectiveCookies('has_adblock'), selectiveCookies);

    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
            const isForum = location.pathname.startsWith('/forum/'),
                  remove = node => (node && node.parentNode.removeChild(node)),
                  hide = node => (node && (node.style.display = 'none'));

            selectiveCookies('viewpref');
            abortExecution(onAccess.InlineScript, 'document.querySelector', { pattern: /\(document(,window)?\);/ });

            function cleaner(log) {
                HeaderAds: {
                    // hide ads above HEADER
                    let nav = _document.querySelector('.menu-main-item');
                    while (nav && (nav.parentNode !== _de))
                        if (!nav.parentNode.querySelector('article, .container[itemtype$="Article"]'))
                            nav = nav.parentNode;
                        else break;
                    if (!nav || (nav.parentNode === _de)) {
                        log && _console.warn('Unable to locate header element');
                        break HeaderAds;
                    }
                    log && _console.log('Processing header:', nav);
                    for (let itm of nav.parentNode.children)
                        if (itm !== nav)
                            hide(itm);
                        else break;
                }

                FixNavMenu: {
                    // hide ad link from the navigation
                    let ad = _document.querySelector('.menu-main-item > a > svg');
                    if (!ad) {
                        log && _console.warn('Unable to locate menu ad item');
                        break FixNavMenu;
                    } else {
                        ad = ad.parentNode.parentNode;
                        hide(ad);
                    }
                }

                SidebarAds: {
                    // remove ads from sidebar
                    let aside = _document.querySelectorAll('[class]:not([id]) > [id]:not([class]) > :first-child + :last-child:not(.v-panel)');
                    if (!aside.length) {
                        log && _console.warn('Unable to locate sidebar');
                        break SidebarAds;
                    }
                    let post;
                    for (let side of aside) {
                        log && _console.log('Processing potential sidebar:', side);
                        for (let itm of Array.from(side.children)) {
                            post = itm.classList.contains('post');
                            if (post) continue;
                            if (itm.querySelector('iframe') || !itm.children.length)
                                remove(itm);
                            let script = itm.querySelector('script');
                            if (itm.querySelector('a[target="_blank"] > img') ||
                                script && script.src === '' && (script.type === 'text/javascript' || !script.type) &&
                                script.textContent.includes('document'))
                                hide(itm);
                        }
                    }
                }
            }

            const cln = setInterval(() => cleaner(false), 50);

            // hide banner next to logo
            if (isForum)
                createStyle('div[class]:not([id]) tr[valign="top"] > td:last-child { display: none !important }');
            // clean page
            window.addEventListener(
                'DOMContentLoaded', function() {
                    clearInterval(cln);
                    const width = () => win.innerWidth || _de.clientWidth || _document.body.clientWidth || 0,
                          height = () => win.innerHeight || _de.clientHeight || _document.body.clientHeight || 0;

                    if (isForum) {
                        // hide banner next to logo
                        //let itm = _document.querySelector('#logostrip');
                        //if (itm) hide(itm.parentNode.nextSibling);
                        // clear background in the download frame
                        if (location.pathname.startsWith('/forum/dl/')) {
                            let setBackground = node => _setAttribute(
                                node,
                                'style', (_getAttribute(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;
                    }

                    cleaner(false);

                    _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);
                            }

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

    scripts['adhands.ru'] = () => scriptLander(() => {
        try {
            let _adv;
            Object.defineProperty(win, 'adv', {
                get: () => _adv,
                set: (v) => {
                    _console.log('Blocked advert on adhands.ru.');
                    nt.defineOn(v, 'advert', '', 'adv.');
                    _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('adv.advert', '');
            }
        }
    }, nullTools);

    scripts['all-episodes.org'] = {
        now: () => {
            nt.define('perROS', 0); // blocks access when = 1
            nt.define('idm', -1); // blocks quality when >= 0
            nt.define('detdet', nt.func(null, 'detdet'));
            abortExecution(onAccess.Get, 'pokazad');
            const _apply = Reflect.apply;
            // skip check for ads
            const _toString = Function.prototype.call.bind(Function.prototype.toString);
            win.setTimeout = new Proxy(win.setTimeout, {
                apply (fun, that, args) {
                    if (args[0]) {
                        const text = _toString(args[0]);
                        if (text.includes('#bip') || text.includes('#advtss')) {
                            _console.log('Skipped check.');
                            return;
                        }
                    }
                    return _apply(fun, that, args);
                }
            });
            // wrap player to prevent some events and interactions
            let _playerInstance = win.playerInstance;
            Object.defineProperty(win, 'playerInstance', {
                get () { return _playerInstance; },
                set (vl) {
                    _console.log('player', vl);
                    vl.on = new Proxy(vl.on, {
                        apply (fun, that, args) {
                            if (/^(ad[A-Z]|before(Play|Complete))/.test(args[0]))
                                return;
                            return _apply(fun, that, args);
                        }
                    });
                    vl.getAdBlock = () => false;
                    _playerInstance = vl;
                }
            });
        }
    };

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

    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['tv.animebest.org'] = {
        now: () => {
            let _eval = win.eval;
            win.eval = new win.Proxy(win.eval, {
                apply: (evl, ths, args) => {
                    if (typeof args[0] === 'string' &&
                        args[0].includes("'VASTP'")) {
                        args[0] = args[0].replace("'VASTP'", "''");
                        win.eval = _eval;
                    }
                    return Reflect.apply(evl, ths, args);
                }
            });
        }
    };

    scripts['audioportal.su'] = {
        now: () => createStyle('#blink2 { display: none !important }'),
        dom: () => {
            let links = _document.querySelectorAll('a[onclick*="clickme("]');
            if (!links) return;
            for (let link of links)
                clickme(link);
        }
    };

    scripts['avito.ru'] = () => scriptLander(() => selectiveCookies('abp|cmtchd|crookie|is_adblock'), selectiveCookies);

    scripts['di.fm'] = () => scriptLander(() => {
        let log = false;
        // wrap global app object to catch registration of specific modules
        let _di = undefined;
        Object.defineProperty(win, 'di', {
            get: () => _di,
            set: vl => {
                if (vl === _di)
                    return;
                log && _console.log('di =', vl);
                _di = new Proxy(vl, {
                    set: (di, name, vl) => {
                        if (vl === di[name])
                            return true;
                        if (name === 'app') {
                            log && _console.log('di.app =', vl);
                            if ('module' in vl)
                                vl.module = new Proxy(vl.module, {
                                    apply: (module, that, args) => {
                                        if (/Wall|Banner|Detect|WebplayerApp\.Ads/.test(args[0])) {
                                            let name = args[0];
                                            log && _console.log('wrap', name, 'module');
                                            if (typeof args[1] === 'function')
                                                args[1] = new Proxy(args[1], {
                                                    apply: (fun, that, args) => {
                                                        if (args[0]) // module object
                                                            args[0].start = () => _console.log('Skipped start of', name);
                                                        return Reflect.apply(fun, that, args);
                                                    }
                                                });
                                        }// else log && _console.log('loading module', args[0]);
                                        if (args[0] === 'Modals') {
                                            log && _console.log('wrap', name, 'module');
                                            if (typeof args[1] === 'function')
                                                args[1] = new Proxy(args[1], {
                                                    apply: (fun, that, args) => {
                                                        if ('commands' in args[1] && 'setHandlers' in args[1].commands &&
                                                            !Object.hasOwnProperty.call(args[1].commands, 'setHandlers')) {
                                                            let _commands = args[1].commands;
                                                            _commands.setHandlers = new Proxy(_commands.setHandlers, {
                                                                apply: (fun, that, args) => {
                                                                    for (let name in args[0])
                                                                        if (name === 'modal:streaminterrupt' ||
                                                                            name === 'modal:midroll')
                                                                            args[0][name] = () => _console.log('Skipped', name, 'window');
                                                                    delete _commands.setHandlers;
                                                                    return Reflect.apply(fun, that, args);
                                                                }
                                                            });
                                                        }
                                                        return Reflect.apply(fun, that, args);
                                                    }
                                                });
                                        }
                                        return Reflect.apply(module, that, args);
                                    }
                                });
                        }
                        di[name] = vl;
                        return true;
                    }
                });
            }
        });
        // don't send errorception logs
        Object.defineProperty(win, 'onerror', {
            set: vl => log && _console.trace('Skipped global onerror callback:', vl)
        });
    });

    scripts['draug.ru'] = {
        other: 'vargr.ru',
        now: () => scriptLander(() => {
            if (location.pathname === '/pop.html')
                win.close();
            createStyle([
                '#timer_1 { display: none !important }',
                '#timer_2 { display: block !important }'
            ]);
            let _contentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
            let _get_contentWindow = Function.prototype.apply.bind(_contentWindow.get);
            _contentWindow.get = function() {
                let res = _get_contentWindow(this);
                if (res.location.href === 'about:blank')
                    res.document.write = (...args) => _console.log('Skipped iframe.write(', ...args, ')');
                return res;
            };
            Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', _contentWindow);
        }),
        dom: () => {
            let list = _querySelectorAll('div[id^="yandex_rtb_"], .adsbygoogle');
            list.forEach(node => _console.log('Removed:', node.parentNode.parentNode.removeChild(node.parentNode)));
        }
    };

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

    scripts['echo.msk.ru'] = () => scriptLander(() => {
        selectiveCookies();
        selectiveEval(evalPatternYandex, /^document\.write/, /callAdblock/);
    }, selectiveEval, selectiveCookies);

    scripts['fastpic.ru'] = () => {
        // Had to obfuscate property name to avoid triggering anti-obfuscation on greatest.deepsurf.us -_- (Exception 403012)
        nt.define(`_0x${'4955'}`, []);
    };

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

    scripts['forbes.com'] = () => {
        nt.define('Object.prototype.isAdLight', true);
        nt.define('Object.prototype.adblockPresent', false);
        nt.define('Object.prototype.isAdvertisement', false);
        nt.define('Object.prototype.articleRetracted', false);
        nt.define('Object.prototype.articleIsBlocked', false);
    };

    scripts['friends.in.ua'] = () => scriptLander(() => {
        Object.defineProperty(win, 'need_warning', {
            get: () => 0, set: () => null
        });
    });

    scripts['gamerevolution.com'] = () => {
        const _clientHeight = Object.getOwnPropertyDescriptor(_Element, 'clientHeight');
        const _apply = Reflect.apply;
        _clientHeight.get = new Proxy(_clientHeight.get, {
            apply (...args) { return _apply(...args) || 1; }
        });
        Object.defineProperty(_Element, 'clientHeight', _clientHeight);

        const toReplace = [
            'blockerDetected', 'disableDetected', 'hasAdBlocker',
            'hasBlockerFlag', 'hasDisabledAdBlocker', 'hasBlocker'
        ];
        win.Object.defineProperty = new Proxy(win.Object.defineProperty, {
            apply (fun, that, args) {
                if (toReplace.includes(args[1])) {
                    args[2] = { value: () => false };
                    console.log(args);
                }
                return _apply(fun, that, args);
            }
        });
    };

    scripts['gamersheroes.com'] = () => abortExecution(onAccess.InlineScript, 'document.createElement', {
        pattern: /window\[\w+\(\[(\d+,?\s?)+\],\s?\w+\)\]/
    });

    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(node, 'onerror') !== null)
                        node.removeAttribute('onerror');
            }
        )).observe(_document.documentElement, { childList:true, subtree: true })
    };

    scripts['gamepur.com'] = () => {
        nt.define('ga', nt.func(null, 'ga'));
        win.Object.defineProperty = new Proxy(win.Object.defineProperty, {
            apply: (fun, that, args) => {
                if (typeof args[1] === 'string' &&
                    (args[1] === 'hasAdblocker' || args[1] === 'blockerDetected'))
                    throw new ReferenceError(`${args[1]} is not defined`);
                return Reflect.apply(fun, that, args);
            }
        });
    };

    scripts['gismeteo.ru'] = {
        other: 'gismeteo.by, gismeteo.kz, gismeteo.md, gismeteo.ua',
        now: () => scriptLander(() => {
            selectiveCookies('ab_[^=]*|redirect|_gab|mkrft');
            gardener('div > script', /AdvManager/i, { observe: true, parent: 'div' });
            // obfuscated Yandex.Direct
            nt.define('Object.prototype.initYaDirect', undefined);
        }, nullTools, selectiveCookies)
    };

    scripts['gorodrabot.ru'] = {
        other: 'sdamgia.ru',
        now: () => scriptLander(() => {
            abortExecution(onAccess.Get, 'Object.prototype.initYaDirect');
            abortExecution(onAccess.Get, 'Object.prototype.initYaContext');
        }, abortExecutionModule)
    };

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

    scripts['hqq.tv'] = () => scriptLander(() => {
        // disable anti-debugging in hqq.tv player
        let isObfuscated = text => /[^a-z0-9]([a-z0-9]{1,2}\.[a-z0-9]{1,2}\(|[a-z0-9]{4}\.[a-z]\(\d+\)|[a-z0-9]\[[a-z0-9]{1,2}\]\[[a-z0-9]{1,2}\])/i.test(text);
        deepWrapAPI(root => {
            // skip obfuscated stuff and a few other calls
            let _setInterval = root.setInterval,
                _setTimeout = root.setTimeout,
                _toString = root.Function.prototype.call.bind(root.Function.prototype.toString);
            root.setInterval = (...args) => {
                let fun = args[0];
                if (fun instanceof Function) {
                    let text = _toString(fun),
                        skip = text.includes('check();') || isObfuscated(text);
                    _console.trace('setInterval', text, 'skip', skip);
                    if (skip) return -1;
                }
                return _setInterval.apply(this, args);
            };
            let wrappedST = new WeakSet();
            root.setTimeout = (...args) => {
                let fun = args[0];
                if (fun instanceof Function) {
                    let text = _toString(fun),
                        skip = fun.name === 'check' || isObfuscated(text);
                    if (!wrappedST.has(fun)) {
                        _console.trace('setTimeout', text, 'skip', skip);
                        wrappedST.add(fun);
                    }
                    if (skip) return;
                }
                return _setTimeout.apply(this, args);
            };
            // skip 'debugger' call
            let _eval = root.eval;
            root.eval = text => {
                if (typeof text === 'string' && text.includes('debugger;')) {
                    _console.trace('skip eval', text);
                    return;
                }
                _eval(text);
            };
            // Prevent RegExpt + toString trick
            let _proto = undefined;
            try {
                _proto = root.RegExp.prototype;
            } catch(ignore) {
                return;
            }
            let _RE_tS = Object.getOwnPropertyDescriptor(_proto, 'toString');
            let _RE_tSV = _RE_tS.value || _RE_tS.get();
            Object.defineProperty(_proto, 'toString', {
                enumerable: _RE_tS.enumerable,
                configurable: _RE_tS.configurable,
                get: () => _RE_tSV,
                set: val => _console.trace('Attempt to change toString for', this, 'with', _toString(val))
            });
        });
    }, deepWrapAPI);

    scripts['hideip.me'] = {
        now: () => scriptLander(() => {
            let _innerHTML = Object.getOwnPropertyDescriptor(_Element, '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, '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 undefined;
                return _dP.apply(this, args);
            };
        })
    };

    scripts['igra-prestoloff.cx'] = () => scriptLander(() => {
        /*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('');
        }, { enumerable: true});
    });

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

    scripts['inoreader.com'] = () => scriptLander(() => {
        let i = setInterval(() => {
            if ('adb_detected' in win) {
                win.adb_detected = () => adb_not_detected();
                clearInterval(i);
            }
        }, 10);
        _document.addEventListener('DOMContentLoaded', () => clearInterval(i), false);
    });

    scripts['it-actual.ru'] = () => scriptLander(() => {
        abortExecution(onAccess.All, 'blocked');
        abortExecution(onAccess.Get, 'nsg');
    }, abortExecutionModule);

    scripts['ivi.ru'] = () => {
        let _xhr_open = win.XMLHttpRequest.prototype.open;
        win.XMLHttpRequest.prototype.open = function(method, url, ...args) {
            if (typeof url === 'string')
                if (url.endsWith('/track'))
                    return;
            return _xhr_open.call(this, method, url, ...args);
        };
        let _responseText = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText');
        let _responseText_get = _responseText.get;
        _responseText.get = function() {
            if (this.__responseText__)
                return this.__responseText__;
            let res = _responseText_get.apply(this, arguments);
            let o;
            try {
                if (res)
                    o = JSON.parse(res);
            } catch(ignore) {};
            let changed = false;
            if (o && o.result) {
                if (o.result instanceof Array &&
                    'adv_network_logo_url' in o.result[0]) {
                    o.result = [];
                    changed = true;
                }
                if (o.result.show_adv) {
                    o.result.show_adv = false;
                    changed = true;
                }
            }
            if (changed) {
                _console.log('changed response >>', o);
                res = JSON.stringify(o);
            }
            this.__responseText__ = res;
            return res;
        };
        Object.defineProperty(XMLHttpRequest.prototype, 'responseText', _responseText);
    };

    scripts['kakprosto.ru'] = () => scriptLander(() => {
        selectiveCookies('yadb');
        abortExecution(onAccess.InlineScript, 'yaProxy', { pattern: /yadb/ });
        abortExecution(onAccess.InlineScript, 'yandexContextAsyncCallbacks');
        abortExecution(onAccess.InlineScript, 'adfoxAsyncParams');
        abortExecution(onAccess.InlineScript, 'adfoxBackGroundLoaded');
    }, selectiveCookies, abortExecutionModule);

    scripts['kinopoisk.ru'] = () => {
        // filter cookies
        // set no-branding body style and adjust other blocks on the page
        let style = [
            '.app__header.app__header_margin-bottom_brand, #top { margin-bottom: 20px !important }',
            '.app__branding { display: none !important}'
        ];
        if (location.hostname === 'www.kinopoisk.ru' && !location.pathname.startsWith('/games/'))
            style.push('html:not(#id), body:not(#id), .app-container { background: #d5d5d5 url(/images/noBrandBg.jpg) 50% 0 no-repeat !important }');
        createStyle(style);
        scriptLander(() => {
            selectiveCookies('cmtchd|crookie|kpunk')
            // filter JSON
            const _apply = Reflect.apply;
            win.JSON.parse = new Proxy(win.JSON.parse, {
                apply (fun, that, args) {
                    let o = _apply(fun, that, args);
                    let name = 'antiAdBlockCookieName';
                    if (name in o && typeof o[name] === 'string')
                        selectiveCookies(o[name]);
                    name = 'branding';
                    if (name in o) o[name] = {};
                    // tricks against ads in the trailer player
                    // if (location.hostname.startsWith('widgets.'))
                    if (o.page && o.page.playerParams)
                        delete o.page.playerParams.adConfig;
                    if (o.common && o.common.bunker && o.common.bunker.adv && o.common.bunker.adv.filmIdWithoutAd)
                        o.common.bunker.adv.filmIdWithoutAd.includes = () => true;
                    //_console.log('JSON.parse', o);
                    return o;
                }
            });
            // skip timeout check for blocked requests
            const _toString = Function.prototype.apply.bind(Function.prototype.toString);
            win.setTimeout = new Proxy(win.setTimeout, {
                apply(fun, that, args) {
                    if (args[1] === 100) {
                        let str = _toString(args[0]);
                        if (str.endsWith('{a()}') || str.endsWith('{n()}'))
                            return;
                    }
                    return _apply(fun, that, args);
                }
            });
            // obfuscated Yandex.Direct
            nt.define('Object.prototype.initYaDirect', undefined);
            nt.define('Object.prototype._resolveDetectResult', () => null);
            nt.define('Object.prototype.detectResultPromise', new Promise(r => r(false)));
            // catch branding and other things
            let _KP = undefined;
            Object.defineProperty(win, 'KP', {
                get: () => _KP,
                set: val => {
                    if (_KP === val)
                        return true;
                    _KP = new Proxy(val, {
                        set: (kp, name, val) => {
                            if (name === 'branding') {
                                kp[name] = new Proxy({ weborama: {} }, {
                                    get: (kp, name) => name in kp ? kp[name] : '',
                                    set: () => true
                                });
                                return true;
                            }
                            if (name === 'config')
                                val = new Proxy(val, {
                                    set: (cfg, name, val) => {
                                        if (name === 'anContextUrl')
                                            return true;
                                        if (name === 'adfoxEnabled' || name === 'hasBranding')
                                            val = false;
                                        if (name === 'adfoxVideoAdUrls')
                                            val = {flash:{}, html:{}};
                                        cfg[name] = val;
                                        return true;
                                    }
                                });
                            kp[name] = val;
                            return true;
                        }
                    });
                    _console.log('KP =', val);
                }
            });
        }, selectiveCookies, nullTools);
    };

    scripts['korrespondent.net'] = {
        now: () => scriptLander(() => {
            nt.define('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['libertycity.ru'] = () => scriptLander(() => {
        nt.define('adBlockEnabled', false);
    }, nullTools);

    scripts['liveinternet.ru'] = () => scriptLander(() => {
        selectiveEval(evalPatternYandex);
        selectiveCookies('bltsr|blcrm');
    }, selectiveEval, selectiveCookies);

    scripts['livejournal.com'] = () => scriptLander(() => {
        nt.define('Object.prototype.Adf', undefined);
    }, nullTools);

    scripts['mail.ru'] = {
        other: 'ok.ru, sportmail.ru',
        now: () => scriptLander(() => {
            selectiveCookies('act|testcookie');
            const _hostparts = location.hostname.split('.');
            const _subdomain = _hostparts.slice(-3).join('.');
            const _hostname = _hostparts.slice(-2).join('.');
            const _emailru = _subdomain === 'e.mail.ru' || _subdomain === 'octavius.mail.ru';
            const _mymailru = _subdomain === 'my.mail.ru';
            const _okru = _hostname === 'ok.ru';
            // setTimeout filter
            const pattern = /advBlock|rbParams/i;
            const _toString = Function.prototype.call.bind(Function.prototype.toString);
            const _setTimeout = Function.prototype.apply.bind(win.setTimeout);
            win.setTimeout = function setTimeout(...args) {
                let text = _toString(args[0]);
                if (pattern.test(text)) {
                    _console.trace('Skipped setTimeout:', text);
                    return;
                }
                return _setTimeout(this, args);
            };

            // Trick to prevent mail.ru from removing 3rd-party styles
            nt.define('Object.prototype.restoreVisibility', nt.func(null, 'restoreVisibility'));
            // Other Yandex Direct and other ads
            nt.define('Object.prototype.initMimic', undefined);
            nt.define('Object.prototype.hpConfig', undefined);
            nt.define('Object.prototype.direct', undefined);
            nt.define('Object.prototype.getAds', undefined);
            nt.define('rb_counter', nt.func(null, 'rb_counter'));
            if (_hostname === 'mail.ru') {
                if (_subdomain === _hostname)
                    nt.define('Object.prototype.baits', undefined);
                if (_mymailru)
                    nt.define('Object.prototype.runMimic', nt.func(null, 'runMimic'));
            }
            if (!_emailru && !_mymailru) {
                nt.define('Object.prototype.mimic', undefined);
                let xray = nt.func(undefined, 'xray');
                nt.defineOn(xray, 'radarPrefix', null, 'xray.');
                nt.defineOn(xray, 'xrayRadarUrl', undefined, 'xray.');
                nt.defineOn(xray, 'defaultParams', nt.proxy({i: undefined, p: 'media'}), 'xray.');
                nt.define('Object.prototype.xray', nt.proxy(xray));
            }
            // banners on ok.ru and counter
            nt.define('getAdvTargetParam', nt.func(null, 'getAdvTargetParam'));
            // shenanigans against ok.ru ABP detector
            if (_okru) {
                abortExecution(onAccess.Get, 'OK.hooks');
                // break detection in case detector wasn't wrapped
                abortExecution(onAccess.Set, 'Object.prototype.adBlockDetected');
            }
            // news.mail.ru and sportmail.ru
            abortExecution(onAccess.Get, 'myWidget');
            // cleanup e.mail.ru configs and mimic config on news and sport
            const _apply = Reflect.apply;
            const emptyString = (root, name) => root[name] && (root[name] = '');
            const detectMimic = /direct|240x400|SlotView/;
            win.JSON.parse = new Proxy(win.JSON.parse, {
                apply (fun, that, args) {
                    let o = _apply(fun, that, args);
                    if (o && typeof o === 'object') {
                        if (o.cfg && o.cfg.sotaFeatures) {
                            let root = o.cfg.sotaFeatures;
                            if (Array.isArray(root.adv)) root.adv = [];
                            for (let name in root)
                                if (name.startsWith('adv-') || name.startsWith('adman-'))
                                    delete root[name];
                            [ 'email_logs_to', 'smokescreen-locators'
                            ].forEach(name => emptyString(root, name));
                        }
                        if (o.userConfig) {
                            if (Array.isArray(o.userConfig.honeypot))
                                o.userConfig.honeypot.forEach((v, id, me) => (me[id] = []));
                            const cfg = o.userConfig.config;
                            if (cfg && cfg.honeypot)
                                emptyString(cfg.honeypot, 'baits');
                        }
                        if (o.body) {
                            const flags = o.body.common_purpose_flags;
                            if (flags && 'hide_ad_in_mail_web' in flags)
                                flags.hide_ad_in_mail_web = true;
                            if (o.body.show_me_ads)
                                o.body.show_me_ads = false;
                        }
                        //_console.log('JSON.parse', o);
                    }
                    if (Array.isArray(o))
                        if (o.some(t => typeof t === 'string' && detectMimic.test(t))) {
                            _console.log('Replaced', o);
                            o = [];
                        } //else _console.log('JSON.parse', o);
                    return o;
                }
            });
            // all the rest is only needed on main page and in emails
            if (_subdomain !== 'mail.ru' && !_emailru && !_okru)
                return;

            // Disable page scrambler on mail.ru to let extensions easily block ads there
            let logger = {
                apply: (target, thisArg, args) => {
                    let res = target.apply(thisArg, args);
                    _console.log(`${target._name}(`, ...args, `)\n>>`, res);
                    return res;
                }
            };

            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 = () => false;
                    locator.wrap = () => false;
                }
                try {
                    let names = [];
                    for (let name in locator)
                        if (locator[name] instanceof Function && name !== 'transform') {
                            locator[name]._name = "locator." + name;
                            locator[name] = new Proxy(locator[name], logger);
                            names.push(name);
                        }
                    _console.log(`[locator] wrapped properties: ${names.length ? names.join(', ') : '[empty]'}`);
                } catch(e) {
                    _console.log(e);
                }
                return locator;
            }

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

            {
                const missingCheck = {
                    get: (obj, name) => {
                        let res = obj[name];
                        if (!(name in obj))
                            _console.trace(`Missing "${name}" in`, obj);
                        return res;
                    }
                };
                const skipLog = (name, ret) => (...args) => (_console.log(`Skip ${name}(`, ...args, ')'), ret);
                const createSkipAllObject = (baseName, obj = {}) => new Proxy(obj, {
                    get: (o, name) => {
                        if (name in o)
                            return o[name];
                        _console.log(`Created stub for "${name}" in ${baseName}.`);
                        o[name] = skipLog(`${baseName}.${name}`);
                        return o[name];
                    },
                    set: () => true
                });
                const _apply = Reflect.apply;
                const redefiner = {
                    apply: (target, thisArg, args) => {
                        let res = undefined;
                        let warn = false;
                        let name = target._name;
                        if (name === 'mrg-smokescreen/Welter')
                            res = {
                                isWelter: () => true,
                                wrap: skipLog(`${name}.wrap`)
                            };
                        if (name === 'mrg-smokescreen/StyleSheets')
                            res = createSkipAllObject(name);
                        if (name === 'mrg-smokescreen/Honeypot')
                            res = {
                                check: (...args) => (_console.log(`${name}.check(`, ...args, ')'), new Promise(() => undefined)),
                                version: "-1"
                            }
                        if (name === 'advert/adman/adman') {
                            let features = { siteZones: {}, slots: {} };
                            [
                                'expId', 'siteId', 'mimicEndpoint', 'mimicPartnerId', 'immediateFetchTimeout', 'delayedFetchTimeout'
                            ].forEach(name => void (features[name] = null));
                            res = {};
                            res.getFeatures = skipLog('advert/adman/adman.getFeatures', features);
                            res = createSkipAllObject(name, res);
                        }
                        if (name === 'OK/banners/moneySave' || name.startsWith('OK/banners/blockDetect/'))
                            res = createSkipAllObject(name);
                        if (res) {
                            Object.defineProperty(res, Symbol.toStringTag, {
                                get: () => `Skiplog object for ${name}`
                            });
                            Object.defineProperty(res, Symbol.toPrimitive, {
                                value: function(hint) {
                                    if (hint === 'string')
                                        return Object.prototype.toString.call(this);
                                    return `[missing toPrimitive] ${name} ${hint}`;
                                }
                            });
                            res = new Proxy(res, missingCheck);
                        } else {
                            res = _apply(target, thisArg, args);
                            warn = true;
                        }
                        if (name === 'mrg-smokescreen/Utils')
                            res.extend = function(...args) {
                                let res = {
                                    enable: false,
                                    match: [],
                                    links: []
                                };
                                _console.log(`${name}.extend(`, ...args, ') >>', res );
                                return res;
                            };
                        _console[warn?'warn':'log'](name, '(',...args,')\n>>', res);
                        return res;
                    }
                };

                let advModuleNamesStartWith = /^(mrg-(context|honeypot)|adv\/)/;
                let advModuleNamesGeneric = /advert|banner|mimic|smoke/i;
                let wrapAdFuncs = {
                    apply: (target, thisArg, args) => {
                        let module = args[0];
                        if (typeof module === 'string')
                            if ((advModuleNamesStartWith.test(module) ||
                                 advModuleNamesGeneric.test(module)) &&
                                // fix for e.mail.ru in Fx56 and below, looks like Proxy is quirky there
                                !module.startsWith('patron.v2.')) {
                                let fun = args[args.length-1];
                                fun._name = module;
                                args[args.length-1] = new Proxy(fun, redefiner);
                            }
                        return _apply(target, thisArg, args);
                    }
                };
                let wrapDefine = def => {
                    if (!def)
                        return;
                    _console.log('define =', def);
                    def = new Proxy(def, wrapAdFuncs);
                    def._name = 'define';
                    return def;
                };
                let _define = wrapDefine(win.define);
                Object.defineProperty(win, 'define', {
                    get: () => _define,
                    set: x => {
                        if (_define === x)
                            return true;
                        _define = wrapDefine(x);
                        return true;
                    }
                });
            }

            let _honeyPot;
            function defineDetector(mr) {
                let __ = mr._ || {};
                let setHoneyPot = o => {
                    if (!o || o === _honeyPot) return;
                    _console.log('[honeyPot]', o);
                    _honeyPot = function() {
                        this.check = new Proxy(() => {
                            __.STUCK_IN_POT = false;
                            return false;
                        }, logger);
                        this.check._name = 'honeyPot.check';
                        this.destroy = () => null;
                    };
                };
                if ('honeyPot' in mr)
                    setHoneyPot(mr.honeyPot);
                else
                    Object.defineProperty(mr, 'honeyPot', {
                        get: () => _honeyPot,
                        set: setHoneyPot
                    });

                __ = new Proxy(__, {
                    get: (t, p) => t[p],
                    set: (t, p, v) => {
                        _console.log(`mr._.${p} =`, v);
                        t[p] = v;
                        return true;
                    }
                });
                mr._ = __;
            }

            function defineAdd(mr) {
                let _add;
                let addWrapper = {
                    apply: (tgt, that, args) => {
                        let module = args[0];
                        if (typeof module === 'string' && module.startsWith('ad')) {
                            _console.log('Skip module:', module);
                            return;
                        }
                        if (typeof module === 'object' && module.name.startsWith('ad'))
                            _console.log('Loaded module:', module);
                        return logger.apply(tgt, that, args);
                    }
                };
                let setMrAdd = v => {
                    if (!v) return;
                    v._name = 'mr.add';
                    v = new Proxy(v, addWrapper);
                    _add = v;
                };
                if ('add' in mr)
                    setMrAdd(mr.add);
                Object.defineProperty(mr, 'add', {
                    get: () => _add,
                    set: setMrAdd
                });

            }

            const _mr_wrapper = vl => {
                defineLocator(vl.mimic ? vl.mimic : vl);
                defineDetector(vl);
                defineAdd(vl);
                return vl;
            };
            if ('mr' in win) {
                _console.log('Found existing "mr" object.');
                win.mr = _mr_wrapper(win.mr);
            } else {
                let _mr = undefined;
                Object.defineProperty(win, 'mr', {
                    get: () => _mr,
                    set: vl => { _mr = vl ? _mr_wrapper(vl) : vl },
                    configurable: true
                });
                let _defineProperty = Function.prototype.apply.bind(Object.defineProperty);
                Object.defineProperty = function defineProperty(o, name, conf) {
                    if (name === 'mr' && o instanceof Window) {
                        _console.trace('Object.defineProperty(', ...arguments, ')');
                        conf.set(_mr_wrapper(conf.get()));
                    }
                    if ((name === 'honeyPot' || name === 'add') && _mr === o && conf.set)
                        return;
                    return _defineProperty(this, arguments);
                };
            }
        }, nullTools, selectiveCookies, abortExecutionModule)
    };

    scripts['oms.matchat.online'] = () => scriptLander(() => {
        let _rmpGlobals = undefined;
        Object.defineProperty(win, 'rmpGlobals', {
            get: () => _rmpGlobals,
            set: x => {
                if (x === _rmpGlobals)
                    return true;
                _rmpGlobals = new Proxy(x, {
                    get: (obj, name) => {
                        if (name === 'adBlockerDetected')
                            return false;
                        return obj[name];
                    },
                    set: (obj, name, val) => {
                        if (name === 'adBlockerDetected')
                            _console.trace('rmpGlobals.adBlockerDetected =', val)
                        else
                            obj[name] = val;
                        return true;
                    }
                });
            }
        });
    });

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

    scripts['metabomb.net'] = {
        other: 'eurogamer.net, eurogamer.cz, eurogamer.de, eurogamer.es, eurogamer.it' +
        'eurogamer.nl, eurogamer.pl, eurogamer.pt, usgamer.net',
        now: () => scriptLander(() => {
            abortExecution(onAccess.InlineScript, '_sp_');
            selectiveCookies('sp');
        }, selectiveCookies, abortExecutionModule)
    };

    scripts['n-torrents.org'] = () => scriptLander(() => {
        let _$ = undefined;
        Object.defineProperty(win, '$', {
            get: () => _$,
            set: vl => {
                _$ = vl;
                if (!vl.fn)
                    return true;
                let _videoPopup = vl.fn.videoPopup;
                Object.defineProperty(vl.fn, 'videoPopup', {
                    get: () => _videoPopup,
                    set: vl => {
                        if (vl === _videoPopup)
                            return true;
                        _videoPopup = new Proxy(vl, {
                            apply: (fun, obj, args) => {
                                let opts = args[0];
                                if (opts) {
                                    opts.adv = '';
                                    opts.duration = 0;
                                }
                                return Reflect.apply(fun, obj, args);
                            }
                        });
                        return true;
                    }
                });
                return true
            }
        });
    });

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

    scripts['newdeaf-online.net'] = {
        dom: () => {
            let adNodes = _document.querySelectorAll('.ads');
            if (!adNodes)
                return;
            let getter = x => {
                let val = x;
                return () => (_console.trace('read .ads', name, val), val);
            };
            let setter = x => _console.trace('skip write .ads', name, x);
            for (let adNode of adNodes)
                for (let name of ['innerHTML'])
                    Object.defineProperty(adNode, name, {
                        get: getter(ads[name]),
                        set: setter
                    });
        }
    };

    scripts['overclockers.ru'] = {
        dom: () => scriptLander(() => {
            let killed = () => _console.warn('Anti-Adblock killed.');
            if ('$' in win)
                win.$ = new Proxy($, {
                    apply: (tgt, that, args) => {
                        let res = tgt.apply(that, args);
                        if (res[0] && res[0] === _document.body) {
                            res.html = killed;
                            res.empty = killed;
                        }
                        return res;
                    }
                });
        })
    };
    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.pw, piratbit.top',
        dom: () => {
            const remove = node => node && node.parentNode && (_console.log('removed', node), node.parentNode.removeChild(node));
            const isAdLink = el => location.hostname === el.hostname && /^\/(\w{3}|exit|out)\/[\w=/]{20,}$/.test(el.pathname);
            // line above topic content and images in the slider in the header
            for (let el of _document.querySelectorAll('.releas-navbar div a, #page_contents a')) if (isAdLink(el))
                remove(el.closest('tr[class]:not(.top_line):not(.active), .row2[id^="post_"]') || el.closest('div[style]:not(.row1):not(.btn-group)'));
        }
    };

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

    scripts['pixelexperience.org'] = () => scriptLander(() => {
        abortExecution(onAccess.InlineScript, 'eval', 'blockadblock');
    }, abortExecutionModule);

    scripts['peka2.tv'] = () => {
        let bodyClass = 'body--branding';
        let checkNode = node => {
            for (let className of node.classList)
                if (className.includes('banner') || className === bodyClass) {
                    _removeAttribute(node, 'style');
                    node.classList.remove(className);
                    for (let attr of Array.from(node.attributes))
                        if (attr.name.startsWith('advert'))
                            _removeAttribute(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: () => {
            nt.define('ab', false);
            nt.define('tryMessage', nt.func(null, 'tryMessage'));
        }
    };

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

    scripts['rbc.ru'] = {
        other: 'autonews.ru, rbcplus.ru, sportrbc.ru',
        now: () => {
            scriptLander(() => selectiveCookies('adb_on'), selectiveCookies);
            let _RA = undefined;
            let setArgs = {
                'showBanners': true,
                'showAds': true,
                'banners.staticPath': '',
                'paywall.staticPath': '',
                'banners.dfp.config': [],
                'banners.dfp.pageTargeting': () => null,
            };
            Object.defineProperty(win, 'RA', {
                get: () => _RA,
                set: vl => {
                    _console.log('RA =', vl);
                    if ('repo' in vl) {
                        _console.log('RA.repo =', vl.repo);
                        vl.repo = new Proxy(vl.repo, {
                            set: (o, name, val) => {
                                if (name === 'banner') {
                                    _console.log(`RA.repo.${name} =`, val);
                                    val = new Proxy(val, {
                                        get: (o, name) => {
                                            let res = o[name];
                                            if (typeof o[name] === 'function') {
                                                res = () => undefined;
                                                if (name === 'getService')
                                                    res = service => {
                                                        if (service === 'dfp')
                                                            return {
                                                                getPlaces: () => undefined,
                                                                createPlaceholder: () => undefined
                                                            }
                                                        return undefined;
                                                    }
                                                    res.toString = o[name].toString.bind(o[name]);
                                            }
                                            if (name === 'isInited')
                                                res = true;
                                            _console.trace(`get RA.repo.banner.${name}`, res);
                                            return res;
                                        }
                                    });
                                }
                                o[name] = val;
                                return true;
                            }
                        });
                    } else
                        _console.log('Unable to locate RA.repo');
                    _RA = new Proxy(vl, {
                        set: (o, name, val) => {
                            if (name === 'config') {
                                _console.log('RA.config =', val);
                                if ('set' in val) {
                                    val.set = new Proxy(val.set, {
                                        apply: (set, that, args) => {
                                            let name = args[0];
                                            if (name in setArgs)
                                                args[1] = setArgs[name];
                                            if (name in setArgs || name === 'checkad')
                                                _console.log('RA.config.set(', ...args, ')');
                                            return Reflect.apply(set, that, args);
                                        }
                                    });
                                    val.set('showAds', true); // pretend ads already were shown
                                }
                            }
                            o[name] = val;
                            return true;
                        }
                    });
                }
            });
            Object.defineProperty(win, 'bannersConfig', {
                get: () => [], set: () => null
            });
            // pretend there is a paywall landing on screen already
            let pwl = _document.createElement('div');
            pwl.style.display = 'none';
            pwl.className = 'js-paywall-landing';
            _document.documentElement.appendChild(pwl);
            // detect and skip execution of one of the ABP detectors
            let _setTimeout = Function.prototype.apply.bind(win.setTimeout);
            let _toString = Function.prototype.call.bind(Function.prototype.toString);
            win.setTimeout = function setTimeout() {
                if (typeof arguments[0] === 'function') {
                    let fts = _toString(arguments[0]);
                    if (/\.length\s*>\s*0\s*&&/.test(fts) && /:hidden/.test(fts)) {
                        _console.log('Skipped setTimout(', fts, arguments[1], ')');
                        return;
                    }
                }
                return _setTimeout(this, arguments);
            };
            // hide banner placeholders
            createStyle('[data-banner-id], .banner__container, .banners__yandex__article { display: none !important }');
        },
        dom: () => {
            // hide sticky banner place at the top of the page
            for (let itm of _document.querySelectorAll('.l-sticky'))
                if (itm.querySelector('.banner__container__link'))
                    itm.style.display = 'none';
        }
    };

    scripts['rp5.ru'] = {
        other: 'rp5.by, rp5.co.uk, rp5.kz, rp5.lv, rp5.md, rp5.ua',
        now: () => {
            Object.defineProperty(win, 'sContentBottom', {
                get: () => '',
                set: () => true
            });
            // skip timeout check for blocked requests
            let _setTimeout = Function.prototype.apply.bind(win.setTimeout);
            let _toString = Function.prototype.apply.bind(Function.prototype.toString);
            win.setTimeout = function(...args) {
                let str = (typeof args[0] === 'string' ? args[0] : _toString(args[0]));
                if (str.includes('xvb')) {
                    _console.log('Blocked setTimeout for:', str);
                    return;
                }
                return _setTimeout(this, args);
            };
        },
        dom: () => {
            let node = selectNodeByTextContent('Разместить текстовое объявление', { root: _de.querySelector('#content-wrapper'), shallow: true });
            if (node)
                node.style.display = 'none';
        }
    };

    scripts['rutube.ru'] = () => scriptLander(() => {
        let _parse = JSON.parse;
        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 (res.appearance) {
                    if ('forbid_seek' in res.appearance && res.appearance.forbid_seek)
                        res.appearance.forbid_seek = false;
                    if ('forbid_timeline_preview' in res.appearance && res.appearance.forbid_timeline_preview)
                        res.appearance.forbid_timeline_preview = false;
                }
                _skip_enabled = !!res.remove_unseekable_blocks;
                //res.advert = [];
                delete res.advert;
                //for (let limit of res.limits)
                //    limit.limit = 0;
                delete res.limits;
                //res.yast = null;
                //res.yast_live_online = null;
                delete res.yast;
                delete res.yast_live_online;
                Object.defineProperty(res, 'stat', {
                    get: () => [],
                    set: () => true,
                    enumerable: true
                });
            }

            // parse video configuration
            if ('video_url' in res) {
                log = true;
                if (res.cuepoints && !_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'] = {
        other: 'simpsonsua.tv',
        now: () => scriptLander(() => {
            let _addEventListener = _Document.addEventListener;
            _document.addEventListener = function(event, callback) {
                if (event === 'DOMContentLoaded' && callback.toString().includes('show_warning'))
                    return;
                return _addEventListener.apply(this, arguments);
            };
            nt.define('need_warning', 0);
            nt.define('onYouTubeIframeAPIReady', nt.func(null, 'onYouTubeIframeAPIReady'));
        }, nullTools)
    };

    scripts['smotret-anime-365.ru'] = () => scriptLander(() => {
        deepWrapAPI(root => {
            let _call = root.Function.prototype.call;
            let _pause = _call.bind(root.Audio.prototype.pause);
            let _addEventListener = _call.bind(root.Element.prototype.addEventListener);
            let stopper = e => _pause(e.target);
            let _construct = root.Reflect.construct;
            root.Audio = new Proxy(root.Audio, {
                construct: (audio, args) => {
                    let res = _construct(audio, args);
                    _addEventListener(res, 'play', stopper, true);
                    return res;
                }
            });
            let _apply = root.Reflect.apply;
            let _tagName_get = _call.bind(Object.getOwnPropertyDescriptor(_Element, 'tagName').get);
            root.Document.prototype.createElement = new Proxy(root.Document.prototype.createElement, {
                apply: (fun, that, args) => {
                    let res = _apply(fun, that, args);
                    if (_tagName_get(res) === 'AUDIO')
                        _addEventListener(res, 'play', stopper, true);
                    return res;
                }
            });
        });
    }, deepWrapAPI);

    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, 'clientHeight'),
            _clientWidth = Object.getOwnPropertyDescriptor(_Element, '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, 'clientHeight', _clientHeight);
        Object.defineProperty(_Element, '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'] = {
        other: 'tribuna.com',
        now: () => {
            // extra functionality: shows/hides panel at the top depending on scroll direction
            createStyle([
                '.user-panel__fixed { transition: top 0.2s ease-in-out!important; }',
                '.popup__overlay.feedback { display: none!important }',
                '.user-panel-up { top: -40px!important }',
                '#branding-layout { margin-top: 100px!important }'
            ], {id: 'fixes'}, false);
            scriptLander(() => {
                yandexRavenStub();
                webpackJsonpFilter(/AdBlockDetector|addBranding|loadPlista/);
            }, nullTools, yandexRavenStub, webpackJsonpFilter);
        },
        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['video.khl.ru'] = () => {
        let props = new Set(['detectBlockers', 'detectBlockersByLink', 'detectBlockersByElement']);
        win.Object.defineProperty = new Proxy(win.Object.defineProperty, {
            apply (def, that, args) {
                if (props.has(args[1])) {
                    args[2] = {
                        key: args[1],
                        value: () => _console.log(`Skipped ${args[1]} call.`)
                    };
                    _console.log(`Replaced method ${args[1]}.`);
                }
                return Reflect.apply(def, that, args);
            }
        });
    };

    scripts['xatab-repack.net'] = {
        other: 'rg-mechanics.org',
        now: () => abortExecution(onAccess.Set, 'blocked')
    };

    scripts['xittv.net'] = () => scriptLander(() => {
        let logNames = ['setup', 'trigger', 'on', 'off', 'onReady', 'onError', 'getConfig', 'addPlugin', 'getAdBlock'];
        let skipEvents = ['adComplete', 'adSkipped', 'adBlock', 'adRequest', 'adMeta', 'adImpression', 'adError', 'adTime', 'adStarted', 'adClick'];
        let _jwplayer = undefined;
        Object.defineProperty(win, 'jwplayer', {
            get: () => _jwplayer,
            set: x => {
                _jwplayer = new Proxy(x, {
                    apply: (fun, that, args) => {
                        let res = fun.apply(that, args);
                        res = new Proxy(res, {
                            get: (obj, name) => {
                                if (logNames.includes(name) && obj[name] instanceof Function)
                                    return new Proxy(obj[name], {
                                        apply: (fun, that, args) => {
                                            if (name === 'setup') {
                                                let o = args[0];
                                                if (o)
                                                    delete o.advertising;
                                            }
                                            if (name === 'on' || name === 'trigger') {
                                                let events = typeof args[0] === 'string' ? args[0].split(" ") : null;
                                                if (events.length === 1 && skipEvents.includes(events[0]))
                                                    return res;
                                                if (events.length > 1) {
                                                    let names = [];
                                                    for (let event of events)
                                                        if (!skipEvents.includes(event))
                                                            names.push(event);
                                                    if (names.length > 0)
                                                        args[0] = names.join(" ");
                                                    else
                                                        return res;
                                                }
                                            }
                                            let subres = fun.apply(that, args);
                                            _console.trace(`jwplayer().${name}(`, ...args, `) >>`, res);
                                            return subres;
                                        }
                                    });
                                return obj[name];
                            }
                        });
                        return res;
                    }
                });
                _console.log('jwplayer =', x);
            }
        });
    });

    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['yapx.ru'] = () => scriptLander(() => {
        selectiveCookies('adblock_state|adblock_views');
        nt.define('blockAdBlock', {
            on: nt.func(nt.proxy({}, 'blockAdBlock.on', null), 'blockAdBlock.on'),
            check: nt.func(null, 'blockAdBlock.check')
        });
    }, selectiveCookies, nullTools);

    scripts['znanija.com'] = () => scriptLander(() => {
        abortExecution(onAccess.Set, 'getAdBlockType');
    }, abortExecutionModule);

    scripts['rambler.ru'] = {
        other: 'championat.com, eda.ru, gazeta.ru, lenta.ru, media.eagleplatform.com, passion.ru, quto.ru, rns.online, wmj.ru',
        now: () => {
            scriptLander(() => {
                selectiveCookies('detect_count');
                // Prevent autoplay
                if (!('EaglePlayer' in win)) {
                    let _EaglePlayer = undefined;
                    Object.defineProperty(win, 'EaglePlayer', {
                        enumerable: true,
                        get: () => _EaglePlayer,
                        set: x => {
                            if (x === _EaglePlayer)
                                return true;
                            _EaglePlayer = new Proxy(x, {
                                construct: (targ, args) => {
                                    let player = new targ(...args);
                                    if (!player.options) {
                                        _console.log('EaglePlayer: no options', EaglePlayer);
                                        return player;
                                    }
                                    Object.defineProperty(player.options, 'autoplay', {
                                        get: () => false,
                                        set: () => true
                                    });
                                    Object.defineProperty(player.options, 'scroll', {
                                        get: () => false,
                                        set: () => true
                                    });
                                    return player;
                                }
                            });
                        }
                    });
                    const _setAttribute = Function.prototype.apply.bind(_Element.setAttribute);
                    const isAutoplay = name => /^autoplay$/i.test(name);
                    _Element.setAttribute = function setAttribute(name) {
                        if (!this._stopped && isAutoplay(name)) {
                            _console.log('Prevented assigning autoplay attribute.');
                            return null;
                        }
                        return _setAttribute(this, arguments);
                    };
                } else {
                    _console.log('EaglePlayer function already exists.');
                    if (inIFrame) {
                        let _setAttribute = Function.prototype.apply.bind(_Element.setAttribute);
                        let isAutoplay = /^autoplay$/i;
                        _Element.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(this, arguments);
                        };
                    }
                }
                if (location.hostname.endsWith('.media.eagleplatform.com'))
                    return;
                // Wrapper for adv loader settings in QW50aS1BZEJsb2Nr['7t7hystz']
                const _contexts = new WeakMap();
                Object.defineProperty(Object.prototype, 'Settings', {
                    set: function(val) {
                        if (typeof val === 'object' && 'Transports' in val && 'Urls' in val)
                            val.Urls = [];
                        _contexts.set(this, val);
                    },
                    get: function() { return _contexts.get(this); }
                });
                // disable video pop-outs in articles on gazeta.ru
                if (location.hostname === 'gazeta.ru' || location.hostname.endsWith('.gazeta.ru'))
                    nt.define('creepyVideo', nt.func(null, 'creepyVideo'));
                // disable some logging
                yandexRavenStub();
                // prevent ads from loading
                abortExecution(onAccess.Get, 'g_Gazeta_AdFree');
                abortExecution(onAccess.Get, 'g_GazetaNoExchange');

                const blockPatterns = /\[[a-z]{1,4}\("0x[\da-f]+"\)\]|\.(rnet\.plus|24smi\.net|infox\.sg|lentainform\.com)\//i;
                const _toString = Function.prototype.call.bind(Function.prototype.toString);
                const _setTimeout = Function.prototype.call.bind(win.setTimeout);
                win.setTimeout = function setTimeout(f, sleep) {
                    let str = (typeof f === 'function' ? _toString(f) : ''),
                        detected = blockPatterns.test(str);
                    if (!detected && f) {
                        try {
                            str = f.toString();
                        } catch(ignore) {};
                        if (str)
                            detected = blockPatterns.test(str);
                    }
                    if (detected) {
                        _console.trace(`Stopped setTimeout for: ${str.slice(0,100)}\u2026`);
                        return null;
                    };
                    return _setTimeout(this, f, sleep);
                };
            }, nullTools, yandexRavenStub, selectiveCookies, abortExecutionModule)
        },
        dom: () => {
            // disable video pop-outs in articles on lenta.ru and rambler.ru
            let domain = location.hostname.split('.');
            if (['lenta', 'rambler'].includes(domain[domain.length - 2])) {
                const player = _document.querySelector('.js-video-box__container, .j-mini-player__video');
                player && player.removeAttribute('class');
            }
            // remove utm_ form links
            const parser = _document.createElement('a');
            _document.addEventListener('mousedown', (e) => {
                let t = e.target;
                if (!t.href)
                    t = t.closest('A');
                if (t && t.href) {
                    parser.href = t.href;
                    let remove = [];
                    let params = parser.search.slice(1).split('&').filter(name => {
                        if (name.startsWith('utm_')) {
                            remove.push(name);
                            return false;
                        }
                        return true;
                    });
                    if (remove.length)
                        _console.log('Removed parameters from link:', ...remove);
                    if (params.length)
                        parser.search = `?${params.join('&')}`;
                    else
                        parser.search = '';
                    t.href = parser.href;
                }
            }, false);
        }
    };

    scripts['reactor.cc'] = {
        other: 'joyreactor.cc, pornreactor.cc',
        now: () => {
            scriptLander(() => {
                selectiveEval();
                win.open = function(){
                    throw new ReferenceError('Redirect prevention.');
                };
                nt.define('Worker', function(){});
                nt.define('JRCH', win.CoinHive);
            }, nullTools, selectiveEval);
        },
        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 tid = undefined;
            function probe() {
                let node = selectNodeByTextContent('блокировщик рекламы');
                if (!node) return;
                while (node.parentNode.offsetHeight < 750 && node !== _document.body)
                    node = node.parentNode;
                _setAttribute(node, 'style', 'background:none!important');
                // stop observer
                if (!tid) tid = setTimeout(() => this.disconnect(), 1000);
            }
            (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@');
        }
    };

    // 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 (let domain of (scripts[name].other && scripts[name].other.split(/,\s*/) || [])) {
            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
    let domain = _document.domain;
    while (domain.includes('.')) {
        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
        // Debug function, lists all unusual window properties
        const _toString = Function.prototype.call.bind(Function.prototype.toString);
        const isNativeFunction = new RegExp (`^[^{]*\\{[\\s\\r\\n]*\\[native\\scode\\][\\s\\r\\n]*\\}$`);
        function getStrangeObjectsList() {
            _console.group('Window strangers list');
            let _skip = 'frames/self/window/webkitStorageInfo'.split('/');
            for (let n of Object.getOwnPropertyNames(win))
                try {
                    let val = win[n];
                    if (val && !_skip.includes(n) && (win !== window && val !== window[n] || win === window) &&
                        (!(val instanceof Function) || val instanceof Function && !isNativeFunction.test(_toString(val))))
                        _console.log(`${n} =`, val);
                } catch (e) {
                    _console.log(n, 'returns error on read', e);
                }
            _console.groupEnd('Window strangers list');
        }

        const _createTextNode = _Document.createTextNode.bind(_document);
        const createOptionsWindow = () => {
            const lines = {
                linked: [],
                langs: {
                    eng: 'English',
                    rus: 'Русский'
                },
                sObjBtn: {
                    eng: 'List unusual "window" properties in console',
                    rus: 'Вывести в консоль нестандартные свойства «window»'
                },
                HeaderTools: {
                    eng: 'Tools',
                    rus: 'Инструменты'
                },
                HeaderOptions: {
                    eng: 'Options',
                    rus: 'Настройки'
                },
                CoinHiveStubLabel: {
                    eng: 'Inject stub for CoinHive (miner) on all pages',
                    rus: 'Добавлять заглушку против CoinHive (майнер) на всех страницах'
                },
                AccessStatisticsLabel: {
                    eng: 'Display stubs access statistics',
                    rus: 'Выводить статистику запросов к заглушкам'
                },
                AbortExecutionStatisticsLabel: {
                    eng: 'Display abort execution statistics',
                    rus: 'Выводить статистику прерывания исполнения скриптов'
                },
                reg (el, name) {
                    this[name].link = el;
                    this.linked.push(name);
                },
                setLang (lang = 'eng') {
                    for (let name of this.linked) {
                        const el = this[name].link;
                        const label = this[name][lang];
                        el.textContent = label;
                    }
                    this.langs.link.value = lang;
                    jsf.Lang = lang;
                }
            };
            const root = _createElement('div'),
                  shadow = _attachShadow ?  _attachShadow(root, { mode: 'closed' }) : root,
                  overlay = _createElement('div'),
                  inner = _createElement('div'),
                  style = _createElement('style');

            overlay.id = 'overlay';
            overlay.appendChild(inner);
            shadow.appendChild(style);
            shadow.appendChild(overlay);

            inner.id = 'inner';
            inner.br = function appendBreakLine() {
                return this.appendChild(_createElement('br'));
            };

            // tools
            style.appendChild(
                _createTextNode([
                    `#overlay {${[
                        'position: fixed',
                        'top: 0; left: 0',
                        'bottom: 0; right: 0',
                        'background: rgba(0,0,0,0.85)',
                        'z-index: 2147483647'
                    ].join('; ')}}`,
                    `#inner {${[
                        'font-family: Helvetica, Arial, sans-serif',
                        'background: whitesmoke',
                        'font-size: 10pt',
                        'color: black',
                        'padding: 1.5em 1em 1.5em 1em',
                        'max-width: 150ch',
                        'position: absolute',
                        'top: 50%; left: 50%',
                        'transform: translate(-50%, -50%)'
                    ].join('; ')}}`,
                    `#closeOptionsButton {${[
                        'float: right',
                        'transform: translate(1em, -1.5em)',
                        'border: 0',
                        'border-radius: 0',
                        'background: none',
                        'box-shadow: none'
                    ].join('; ')}}`,
                    `#selectLang {${[
                        'float: right',
                        'transform: translate(0, -1.5em)'
                    ].join('; ')}}`,
                    'h2 { margin-top: 0 }',
                    `.optionsLabel {${[
                        'display: block',
                        'padding-left: 1.5em',
                        'text-indent: -1em'
                    ].join('; ')}}`,
                    `.optionsCheckbox {${[
                        'width: 1em',
                        'height: 1em',
                        'padding: 0',
                        'margin: 0',
                        'vertical-align: bottom',
                        'position: relative',
                        'left: -0.25em'
                    ].join('; ')}}`
                ].join('\n'))
            );

            // components
            function createCheckbox(name) {
                const checkbox = _createElement('input'),
                      label =  _createElement('label');
                checkbox.type = 'checkbox';
                checkbox.classList.add('optionsCheckbox');
                checkbox.checked = jsf[name];
                checkbox.onclick = e => {
                    jsf[name] = e.target.checked;
                    return true
                };
                label.classList.add('optionsLabel');
                label.appendChild(checkbox);
                const text = _createTextNode('');
                label.appendChild(text);
                Object.defineProperty(label, 'textContent', {
                    set (title) { text.textContent = title; }
                });
                return label;
            }

            // language & close
            const closeBtn = _createElement('button');
            closeBtn.onclick = () => _removeChild(root);
            closeBtn.textContent = '\u2715';
            closeBtn.id = 'closeOptionsButton';
            inner.appendChild(closeBtn);

            overlay.addEventListener('click', e => {
                if (e.target === overlay) {
                    _removeChild(root);
                    e.preventDefault();
                }
                e.stopPropagation();
            }, false);

            const selectLang = _createElement('select');
            for (let name in lines.langs) {
                const langOption = _createElement('option');
                langOption.value = name;
                langOption.innerText = lines.langs[name];
                selectLang.appendChild(langOption);
            }
            selectLang.id = 'selectLang';
            lines.langs.link = selectLang;
            inner.appendChild(selectLang);

            selectLang.onchange = e => {
                const lang = e.target.value;
                lines.setLang(lang);
            };

            // fill options form
            const header = _createElement('h2');
            header.textContent = 'RU AdList JS Fixes';
            inner.appendChild(header);

            lines.reg(inner.appendChild(_createElement('h3')), 'HeaderTools');

            const sObjBtn = _createElement('button');
            sObjBtn.onclick = getStrangeObjectsList;
            sObjBtn.textContent = '';
            lines.reg(inner.appendChild(sObjBtn), 'sObjBtn');

            lines.reg(inner.appendChild(_createElement('h3')), 'HeaderOptions');

            lines.reg(inner.appendChild(createCheckbox('CoinHiveStub')), 'CoinHiveStubLabel');
            lines.reg(inner.appendChild(createCheckbox('AccessStatistics')), 'AccessStatisticsLabel');
            lines.reg(inner.appendChild(createCheckbox('AbortExecutionStatistics')), 'AbortExecutionStatisticsLabel');

            lines.setLang(jsf.Lang);

            return root;
        };

        let optionsWindow;
        GM_registerMenuCommand('Options', () => _appendChild(optionsWindow = optionsWindow || createOptionsWindow()));
    }
})();