/// <inheritdoc />
 /// <param name="uri">Uri Address</param>
 public static HttpResponse GetThroughCloudflare(this HttpRequest request, Uri uri,
                                                 DLog log = null,
     #if USE_CAPTCHA_SOLVER
                                                 DSolveRecaptcha reCaptchaSolver = null,
     #endif
                                                 CancellationToken cancellationToken = default(CancellationToken))
 {
     return(GetThroughCloudflare(request, uri.AbsoluteUri, log,
         #if USE_CAPTCHA_SOLVER
                                 reCaptchaSolver,
         #endif
                                 cancellationToken));
 }
        /// <summary>
        /// GET request with bypassing Cloudflare JavaScript challenge.
        /// </summary>
        /// <param name="uri">Address</param>
        /// <param name="log">Log action</param>
        /// <param name="cancellationToken">Cancel protection</param>
        /// <returns>Returns original HttpResponse</returns>
        public static HttpResponse GetThroughCloudflare(this HttpRequest request, string url,
                                                        DLog log = null,
            #if USE_CAPTCHA_SOLVER
                                                        DSolveRecaptcha reCaptchaSolver = null,
            #endif
                                                        CancellationToken cancellationToken = default(CancellationToken))
        {
            // User-Agent is required
            if (string.IsNullOrEmpty(request.UserAgent))
            {
                request.UserAgent = Http.ChromeUserAgent();
            }

            log?.Invoke("Проверяем наличие CloudFlare по адресу: " + url);

            HttpResponse response = null;

            for (int i = 0; i < MaxRetries; i++)
            {
                string retry = $". Попытка {i + 1} из {MaxRetries}.";

                #region Try catch disabled

                /*try
                 * {*/
                #endregion

                response = ManualGet(request, url);
                if (!response.IsCloudflared())
                {
                    log?.Invoke("УСПЕХ: Cloudflare не обнаружен, работаем дальше: " + url);
                    return(response);
                }

                log?.Invoke("Обхожу CloudFlare" + retry);

                if (cancellationToken != default(CancellationToken))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                response = PassClearance(request, response, url, log, cancellationToken);

                // JS Challange passed
                if (response.StatusCode == HttpStatusCode.Found)
                {
                    // Т.к. ранее использовался ручной режим - нужно обработать редирект, если он есть, чтобы вернуть отфильтрованное тело запроса

                    // TODO: иногда бывает 403 forbidden из-за cloudflare, в чем проблема?
                    if (response.HasRedirect)
                    {
                        response = request.Get(response.RedirectAddress);
                    }

                    break;
                }

                #if USE_CAPTCHA_SOLVER
                if (response.StatusCode == HttpStatusCode.Forbidden && reCaptchaSolver == null)
                {
                    continue;
                }
                #else
                if (response.StatusCode == HttpStatusCode.Forbidden) // && reCaptchaSolver == null)
                {
                    continue;
                }
                #endif

                // Not implemented status code
                if (response.StatusCode != HttpStatusCode.Forbidden)
                {
                    log?.Invoke(
                        $"CloudFlare не смог пройти JS Challange, причина не ясна. Статус код: {response.StatusCode}" +
                        retry);

                    #if USE_CAPTCHA_SOLVER
                    continue;
                    #endif
                }

                #region Captcha solver
                #if USE_CAPTCHA_SOLVER
                // IF Forbidden and has recaptcha
                //

                // ReCaptcha solve required
                string strResponse = response.ToString();
                // TODO: while for captcha solving
                const string siteKeyPattern = "data-sitekey=\"";
                if (!strResponse.Contains(siteKeyPattern))
                {
                    string error =
                        "CloudFlare не смог пройти т.к. возращен код Forbidden, но ключ сайта для рекаптчи не найден" +
                        retry;

                    log?.Invoke(error);
                    throw new CloudflareException(error);
                }

                if (reCaptchaSolver == null)
                {
                    throw new CloudflareException("Cloudflare требует решение ReCaptcha, но делегат для её решения не был предоставлен");
                }

                string siteKey = strResponse.Substring(siteKeyPattern, "\"");
                if (siteKey == string.Empty)
                {
                    throw new CloudflareException("Cloudflare требует решение ReCaptcha, но ключ сайта не был найден в HTML коде");
                }

                string submitRelativeUrl = strResponse.Substring("id=\"challenge-form\" action=\"", "\"");
                if (submitRelativeUrl == string.Empty)
                {
                    throw new CloudflareException("Cloudflare требует решение ReCaptcha, но адрес публикации формы с каптчей не найден в HTML коде");
                }

                // Build uri Form GET action
                var address   = response.Address;
                var submitUri = new Uri($"{address.Scheme}://{address.Host}:{address.Port}{submitRelativeUrl}");

                log?.Invoke("CloudFlare выдал рекаптчу, решаю...");

                // Решаем рекаптчу и отправляем форму
                var rp = new RequestParams {
                    ["g-recaptcha-response"] = reCaptchaSolver(siteKey)
                };
                response = request.Get(submitUri, rp);

                // После отправки формы кидает на главную, поэтому идем по адресу какой требуется согласно функции
                if (response.Address.AbsoluteUri != url)
                {
                    response = request.Get(url);
                }
                #endif

                #endregion
            }

            if (response == null)
            {
                throw new Exception("Ответ null");
            }

            // Clearance failed.
            if (response.IsCloudflared())
            {
                throw new CloudflareException(MaxRetries, "Превышен лимит попыток обхода Cloudflare");
            }

            log?.Invoke("CloudFlare успешно пройден");
            return(response);
        }