protected Map<String, String> getUserData() {
    Map<String, String> userData = new HashMap<>();
    try (ScrapClient cli = new ScrapClient()) {
      int status =
          cli.post(apiUrl + "user", getMapWithCredentials(), ScrapClient.PostType.URL_ENCODED);
      if (status != 200) {
        return userData;
      }

      userData = parseAnswer(cli.getContentAsString());

    } catch (Exception ex) {
      LOG.error("exception", ex);
    }
    return userData;
  }
  @Override
  public boolean solve(Captcha cap) {
    if (!(cap instanceof CaptchaImage)) {
      return false;
    }

    captchaCount.incrementAndGet();

    CaptchaImage captcha = (CaptchaImage) cap;
    captcha.setLastSolver(this);
    captcha.setStatus(Captcha.Status.CREATED);

    String filename = null;
    String textMimeType = captcha.getMimes()[0];
    String[] mimes = null;

    if (textMimeType != null) mimes = textMimeType.split("/");
    else textMimeType = "application/octet-stream";
    if (mimes != null && mimes.length == 2) {
      if (isValidImageExtension(mimes[1])) {
        filename = "image." + mimes[1];
      }
    } else {
      filename = "image.png";
    }

    Map<String, Object> data = getMapWithCredentials();
    data.put(
        "captchafile",
        new ByteArrayBody(captcha.getImage(), ContentType.create(textMimeType), filename));

    long started = System.currentTimeMillis();
    captcha.setStatus(Captcha.Status.SUBMITTED);
    try (ScrapClient http = new ScrapClient()) {
      int httpStatus = 0;
      int retry = 0;
      while (true) {
        httpStatus = http.post(apiUrl + "captcha", data, ScrapClient.PostType.MULTIPART);
        if (!isRetryableStatus(httpStatus)) {
          break;
        }

        if (++retry > maxRetryOnOverload) {
          break;
        }

        try {
          Long sleep = 5000l * retry;
          LOG.debug("server is overloaded, sleeping {} ms", sleep);
          Thread.sleep(sleep);
        } catch (InterruptedException ex) {
          break;
        }
      }

      if (httpStatus >= 500) {
        captcha.setError(Captcha.Error.SERVICE_OVERLOADED);
        return false;
      } else if (httpStatus == 403) {
        captcha.setError(Captcha.Error.INVALID_CREDENTIALS);
        return false;
      } else if (httpStatus == 400) {
        captcha.setError(Captcha.Error.INVALID_CREDENTIALS);
        return false;
      } else if (httpStatus != 303) {
        captcha.setError(Captcha.Error.NETWORK_ERROR);
        return false;
      }

      String location = http.getResponseHeader("location");
      if (location == null || !location.startsWith(apiUrl)) {
        LOG.warn("invalid location : {}", location);
        captcha.setError(Captcha.Error.NETWORK_ERROR);
        return false;
      }

      String captchaId = extractId(location);
      if (captchaId == null) {
        captcha.setError(Captcha.Error.NETWORK_ERROR);
        return false;
      }
      captcha.setId(captchaId);

      long timeLimit = System.currentTimeMillis() + timeoutMS;
      while (System.currentTimeMillis() < timeLimit) {

        int status = http.get(location + "?" + random.nextInt(Integer.MAX_VALUE));
        if (status == 200) {
          Map<String, String> answer = parseAnswer(http.getContentAsString());
          if (answer.get("text") != null && !answer.get("text").isEmpty()) {
            captcha.setResponse(answer.get("text"));
            captcha.setStatus(Captcha.Status.SOLVED);
            return true;
          }
        }

        try {
          Thread.sleep(POLLING_PAUSE_MS);
        } catch (InterruptedException ex) {
          break;
        }
      }

      captcha.setError(Captcha.Error.TIMEOUT);
      captcha.setStatus(Captcha.Status.ERROR);

    } catch (IOException ex) {
      LOG.error("io exception", ex);
      captcha.setError(EXCEPTION);
    } finally {
      captcha.setSolveDuration(System.currentTimeMillis() - started);
    }

    return false;
  }