diff --git a/.gitignore.txt b/.gitignore.txt
new file mode 100644
index 0000000..e69de29
diff --git a/accounts/5206481_202505141a65f0291b4da44e95ed151879f9adce1449a0e39e48edf7de6fb8d871fd7c.txt b/accounts/5206481_202505141a65f0291b4da44e95ed151879f9adce1449a0e39e48edf7de6fb8d871fd7c.txt
new file mode 100644
index 0000000..b7ccd24
--- /dev/null
+++ b/accounts/5206481_202505141a65f0291b4da44e95ed151879f9adce1449a0e39e48edf7de6fb8d871fd7c.txt
@@ -0,0 +1 @@
+markarenderwrvekx@gmail.com:seFFX1LvcMp4:arturkozlov99w5f1b2@mail.com:password
\ No newline at end of file
diff --git a/chromedriver b/chromedriver
new file mode 100644
index 0000000..e69de29
diff --git a/pom.xml b/pom.xml
index 647e8d2..31a0a8f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,13 @@
${jackson.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ ${jackson.version}
+
+
org.apache.httpcomponents.client5
@@ -112,6 +119,29 @@
selenium-devtools-v135
${selenium.version}
+
+
+
+ jakarta.mail
+ jakarta.mail-api
+ 2.1.3
+
+
+ org.eclipse.angus
+ angus-mail
+ 2.0.3
+
+
+ com.alibaba
+ fastjson
+ 1.2.83
+
+
+ org.checkerframework
+ checker-qual
+ 3.42.0
+ provided
+
diff --git a/src/main/java/com/google/accountgen/AccountProcessingService.java b/src/main/java/com/google/accountgen/AccountProcessingService.java
new file mode 100644
index 0000000..fd6ee2c
--- /dev/null
+++ b/src/main/java/com/google/accountgen/AccountProcessingService.java
@@ -0,0 +1,495 @@
+package com.google.accountgen;
+
+import com.google.accountgen.config.AppProperties;
+import com.google.accountgen.cookies.CookieManager;
+import com.google.accountgen.proxy.ProxyManager;
+import com.google.accountgen.service.AccountManager;
+import com.google.accountgen.service.DarkShoppingService;
+import com.google.accountgen.service.EmailService;
+import com.google.accountgen.service.TwoCaptchaService;
+import com.google.accountgen.youtube.YouTubeMusicHandler;
+import com.google.accountgen.login.GoogleLoginHandler;
+import io.github.bonigarcia.wdm.WebDriverManager;
+import org.openqa.selenium.*;
+import org.openqa.selenium.logging.LogType;
+import org.openqa.selenium.logging.LoggingPreferences;
+import org.openqa.selenium.remote.CapabilityType;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.chrome.ChromeDriver;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.io.File;
+
+@Component
+public class AccountProcessingService {
+
+ private static final Logger logger = LoggerFactory.getLogger(AccountProcessingService.class);
+
+ private final AppProperties appProperties;
+ private final DarkShoppingService darkShoppingService;
+ private final AccountManager accountManager;
+ private final EmailService emailService;
+ private final ProxyManager proxyManager;
+ private final CookieManager cookieManager;
+ private final TwoCaptchaService twoCaptchaService;
+
+ private String chosenProxy;
+
+ public AccountProcessingService(AppProperties appProperties, ProxyManager proxyManager, TwoCaptchaService twoCaptchaService) {
+ this.appProperties = appProperties;
+ this.proxyManager = proxyManager;
+ this.cookieManager = new CookieManager(appProperties.getCookies().getDirectory());
+ this.twoCaptchaService = twoCaptchaService;
+
+ this.darkShoppingService = new DarkShoppingService(
+ appProperties.getDarkshopping().getApiKey(),
+ appProperties.getAccounts().getDirectory()
+ );
+ this.accountManager = new AccountManager(
+ appProperties.getAccounts().getDirectory(),
+ this.darkShoppingService,
+ appProperties.getDarkshopping().getProductId(),
+ appProperties.getDarkshopping().getPurchaseQuantity()
+ );
+ if (appProperties.getImap() != null && appProperties.getImap().getHost() != null && !appProperties.getImap().getHost().isEmpty() && !appProperties.getImap().getHost().equals("imap.example.com")) {
+ this.emailService = new EmailService(
+ appProperties.getImap().getHost(),
+ appProperties.getImap().getPort(),
+ appProperties.getImap().getUsername(),
+ appProperties.getImap().getPassword(),
+ appProperties.getImap().isSsl()
+ );
+ logger.info("IMAP service configured for host: {}", appProperties.getImap().getHost());
+ } else {
+ this.emailService = null;
+ logger.info("IMAP service is not configured or uses default placeholder values.");
+ }
+ WebDriverManager.chromedriver().setup();
+ if (appProperties.isUseProxy()) {
+ logger.info("Proxy usage is enabled. Initializing ProxyManager...");
+ this.proxyManager.init();
+ bindProxy6ToIp();
+ }
+ }
+
+ private WebDriver setupChromeDriver(String accountEmailForCookies) {
+ logger.info("Настройка и запуск обычного ChromeDriver");
+ Path tempProfile = null;
+ WebDriver driver = null;
+ try {
+ ChromeOptions options = new ChromeOptions();
+ options.setPageLoadStrategy(PageLoadStrategy.EAGER);
+ options.addArguments("--user-agent=" + com.google.accountgen.service.FingerprintUtils.getRandomUserAgent());
+ String windowSize = com.google.accountgen.service.FingerprintUtils.getRandomWindowSize();
+ options.addArguments("--window-size=" + windowSize);
+ options.addArguments("--lang=" + com.google.accountgen.service.FingerprintUtils.getRandomLang());
+ tempProfile = Files.createTempDirectory("chrome-profile-");
+ options.addArguments("--user-data-dir=" + tempProfile.toAbsolutePath());
+ logger.info("Используется временный профиль Chrome: {}", tempProfile);
+ LoggingPreferences logPrefs = new LoggingPreferences();
+ logPrefs.enable(LogType.BROWSER, Level.ALL);
+ logPrefs.enable(LogType.PERFORMANCE, Level.OFF);
+ options.setCapability("goog:loggingPrefs", logPrefs);
+ options.addArguments("--no-sandbox");
+ options.addArguments("--disable-dev-shm-usage");
+ options.addArguments("--disable-extensions");
+ options.addArguments("--disable-blink-features=AutomationControlled");
+ options.addArguments("--disable-notifications");
+ options.addArguments("--disable-popup-blocking");
+ options.addArguments("--log-level=3");
+ options.addArguments("--silent");
+ options.addArguments("--disable-infobars");
+
+ // Удаление "Chrome is being controlled by automated test software"
+ options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));
+ options.setExperimentalOption("useAutomationExtension", false);
+
+ if (appProperties.isUseProxy() && proxyManager.hasProxies()) {
+ chosenProxy = proxyManager.getNextProxy();
+ if (chosenProxy != null) {
+ org.openqa.selenium.Proxy httpProxy = new org.openqa.selenium.Proxy();
+ httpProxy.setHttpProxy(chosenProxy).setSslProxy(chosenProxy);
+ options.setCapability(CapabilityType.PROXY, httpProxy);
+ logger.info("HTTP(S) proxy настроен: {}", chosenProxy);
+ } else {
+ logger.error("Не удалось получить прокси от ProxyManager, хотя прокси включены и должны быть доступны.");
+ }
+ } else if (appProperties.isUseProxy()) {
+ logger.warn("Прокси включены в конфигурации, но ProxyManager не имеет доступных прокси.");
+ }
+
+ logger.info("Создаём обычный ChromeDriver");
+ driver = new ChromeDriver(options);
+
+ logger.info("Attempting to load cookies for account: {}", accountEmailForCookies);
+ driver.get("https://www.google.com");
+ randomSleep(500, 1500);
+ cookieManager.loadCookiesFromJson(driver, accountEmailForCookies);
+ logger.info("Cookies loaded (if any). Proceeding with navigator properties override.");
+
+ org.openqa.selenium.JavascriptExecutor js = (org.openqa.selenium.JavascriptExecutor) driver;
+ js.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined});");
+ js.executeScript("Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});");
+ js.executeScript("Object.defineProperty(navigator, 'plugins', {get: () => [1,2,3,4,5]});");
+ js.executeScript("Intl.DateTimeFormat.prototype.resolvedOptions = function() { return { timeZone: '" + com.google.accountgen.service.FingerprintUtils.getRandomTimezone() + "' }; };");
+ logger.info("Экземпляр ChromeDriver успешно создан.");
+ return driver;
+ } catch (Exception e) {
+ logger.error("Критическая ошибка при настройке или запуске ChromeDriver", e);
+ if (driver != null) {
+ try { driver.quit(); } catch (Exception dq) { logger.error("Error quitting driver during setup error handling", dq); }
+ }
+ if (tempProfile != null) {
+ deleteTempProfileDirectory(tempProfile);
+ }
+ throw new RuntimeException("Не удалось инициализировать ChromeDriver", e);
+ }
+ }
+
+ @EventListener(ApplicationReadyEvent.class)
+ public void processAccounts() {
+ logger.info("Starting account processing...");
+
+ Optional accountDetailsOpt = accountManager.getNextAccount();
+
+ if (accountDetailsOpt.isEmpty()) {
+ logger.warn("No accounts available to process. Exiting.");
+ return;
+ }
+
+ AccountManager.AccountDetails accountDetails = accountDetailsOpt.get();
+ logger.info("Processing account: {} with proxy: {}", accountDetails.email(), chosenProxy != null ? chosenProxy : "none");
+
+ WebDriver driver = setupChromeDriver(accountDetails.email());
+ WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+
+ // Интенсивный прогрев перед основной логикой
+ performIntensiveWarmup(driver, wait, accountDetails.email());
+
+ YouTubeMusicHandler musicHandler = new YouTubeMusicHandler(driver);
+ GoogleLoginHandler loginHandler = new GoogleLoginHandler(driver, wait, twoCaptchaService);
+
+ Path tempProfilePath = null;
+ if (driver instanceof HasCapabilities) {
+ Capabilities capabilities = ((HasCapabilities) driver).getCapabilities();
+ String chromeUserDataDirArg = capabilities.getCapability(ChromeOptions.CAPABILITY).toString();
+ if (chromeUserDataDirArg.contains("user-data-dir=")) {
+ try {
+ String pathStr = chromeUserDataDirArg.split("user-data-dir=")[1].split(",")[0].trim();
+ tempProfilePath = Path.of(pathStr);
+ } catch (Exception e) {
+ logger.warn("Could not parse temp profile path from capabilities: {}", chromeUserDataDirArg, e);
+ }
+ }
+ } else {
+ logger.warn("Driver does not implement HasCapabilities, cannot get user-data-dir.");
+ }
+
+ try {
+ loginHandler.navigateToYouTubeMusicAndHandlePopups();
+ loginHandler.clickSignInOnYouTubeMusic();
+ loginHandler.performGoogleLogin(accountDetails);
+ loginHandler.handlePostLoginPopups();
+
+ wait.until(ExpectedConditions.or(
+ ExpectedConditions.urlContains("music.youtube.com"),
+ ExpectedConditions.visibilityOfElementLocated(By.xpath("//ytmusic-popup-container//ytmusic-settings-button | //a[@aria-label='Account'] | //yt-img-shadow[@class='style-scope ytmusic-nav-bar no-transition' and @height='32']"))
+ ));
+
+ String currentUrl = driver.getCurrentUrl();
+ logger.info("Current URL after login attempt: {}", currentUrl);
+ boolean loggedInSuccessfully = false;
+
+ if (currentUrl.contains("music.youtube.com")) {
+ try {
+ wait.until(ExpectedConditions.visibilityOfElementLocated(
+ By.xpath("//ytmusic-popup-container//ytmusic-settings-button | //a[@aria-label='Account'] | //yt-img-shadow[@class='style-scope ytmusic-nav-bar no-transition' and @height='32']")
+ ));
+ logger.info("Successfully logged into YouTube Music for account: {}", accountDetails.email());
+ loggedInSuccessfully = true;
+ cookieManager.saveCookiesToJson(driver, accountDetails.email());
+ } catch (TimeoutException e) {
+ logger.warn("Could not confirm login on YouTube Music. Account icon not found. Current URL: {}", driver.getCurrentUrl());
+ loggedInSuccessfully = true;
+ cookieManager.saveCookiesToJson(driver, accountDetails.email());
+ }
+ } else if (currentUrl.contains("myaccount.google.com") || currentUrl.contains("accounts.google.com")) {
+ logger.info("Redirected to a Google account page, attempting to navigate to YouTube Music again to confirm login.");
+ driver.get("https://music.youtube.com/");
+ sleep(3000);
+ try {
+ wait.until(ExpectedConditions.visibilityOfElementLocated(
+ By.xpath("//ytmusic-popup-container//ytmusic-settings-button | //a[@aria-label='Account'] | //yt-img-shadow[@class='style-scope ytmusic-nav-bar no-transition' and @height='32']")
+ ));
+ logger.info("Successfully confirmed login on YouTube Music after redirect: {}", accountDetails.email());
+ loggedInSuccessfully = true;
+ cookieManager.saveCookiesToJson(driver, accountDetails.email());
+ } catch (TimeoutException e) {
+ logger.warn("Could not confirm login on YouTube Music after redirect. Account icon not found. Current URL: {}", driver.getCurrentUrl());
+ loggedInSuccessfully = true;
+ cookieManager.saveCookiesToJson(driver, accountDetails.email());
+ }
+ } else if (driver.findElements(By.xpath("//*[contains(text(),'Verify it\'s you') or contains(text(),'Подтвердите, что это вы')]")).size() > 0 ||
+ driver.findElements(By.xpath("//*[contains(text(),'recovery email') or contains(text(),'резервный адрес электронной почты')]")).size() > 0) {
+ logger.warn("Google requires verification for account {}. This scenario is not handled by automated login.", accountDetails.email());
+ } else {
+ logger.warn("Login may have failed for account: {}. Not on YouTube Music and no clear verification page. Current URL: {}", accountDetails.email(), currentUrl);
+ loggedInSuccessfully = true;
+ cookieManager.saveCookiesToJson(driver, accountDetails.email());
+ }
+
+ if (loggedInSuccessfully) {
+ boolean playClicked = musicHandler.playFirstTrackOrVideo();
+ if (playClicked) {
+ logger.info("Клик по первому видео/треку выполнен успешно после логина.");
+ } else {
+ logger.warn("Не удалось кликнуть по первому видео/треку после логина.");
+ }
+ accountManager.markAccountAsUsed(accountDetails);
+ logger.info("Account {} processed successfully. Proceeding to gather POT token and cookies.", accountDetails.email());
+
+ String potToken = musicHandler.getPotToken();
+ if (potToken != null && !potToken.isEmpty()) {
+ logger.info("POT Token for {}: {}", accountDetails.email(), potToken);
+ } else {
+ logger.warn("Failed to retrieve POT token for account: {}", accountDetails.email());
+ }
+
+ String cookiesNetscape = cookieManager.exportCookiesToNetscape(driver);
+ if (cookiesNetscape != null && !cookiesNetscape.isEmpty()) {
+ logger.info("Cookies (Netscape format) for {}: \n{}", accountDetails.email(), cookiesNetscape);
+ } else {
+ logger.warn("Failed to export cookies for account: {}", accountDetails.email());
+ }
+
+ logger.info("Waiting for 10 seconds to observe the page (demonstration)...");
+ sleep(10000);
+
+ } else {
+ logger.error("Login failed or unconfirmed for account {}. Marking as potentially problematic but not used.", accountDetails.email());
+ }
+
+ } catch (NoSuchWindowException e) {
+ logger.error("Browser window was closed unexpectedly during processing for account {}. Proxy: {}. Error: {}", accountDetails.email(), chosenProxy, e.getMessage());
+ makeGlobalScreenshot(driver, "error_window_closed_" + accountDetails.email().split("@")[0]);
+ } catch (TimeoutException e) {
+ logger.error("TimeoutException during processing for account {}. Proxy: {}. Current URL: {}. Error: {}", accountDetails.email(), chosenProxy, driver != null ? driver.getCurrentUrl() : "N/A", e.getMessage());
+ makeGlobalScreenshot(driver, "error_timeout_" + accountDetails.email().split("@")[0]);
+ } catch (WebDriverException e) {
+ logger.error("WebDriverException during processing for account {}. Proxy: {}. Error: {}", accountDetails.email(), chosenProxy, e.getMessage(), e);
+ makeGlobalScreenshot(driver, "error_webdriver_" + accountDetails.email().split("@")[0]);
+ } catch (Exception e) {
+ logger.error("An unexpected error occurred during login or captcha solving for account {}. Proxy: {}. Error: {}", accountDetails.email(), chosenProxy, e.getMessage(), e);
+ makeGlobalScreenshot(driver, "error_unexpected_login_" + accountDetails.email().split("@")[0]);
+ } finally {
+ if (driver != null) {
+ try {
+ logger.info("Quitting WebDriver for account: {}", accountDetails.email());
+ driver.quit();
+ } catch (Exception e) {
+ logger.error("Error quitting WebDriver for account: {}: {}", accountDetails.email(), e.getMessage());
+ }
+ }
+ if (tempProfilePath != null) {
+ deleteTempProfileDirectory(tempProfilePath);
+ }
+ logger.info("Finished processing for account: {} with proxy: {}", accountDetails.email(), chosenProxy != null ? chosenProxy : "none");
+ chosenProxy = null;
+ }
+ }
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Sleep interrupted");
+ }
+ }
+
+ private void bindProxy6ToIp() {
+ if (appProperties.getProxy6() == null || appProperties.getProxy6().getApiKey() == null || appProperties.getProxy6().getApiKey().isEmpty()) {
+ logger.warn("Proxy6 API key is not configured. Skipping IP binding.");
+ return;
+ }
+ String apiKey = appProperties.getProxy6().getApiKey();
+ String ip = "95.26.162.41";
+ String urlString = String.format("https://px6.link/api/%s/ipauth?ip=%s", apiKey, ip);
+
+ try {
+ URL url = new URL(urlString);
+ HttpURLConnection con = (HttpURLConnection) url.openConnection();
+ con.setRequestMethod("GET");
+ con.setConnectTimeout(15000);
+ con.setReadTimeout(15000);
+
+ int status = con.getResponseCode();
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader((status == 200) ? con.getInputStream() : con.getErrorStream(),
+ StandardCharsets.UTF_8));
+ String inputLine;
+ StringBuilder content = new StringBuilder();
+ while ((inputLine = in.readLine()) != null) {
+ content.append(inputLine);
+ }
+ in.close();
+ con.disconnect();
+
+ if (status != 200 || !content.toString().contains("\"status\":\"yes\"")) {
+ logger.warn("Ошибка привязки IP к proxy6: " + content);
+ } else {
+ logger.info("Прокси успешно привязаны к IP через proxy6.net!");
+ }
+ } catch (Exception e) {
+ logger.error("Ошибка при привязке proxy6 к IP", e);
+ }
+ }
+
+ private void makeGlobalScreenshot(WebDriver driver, String filename) {
+ if (driver instanceof TakesScreenshot) {
+ try {
+ File screenshotFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
+ Path destinationDir = Path.of("screenshots", "global");
+ Files.createDirectories(destinationDir);
+ Path screenshotPath = destinationDir.resolve(filename + ".png");
+ Files.copy(screenshotFile.toPath(), screenshotPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ logger.info("Global screenshot taken: {}", screenshotPath);
+ } catch (IOException | WebDriverException ex) {
+ logger.error("Failed to take global screenshot: {}", ex.getMessage());
+ }
+ } else {
+ logger.warn("Driver does not implement TakesScreenshot, cannot take global screenshot.");
+ }
+ }
+
+ private void deleteTempProfileDirectory(Path profilePath) {
+ if (profilePath != null) {
+ try {
+ logger.info("Attempting to delete temporary profile directory: {}", profilePath);
+ try (java.util.stream.Stream walk = Files.walk(profilePath)) {
+ walk.sorted(java.util.Comparator.reverseOrder())
+ .map(Path::toFile)
+ .peek(f -> logger.debug("Deleting: {}", f.getAbsolutePath()))
+ .forEach(File::delete);
+ }
+ logger.info("Successfully deleted temporary profile directory: {}", profilePath);
+ } catch (IOException e) {
+ logger.error("Failed to delete temporary profile directory {}: {}", profilePath, e.getMessage());
+ }
+ }
+ }
+
+ private void randomSleep(int minMs, int maxMs) {
+ try {
+ Thread.sleep(minMs + new java.util.Random().nextInt(maxMs - minMs + 1));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private void performIntensiveWarmup(WebDriver driver, WebDriverWait wait, String accountEmail) {
+ logger.info("Starting intensive warm-up for account: {}", accountEmail);
+ try {
+ // 1. Google Search & Interaction
+ if (!driver.getCurrentUrl().contains("google.com")) {
+ driver.get("https://www.google.com/");
+ randomSleep(1500, 2500);
+ }
+ // Принять куки Google, если есть
+ try {
+ WebElement acceptGoogleCookiesButton = new WebDriverWait(driver, Duration.ofSeconds(7))
+ .until(ExpectedConditions.elementToBeClickable(By.xpath("//button[.//span[normalize-space()='Принять все' or normalize-space()='Accept all']]")));
+ acceptGoogleCookiesButton.click();
+ logger.info("Accepted Google cookies consent (e.g., 'Принять все') during intensive warm-up.");
+ randomSleep(500, 1500);
+ } catch (TimeoutException e) {
+ logger.info("Google cookies consent button (e.g., 'Принять все') not found or not clickable during warm-up.");
+ }
+
+ String[] searchQueries = {"latest tech news", "popular music videos", "weather forecast", "best cooking recipes"};
+ String searchQuery = searchQueries[new java.util.Random().nextInt(searchQueries.length)];
+
+ WebElement searchBox = wait.until(ExpectedConditions.visibilityOfElementLocated(By.name("q")));
+ typeWithDelayActions(driver, searchBox, searchQuery, 80, 180);
+ searchBox.sendKeys(Keys.ENTER);
+ logger.info("Warm-up: Searched Google for '{}'", searchQuery);
+ randomSleep(2000, 4000);
+
+ for (int i = 0; i < 2; i++) { // 2 скролла
+ ((JavascriptExecutor) driver).executeScript("window.scrollBy(0, arguments[0]);", 300 + new java.util.Random().nextInt(400));
+ randomSleep(1000, 2000);
+ }
+
+ // 2. Visit YouTube & Interact
+ driver.get("https://www.youtube.com/");
+ logger.info("Warm-up: Navigated to YouTube.com");
+ randomSleep(3000, 5000);
+
+ // Принять куки YouTube, если есть (могут отличаться от Google)
+ try {
+ WebElement acceptYouTubeCookiesButton = new WebDriverWait(driver, Duration.ofSeconds(7))
+ .until(ExpectedConditions.elementToBeClickable(
+ By.xpath("//button[.//yt-formatted-string[contains(text(),'Accept all') or contains(text(),'Принять все')]] | //tp-yt-paper-button[contains(@aria-label,'Accept') or contains(@aria-label,'Принять')]")
+ ));
+ acceptYouTubeCookiesButton.click();
+ logger.info("Accepted YouTube cookies during intensive warm-up.");
+ randomSleep(1000, 2000);
+ } catch (TimeoutException e) {
+ logger.info("No YouTube cookies consent button found during warm-up.");
+ }
+
+ for (int i = 0; i < 2; i++) { // 2 скролла на YouTube
+ ((JavascriptExecutor) driver).executeScript("window.scrollBy(0, arguments[0]);", 400 + new java.util.Random().nextInt(500));
+ randomSleep(1500, 2500);
+ }
+ logger.info("Warm-up: Scrolled on YouTube homepage.");
+
+ // Опционально: кликнуть на случайное видео (требует аккуратных селекторов)
+ // WebElement firstVideo = driver.findElement(By.cssSelector("ytd-rich-item-renderer #video-title-link"));
+ // if (firstVideo != null && firstVideo.isDisplayed()) { firstVideo.click(); randomSleep(5000, 8000); driver.navigate().back(); }
+
+ // 3. Visit another common site (e.g., Wikipedia - lightweight)
+ // driver.get("https://www.wikipedia.org/");
+ // logger.info("Warm-up: Navigated to Wikipedia.org");
+ // randomSleep(2000, 3000);
+ // ((JavascriptExecutor) driver).executeScript("window.scrollBy(0, arguments[0]);", 200 + new java.util.Random().nextInt(200));
+ // randomSleep(1000, 1500);
+
+ logger.info("Intensive warm-up finished for account: {}. Total duration approx ~1-2 mins.", accountEmail);
+ // Общая длительность этого прогрева будет зависеть от задержек. Можно добавить больше шагов для 3+ минут.
+
+ } catch (Exception e) {
+ logger.warn("Error during intensive warm-up for account {}: {}. Proceeding with login attempt.", accountEmail, e.getMessage());
+ makeGlobalScreenshot(driver, "intensive_warmup_error_" + accountEmail.replace("@", "_"));
+ }
+ }
+
+ // Вспомогательный метод для посимвольного ввода, если он нужен здесь
+ // Если такой уже есть в GoogleLoginHandler, лучше его переиспользовать или вынести в утилиты
+ private void typeWithDelayActions(WebDriver driver, WebElement element, String text, int minDelay, int maxDelay) {
+ org.openqa.selenium.interactions.Actions actions = new org.openqa.selenium.interactions.Actions(driver);
+ for (char c : text.toCharArray()) {
+ actions.sendKeys(element, String.valueOf(c)).perform();
+ randomSleep(minDelay, maxDelay);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/Task.java b/src/main/java/com/google/accountgen/Task.java
index 0702ce3..b364f11 100644
--- a/src/main/java/com/google/accountgen/Task.java
+++ b/src/main/java/com/google/accountgen/Task.java
@@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.Scheduled;
+// import org.springframework.scheduling.annotation.Scheduled; // Закомментировано
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
@@ -62,7 +62,7 @@ public class Task
- @Scheduled(fixedRate = 24 * 60 * 60 * 1000)
+ // @Scheduled(fixedRate = 24 * 60 * 60 * 1000) // Закомментировано
@Async
public void generateProfileMeta()
{
diff --git a/src/main/java/com/google/accountgen/account/GoogleAccountGeneratorImpl.java b/src/main/java/com/google/accountgen/account/GoogleAccountGeneratorImpl.java
index c4a0e26..d9c0e73 100644
--- a/src/main/java/com/google/accountgen/account/GoogleAccountGeneratorImpl.java
+++ b/src/main/java/com/google/accountgen/account/GoogleAccountGeneratorImpl.java
@@ -16,7 +16,6 @@ import org.openqa.selenium.By;
import org.openqa.selenium.PageLoadStrategy;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
@@ -29,6 +28,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
+import org.openqa.selenium.chrome.ChromeDriver;
import java.io.File;
import java.io.IOException;
@@ -38,6 +38,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;
+import java.util.logging.Level;
/**
* Реализация генератора аккаунтов Google
@@ -438,105 +439,73 @@ public class GoogleAccountGeneratorImpl implements GoogleAccountGenerator
}
/**
- * Настраивает и создает экземпляр ChromeDriver
+ * Настраивает и создает экземпляр обычного ChromeDriver
*
* @return настроенный веб-драйвер
*/
private WebDriver setupChromeDriver()
{
- logger.info("Настройка и запуск ChromeDriver");
-
+ logger.info("Настройка и запуск обычного ChromeDriver");
try
{
- // Явно вызываем WebDriverManager для установки/обновления ChromeDriver
- logger.info("Вызов WebDriverManager.chromedriver().setup()...");
- WebDriverManager.chromedriver().setup();
- logger.info("WebDriverManager.chromedriver().setup() завершен.");
-
- // Путь к ChromeDriver уже должен быть установлен в Main.java - оставляем комментарий, но полагаемся на WDM
- // logger.info("Используем путь к ChromeDriver, установленный в Main.java");
-
ChromeOptions options = new ChromeOptions();
- // Если Chrome установлен НЕ в стандартное место, раскомментируйте и укажите путь:
- // options.setBinary("C:\\Путь\\К\\Вашему\\chrome.exe");
-
- // Используем EAGER стратегию загрузки страницы, чтобы не ждать полной загрузки ресурсов
options.setPageLoadStrategy(PageLoadStrategy.EAGER);
-
- // Добавляем User-Agent
- options.addArguments(
- "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"); // Пример современного User-Agent
-
- // Создаём уникальный временный профиль для Chrome
+ // Уникальный fingerprint
+ options.addArguments("--user-agent=" + com.google.accountgen.service.FingerprintUtils.getRandomUserAgent());
+ String windowSize = com.google.accountgen.service.FingerprintUtils.getRandomWindowSize();
+ options.addArguments("--window-size=" + windowSize);
+ options.addArguments("--lang=" + com.google.accountgen.service.FingerprintUtils.getRandomLang());
Path tempProfile = Files.createTempDirectory("chrome-profile-");
options.addArguments("--user-data-dir=" + tempProfile.toAbsolutePath());
logger.info("Используется временный профиль Chrome: {}", tempProfile);
-
- // Настройка логирования браузера для захвата консольных логов
LoggingPreferences logPrefs = new LoggingPreferences();
- logPrefs.enable(LogType.BROWSER, java.util.logging.Level.ALL); // Захват всех консольных логов
- // Отключаем Performance лог, т.к. он был закомментирован
- logPrefs.enable(LogType.PERFORMANCE, java.util.logging.Level.OFF);
+ logPrefs.enable(LogType.BROWSER, Level.ALL);
+ logPrefs.enable(LogType.PERFORMANCE, Level.OFF);
options.setCapability("goog:loggingPrefs", logPrefs);
- logger.info("Настроено логирование браузера (консоль).");
-
- // --- ОТКЛЮЧАЕМ HEADLESS ---
- logger.info("Headless режим ВЫКЛЮЧЕН.");
- // options.addArguments("--headless=new"); // Закомментировано
- // options.addArguments("--disable-gpu"); // Закомментировано (обычно не нужен без headless)
- // options.addArguments("--window-size=1920,1080"); // Размер окна можно установить ниже (или в других опциях)
- // --- КОНЕЦ ОТКЛЮЧЕНИЯ HEADLESS ---
-
- // Оставляем общие опции
- options.addArguments("--no-sandbox"); // Может быть полезно и не в headless
- options.addArguments("--disable-dev-shm-usage"); // Может быть полезно и не в headless
- options.addArguments("--disable-extensions"); // Явно отключаем расширения
+ options.addArguments("--no-sandbox");
+ options.addArguments("--disable-dev-shm-usage");
+ options.addArguments("--disable-extensions");
options.addArguments("--disable-blink-features=AutomationControlled");
options.addArguments("--disable-notifications");
options.addArguments("--disable-popup-blocking");
options.addArguments("--log-level=3");
options.addArguments("--silent");
-
- // --- ДОБАВЛЕННЫЕ ОПЦИИ ДЛЯ МАСКИРОВКИ ---
- options.addArguments("--disable-infobars"); // Убрать инфо-панель (дополнительно к AutomationControlled)
- options.addArguments("--window-size=1366,768"); // Более стандартный размер окна
- options.addArguments("--lang=tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7"); // Установить турецкий язык + запасные
- options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation")); // Еще один способ скрыть автоматизацию
- options.setExperimentalOption("useAutomationExtension", false); // Отключить расширение автоматизации
- // --- КОНЕЦ ДОБАВЛЕННЫХ ОПЦИЙ ---
-
- // Временно отключаем добавление расширения VPN
- logger.info("Загрузка VPN расширения временно отключена.");
-
- // Proxy через Selenium Proxy capability
+ options.addArguments("--disable-infobars");
if (useProxy && proxyManager.hasProxies())
{
chosenProxy = proxyManager.getNextProxy();
- if (chosenProxy == null) {
- logger.error("Не удалось получить ни одного подходящего прокси с proxy6.net. Проверьте фильтры IP/тип или наличие активных прокси.");
- throw new RuntimeException("Нет доступных прокси для использования. Проверьте настройки proxy6.net и фильтры в ProxyManager.");
+ if (chosenProxy != null)
+ {
+ org.openqa.selenium.Proxy httpProxy = new org.openqa.selenium.Proxy();
+ httpProxy.setHttpProxy(chosenProxy).setSslProxy(chosenProxy);
+ options.setCapability(CapabilityType.PROXY, httpProxy);
+ logger.info("HTTP(S) proxy настроен: {}", chosenProxy);
+ }
+ else
+ {
+ logger.error("Не удалось получить прокси от ProxyManager, хотя прокси включены и должны быть доступны.");
}
- // Используем только host:port без логина и пароля
- String finalProxy = "--proxy-server=http://" + chosenProxy;
- options.addArguments(finalProxy);
- org.openqa.selenium.Proxy httpProxy = new org.openqa.selenium.Proxy();
- httpProxy.setHttpProxy(chosenProxy).setSslProxy(chosenProxy);
- options.setCapability(CapabilityType.PROXY, httpProxy);
- logger.info("HTTP(S) proxy настроен: http://{}", chosenProxy);
}
-
-
- // Создаем и возвращаем драйвер
- logger.info("Попытка создания экземпляра ChromeDriver с опциями...");
+ else if (useProxy)
+ {
+ logger.warn("Прокси включены в конфигурации, но ProxyManager не имеет доступных прокси.");
+ }
+ logger.info("Создаём обычный ChromeDriver");
WebDriver driver = new ChromeDriver(options);
+ // --- Антидетект JS ---
+ org.openqa.selenium.JavascriptExecutor js = (org.openqa.selenium.JavascriptExecutor) driver;
+ js.executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined});");
+ js.executeScript("Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});");
+ js.executeScript("Object.defineProperty(navigator, 'plugins', {get: () => [1,2,3,4,5]});");
+ js.executeScript("Intl.DateTimeFormat.prototype.resolvedOptions = function() { return { timeZone: '" + com.google.accountgen.service.FingerprintUtils.getRandomTimezone() + "' }; };");
+ // --- END ---
logger.info("Экземпляр ChromeDriver успешно создан.");
-
return driver;
}
catch (Exception e)
{
logger.error("Критическая ошибка при настройке или запуске ChromeDriver", e);
- throw new RuntimeException("Не удалось инициализировать ChromeDriver", e); // Пробрасываем исключение дальше
+ throw new RuntimeException("Не удалось инициализировать ChromeDriver", e);
}
}
diff --git a/src/main/java/com/google/accountgen/config/AppProperties.java b/src/main/java/com/google/accountgen/config/AppProperties.java
new file mode 100644
index 0000000..35d7f96
--- /dev/null
+++ b/src/main/java/com/google/accountgen/config/AppProperties.java
@@ -0,0 +1,85 @@
+package com.google.accountgen.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties
+@Data // Lombok dlya avtomaticheskoy generacii getterov, setterov, toString, etc.
+public class AppProperties {
+
+ private DarkShoppingProps darkshopping = new DarkShoppingProps();
+ private AccountsProps accounts = new AccountsProps();
+ private ImapProps imap = new ImapProps();
+ private Proxy6Props proxy6 = new Proxy6Props();
+ private Cookies cookies = new Cookies();
+
+ // Существующие свойства из вашего application.yml (если нужны в виде бина)
+ private String smsActivateApiKey;
+ private String captchaApiKey;
+ private String captchaServiceType;
+ private int retryCount;
+ private boolean useProxy;
+ private String proxyLogin;
+ private String proxyPass;
+ private boolean useVpn;
+ private String vpnExtensionPath;
+ private boolean useGoodbyedpi;
+ private String goodbyedpiPath;
+ private String smsService;
+ private int accountCount;
+ private long delayBetweenAttempts;
+ private int defaultCountry;
+
+
+ @Data
+ public static class DarkShoppingProps {
+ private String apiKey;
+ private String productId;
+ private int purchaseQuantity;
+ }
+
+ @Data
+ public static class AccountsProps {
+ private String directory;
+ }
+
+ @Data
+ public static class ImapProps {
+ private String host;
+ private int port;
+ private String username;
+ private String password;
+ private boolean ssl;
+ }
+
+ @Data
+ public static class Proxy6Props {
+ private String apiKey;
+ }
+
+ public void setAccounts(AccountsProps accounts) {
+ this.accounts = accounts;
+ }
+
+ public Cookies getCookies() {
+ return cookies;
+ }
+
+ public void setCookies(Cookies cookies) {
+ this.cookies = cookies;
+ }
+
+ public static class Cookies {
+ private String directory = "accounts_cookies";
+
+ public String getDirectory() {
+ return directory;
+ }
+
+ public void setDirectory(String directory) {
+ this.directory = directory;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/cookies/CookieManager.java b/src/main/java/com/google/accountgen/cookies/CookieManager.java
index 8f2805f..269e773 100644
--- a/src/main/java/com/google/accountgen/cookies/CookieManager.java
+++ b/src/main/java/com/google/accountgen/cookies/CookieManager.java
@@ -5,8 +5,19 @@ import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Date; // Для преобразования expiry
import java.util.Objects;
import java.util.Set;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; // Для дат, если понадобится
/**
* Класс для управления cookies браузера
@@ -14,6 +25,26 @@ import java.util.Set;
public class CookieManager
{
private static final Logger logger = LoggerFactory.getLogger(CookieManager.class);
+ private final ObjectMapper objectMapper;
+ private final String cookiesStorageDir; // Новое поле
+
+ public CookieManager(String cookiesStorageDir) {
+ this.cookiesStorageDir = cookiesStorageDir;
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.registerModule(new JavaTimeModule()); // На случай, если Selenium Cookie использует типы Java Time
+ this.objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); // Для более читаемых дат, если применимо
+ // Гарантируем, что директория существует
+ try {
+ Files.createDirectories(Paths.get(this.cookiesStorageDir));
+ } catch (IOException e) {
+ logger.error("Could not create cookies storage directory: {}", this.cookiesStorageDir, e);
+ }
+ }
+
+ public CookieManager() { // Конструктор по умолчанию для обратной совместимости, если используется без директории
+ this("cookies_storage"); // Директория по умолчанию
+ logger.warn("CookieManager initialized with default cookies_storage directory. Consider providing a configured path.");
+ }
/**
* Экспортирует cookies в формате Netscape
@@ -58,4 +89,71 @@ public class CookieManager
cookie.getValue());
}
+ public void saveCookiesToJson(WebDriver driver, String accountIdentifier) {
+ Path cookieFile = Paths.get(cookiesStorageDir, accountIdentifier + "_cookies.json");
+ Set cookies = driver.manage().getCookies();
+ if (cookies.isEmpty()) {
+ logger.info("No cookies to save for {}", accountIdentifier);
+ return;
+ }
+ try {
+ Files.createDirectories(cookieFile.getParent()); // Убедимся, что директория существует
+ String jsonCookies = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(cookies);
+ Files.writeString(cookieFile, jsonCookies, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ logger.info("Successfully saved {} cookies for {} to {}", cookies.size(), accountIdentifier, cookieFile);
+ } catch (IOException e) {
+ logger.error("Failed to save cookies for {} to {}: {}", accountIdentifier, cookieFile, e.getMessage(), e);
+ }
+ }
+
+ public void loadCookiesFromJson(WebDriver driver, String accountIdentifier) {
+ Path cookieFile = Paths.get(cookiesStorageDir, accountIdentifier + "_cookies.json");
+ if (!Files.exists(cookieFile)) {
+ logger.info("No cookie file found for {} at {}. Skipping cookie loading.", accountIdentifier, cookieFile);
+ return;
+ }
+
+ try {
+ String jsonCookies = Files.readString(cookieFile);
+ Set cookies = objectMapper.readValue(jsonCookies, new TypeReference>() {});
+
+ // Перед загрузкой cookies важно находиться на домене, для которого они предназначены,
+ // или на родительском домене. Selenium может не загрузить cookie, если домен не совпадает.
+ // Обычно безопасно перейти на главный домен Google перед загрузкой.
+ // driver.get("https://.google.com"); // или driver.get("https://youtube.com");
+ // Эта навигация должна быть сделана ДО вызова loadCookiesFromJson, в вызывающем коде.
+
+ int loadedCount = 0;
+ for (Cookie cookie : cookies) {
+ // Selenium Cookie класс не всегда хорошо десериализуется из чистого JSON,
+ // если есть специфичные для Selenium поля или конструкторы.
+ // Попробуем создать новый объект Cookie, чтобы убедиться в корректности полей.
+ // Особенно важен expiry date, который должен быть объектом Date, а не long.
+ Cookie.Builder cookieBuilder = new Cookie.Builder(cookie.getName(), cookie.getValue())
+ .domain(cookie.getDomain())
+ .path(cookie.getPath())
+ .isSecure(cookie.isSecure())
+ .isHttpOnly(cookie.isHttpOnly())
+ .sameSite(cookie.getSameSite());
+
+ if (cookie.getExpiry() != null) {
+ cookieBuilder.expiresOn(cookie.getExpiry());
+ }
+
+ try {
+ driver.manage().addCookie(cookieBuilder.build());
+ loadedCount++;
+ } catch (Exception e) {
+ logger.warn("Failed to load cookie: {} for domain {}. Error: {}. Skipping this cookie.",
+ cookie.getName(), cookie.getDomain(), e.getMessage());
+ }
+ }
+ logger.info("Successfully loaded {} cookies for {} from {}", loadedCount, accountIdentifier, cookieFile);
+
+ } catch (IOException e) {
+ logger.error("Failed to load cookies for {} from {}: {}", accountIdentifier, cookieFile, e.getMessage(), e);
+ } catch (Exception e) {
+ logger.error("Error parsing cookies for {} from {}: {}", accountIdentifier, cookieFile, e.getMessage(), e);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/login/GoogleLoginHandler.java b/src/main/java/com/google/accountgen/login/GoogleLoginHandler.java
new file mode 100644
index 0000000..7b70c99
--- /dev/null
+++ b/src/main/java/com/google/accountgen/login/GoogleLoginHandler.java
@@ -0,0 +1,388 @@
+package com.google.accountgen.login;
+
+import com.google.accountgen.service.AccountManager;
+import com.google.accountgen.service.HumanEmulationUtils;
+import com.google.accountgen.service.TwoCaptchaService;
+import org.openqa.selenium.*;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.time.Duration;
+import java.util.List;
+import java.util.Random;
+
+public class GoogleLoginHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(GoogleLoginHandler.class);
+ private final WebDriver driver;
+ private final WebDriverWait wait;
+ private final WebDriverWait shortWait;
+ private final Random random = new Random();
+ private final TwoCaptchaService twoCaptchaService;
+
+ public GoogleLoginHandler(WebDriver driver, WebDriverWait wait, TwoCaptchaService twoCaptchaService) {
+ this.driver = driver;
+ this.wait = wait;
+ this.shortWait = new WebDriverWait(driver, Duration.ofSeconds(7)); // Короткое ожидание для необязательных элементов
+ this.twoCaptchaService = twoCaptchaService;
+ }
+
+ private void warmUpOnYouTubeMusicPage() {
+ logger.info("Warming up on YouTube Music page...");
+ try {
+ // 1. Случайная прокрутка
+ int scrolls = random.nextInt(2) + 1; // 1 to 2 scrolls
+ for (int i = 0; i < scrolls; i++) {
+ randomScroll();
+ randomSleep(800, 1500);
+ }
+
+ // 2. Попытка найти и кликнуть на "Explore" или "Обзор" (если есть и не требует логина)
+ // Эти селекторы могут потребовать уточнения
+ try {
+ WebElement exploreLink = shortWait.until(ExpectedConditions.elementToBeClickable(
+ By.xpath("//ytmusic-pivot-bar-item-renderer[.//yt-formatted-string[contains(., 'Explore') or contains(., 'Обзор')]]/a | //a[contains(@href, '/explore')]")
+ ));
+ if (exploreLink.isDisplayed() && exploreLink.isEnabled()) {
+ logger.info("Found 'Explore' link, clicking for warm-up.");
+ mouseMoveAndClick(exploreLink);
+ randomSleep(1500, 3000); // Даем время на возможную загрузку
+ // Вернемся назад, чтобы продолжить основной сценарий
+ driver.navigate().back();
+ logger.info("Navigated back after warm-up click.");
+ randomSleep(1000, 2000);
+ }
+ } catch (TimeoutException | NoSuchElementException e) {
+ logger.info("'Explore' link not found or not clickable for warm-up, skipping.");
+ }
+
+ // 3. Еще одна случайная прокрутка
+ randomScroll();
+ randomSleep(500, 1200);
+
+ } catch (Exception e) {
+ logger.warn("Error during warm-up on YouTube Music page, continuing...", e);
+ }
+ logger.info("Warm-up finished.");
+ }
+
+ public void navigateToYouTubeMusicAndHandlePopups() {
+ logger.info("Navigating to YouTube Music and handling initial popups...");
+ driver.get("https://music.youtube.com/");
+ makeScreenshot("yt_music_opened");
+ randomSleep(1000, 2500); // Дополнительная задержка после загрузки
+
+ warmUpOnYouTubeMusicPage(); // <--- Вызов "прогрева"
+
+ // 1. Окно согласия (часто появляется первым)
+ if (driver.getCurrentUrl().contains("consent.youtube.com") || driver.getCurrentUrl().contains("consent.google.com")) {
+ logger.info("Consent page detected.");
+ try {
+ // Обновленный и исправленный XPath
+ WebElement acceptAllButton = wait.until(ExpectedConditions.elementToBeClickable(
+ By.xpath("//button[.//span[normalize-space()='Принять все' or normalize-space()='Accept all']]")
+ ));
+ acceptAllButton.click();
+ logger.info("Clicked 'Accept all' or similar on consent page.");
+ wait.until(ExpectedConditions.not(ExpectedConditions.urlContains("consent.google.com")));
+ wait.until(ExpectedConditions.not(ExpectedConditions.urlContains("consent.youtube.com")));
+ makeScreenshot("yt_consent_accepted");
+ sleep(2000);
+ } catch (TimeoutException e) {
+ logger.warn("Could not find or click 'Accept all' button on consent page, or did not leave consent page in time.");
+ makeScreenshot("yt_consent_error");
+ }
+ }
+
+ // 2. Другие возможные всплывающие окна YouTube Music (например, "Try Premium", "Get to know the new YouTube Music" и т.п.)
+ try {
+ WebElement noThanksButton = shortWait.until(ExpectedConditions.elementToBeClickable(
+ By.xpath("//ytmusic-popup-container//paper-button[contains(normalize-space(),'No thanks')] | //ytmusic-popup-container//yt-button-renderer[contains(normalize-space(),'No thanks') or @aria-label='Dismiss'] | //button[contains(normalize-space(),'No Thanks')]")));
+ if (noThanksButton.isDisplayed() && noThanksButton.isEnabled()) {
+ logger.info("Found a 'No thanks' or 'Dismiss' button in a popup. Clicking...");
+ mouseMoveAndClick(noThanksButton);
+ makeScreenshot("yt_popup_dismissed");
+ randomSleep(1500, 2500);
+ }
+ } catch (TimeoutException e) {
+ logger.info("No 'No thanks' or 'Dismiss' popup found or it's not clickable.");
+ }
+ logger.info("Finished handling initial YouTube popups.");
+ }
+
+ public void clickSignInOnYouTubeMusic() {
+ logger.info("Attempting to click Sign In button on YouTube Music...");
+ WebElement signInButton = wait.until(ExpectedConditions.elementToBeClickable(
+ By.cssSelector("a.sign-in-link.app-bar-button.style-scope.ytmusic-nav-bar")));
+ mouseMoveAndClick(signInButton);
+ makeScreenshot("yt_signin_clicked");
+ logger.info("Sign In button clicked.");
+ randomSleep(1500, 3000); // Увеличена пауза для загрузки страницы логина Google
+ }
+
+ public void performGoogleLogin(AccountManager.AccountDetails accountDetails) throws Exception {
+ logger.info("Performing Google login for account: {}", accountDetails.email());
+
+ // Ввод логина
+ logger.info("Entering email: {}", accountDetails.email());
+ WebElement emailInput = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("input[type='email']#identifierId")));
+ typeWithDelay(emailInput, accountDetails.email());
+ randomSleep(300, 800); // Небольшая пауза перед кликом "Далее"
+ makeScreenshot("login_email_entered");
+ WebElement nextBtnEmail = driver.findElement(By.cssSelector("#identifierNext button"));
+ mouseMoveAndClick(nextBtnEmail);
+ logger.info("Email submitted.");
+ randomSleep(1800, 3200); // Увеличена пауза для возможного появления капчи или загрузки след. шага
+
+ // Проверка на reCAPTCHA
+ try {
+ logger.debug("Checking for reCAPTCHA iframe...");
+ // Ожидаем iframe с reCAPTCHA (стандартные селекторы)
+ WebElement recaptchaFrame = shortWait.until(ExpectedConditions.presenceOfElementLocated(
+ By.xpath("//iframe[starts-with(@name, 'a-') and starts-with(@src, 'https://www.google.com/recaptcha/api2/anchor?')] | //iframe[@title='reCAPTCHA']")
+ ));
+
+ if (recaptchaFrame != null && recaptchaFrame.isDisplayed()) {
+ logger.info("reCAPTCHA iframe detected. Attempting to interact...");
+ makeScreenshot("recaptcha_iframe_detected");
+ randomSleep(500,1200); // Пауза перед переключением во фрейм
+
+ // 1. Switch to reCAPTCHA iframe
+ driver.switchTo().frame(recaptchaFrame);
+ logger.debug("Switched to reCAPTCHA iframe.");
+
+ // 2. Attempt to click the checkbox
+ boolean checkboxClicked = false;
+ try {
+ WebElement recaptchaCheckbox = new WebDriverWait(driver, Duration.ofSeconds(7)) // Используем явное ожидание для чекбокса
+ .until(ExpectedConditions.elementToBeClickable(By.id("recaptcha-anchor")));
+ if (recaptchaCheckbox.isDisplayed() && recaptchaCheckbox.isEnabled()) {
+ logger.info("reCAPTCHA checkbox (id: recaptcha-anchor) found. Clicking...");
+ mouseMoveAndClick(recaptchaCheckbox);
+ randomSleep(1200, 2200); // Увеличена пауза после клика
+ makeScreenshot("recaptcha_checkbox_clicked");
+ logger.info("reCAPTCHA checkbox (id: recaptcha-anchor) clicked successfully.");
+ checkboxClicked = true;
+ } else {
+ logger.warn("reCAPTCHA checkbox (id: recaptcha-anchor) found but not displayed or enabled.");
+ makeScreenshot("recaptcha_checkbox_not_visible_or_enabled");
+ }
+ } catch (TimeoutException | NoSuchElementException e) {
+ logger.warn("reCAPTCHA checkbox (id: recaptcha-anchor) not found or not clickable within its iframe. It might appear later or not at all.");
+ makeScreenshot("recaptcha_checkbox_not_found_or_unclickable");
+ }
+
+ // 3. Switch back to default content to find sitekey elements
+ driver.switchTo().defaultContent();
+ logger.debug("Switched back to default content to find sitekey.");
+ randomSleep(300, 700); // Пауза после возврата из фрейма
+
+ String siteKey = null;
+
+ // 4. Try to get sitekey from data-sitekey attribute of a g-recaptcha div
+ try {
+ List siteKeyElements = driver.findElements(By.className("g-recaptcha"));
+ if (!siteKeyElements.isEmpty()) {
+ logger.debug("Found {} elements with class 'g-recaptcha'. Checking for data-sitekey...", siteKeyElements.size());
+ for (WebElement el : siteKeyElements) {
+ if (el.isDisplayed()) { // Check only visible elements
+ siteKey = el.getAttribute("data-sitekey");
+ if (siteKey != null && !siteKey.isEmpty()) {
+ logger.info("Found sitekey '{}' in a visible 'g-recaptcha' div's data-sitekey attribute.", siteKey);
+ break;
+ }
+ }
+ }
+ if (siteKey == null || siteKey.isEmpty()) {
+ logger.warn("Found 'g-recaptcha' div(s), but data-sitekey attribute is missing, empty, or on a non-displayed element.");
+ }
+ } else {
+ logger.warn("Could not find any div with class 'g-recaptcha' in default content.");
+ }
+ } catch (Exception e) {
+ logger.warn("Exception while trying to find sitekey in 'g-recaptcha' div's data-sitekey attribute.", e);
+ }
+
+ // 5. If sitekey not found from div, try to get it from the iframe's src URL (k= parameter)
+ // We need to find the recaptchaFrame element again because we switched to defaultContent.
+ if (siteKey == null || siteKey.isEmpty()) {
+ logger.info("Sitekey not found in data-sitekey attribute. Attempting to extract from iframe src.");
+ try {
+ // Re-locate the iframe; its reference might be stale
+ WebElement iframeForSrc = new WebDriverWait(driver, Duration.ofSeconds(5))
+ .until(ExpectedConditions.presenceOfElementLocated(
+ By.xpath("//iframe[starts-with(@name, 'a-') and starts-with(@src, 'https://www.google.com/recaptcha/api2/anchor?')] | //iframe[@title='reCAPTCHA']")
+ ));
+ String iframeSrc = iframeForSrc.getAttribute("src");
+ if (iframeSrc != null && iframeSrc.contains("k=")) {
+ siteKey = iframeSrc.split("k=")[1].split("&")[0];
+ logger.info("Extracted sitekey '{}' from iframe src.", siteKey);
+ } else {
+ logger.warn("Could not extract sitekey from iframe src (k= parameter not found or src is null). Iframe src: {}", iframeSrc);
+ makeScreenshot("recaptcha_sitekey_k_param_not_found");
+ }
+ } catch (Exception e) {
+ logger.warn("Exception while trying to re-locate iframe and extract sitekey from its src.", e);
+ makeScreenshot("recaptcha_sitekey_iframe_src_exception");
+ }
+ }
+
+ // At this point, we are in defaultContent.
+
+ if (siteKey == null || siteKey.isEmpty()) {
+ logger.error("Failed to find or extract reCAPTCHA sitekey. Cannot proceed with 2Captcha.");
+ makeScreenshot("recaptcha_sitekey_extraction_failed_overall");
+ throw new RuntimeException("reCAPTCHA sitekey not found. Login aborted for: " + accountDetails.email());
+ }
+
+ logger.info("Using reCAPTCHA sitekey: {}. Checkbox clicked: {}", siteKey, checkboxClicked);
+ // Call 2Captcha (we are already in defaultContent)
+ String captchaToken = twoCaptchaService.solveRecaptchaV2(siteKey, driver.getCurrentUrl());
+
+ if (captchaToken != null && !captchaToken.isEmpty()) {
+ logger.info("reCAPTCHA solved by 2Captcha. Token: {}", captchaToken);
+ WebElement captchaResponseElement = driver.findElement(By.id("g-recaptcha-response")); // Should be in defaultContent
+ ((JavascriptExecutor) driver).executeScript(
+ "arguments[0].style.display = 'block'; arguments[0].value = arguments[1];",
+ captchaResponseElement,
+ captchaToken
+ );
+ ((JavascriptExecutor) driver).executeScript(
+ "var evt = new Event('input', { bubbles: true, cancelable: false }); arguments[0].dispatchEvent(evt);",
+ captchaResponseElement
+ );
+ ((JavascriptExecutor) driver).executeScript(
+ "var evt = new Event('change', { bubbles: true, cancelable: false }); arguments[0].dispatchEvent(evt);",
+ captchaResponseElement
+ );
+ makeScreenshot("recaptcha_token_injected_events_fired_default_content");
+ randomSleep(1200, 2200); // Увеличена пауза
+
+ String nextButtonXPath = "//button[.//span[contains(text(),'Далее') or contains(text(),'Next')]]"; // Should be in defaultContent
+ WebElement nextBtnAfterCaptcha = wait.until(ExpectedConditions.elementToBeClickable(By.xpath(nextButtonXPath)));
+ logger.info("Attempting to click 'Next' button (after 2captcha token) with XPath: {}", nextButtonXPath);
+ mouseMoveAndClick(nextBtnAfterCaptcha);
+ logger.info("Clicked 'Next' button after 2captcha token submission.");
+ randomSleep(2500, 4000); // Увеличена пауза
+ } else {
+ logger.error("Failed to solve reCAPTCHA with 2Captcha for {}. Token was null or empty.", accountDetails.email());
+ makeScreenshot("recaptcha_2captcha_solve_failed");
+ throw new RuntimeException("Failed to solve reCAPTCHA via 2Captcha. Login aborted for: " + accountDetails.email());
+ }
+ } // End of: if (recaptchaFrame != null && recaptchaFrame.isDisplayed())
+ } catch (TimeoutException e) {
+ logger.info("No reCAPTCHA iframe detected within the timeout or it's not displayed. Proceeding without captcha solving.");
+ makeScreenshot("no_recaptcha_iframe_initial_check");
+ } catch (NoSuchElementException e) { // Catching this at a higher level if the initial iframe isn't found
+ logger.info("reCAPTCHA iframe element not found. Proceeding without captcha solving.");
+ makeScreenshot("recaptcha_iframe_not_found_initial_check");
+ }
+ // Скроллим страницу для человечности
+ randomScroll();
+
+ // Ввод пароля
+ logger.info("Entering password...");
+ WebElement passwordInput = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("input[type='password'][name='Passwd'], input[type='password'][name='password']")));
+ typeWithDelay(passwordInput, accountDetails.password());
+ randomSleep(300, 800); // Небольшая пауза перед кликом "Далее"
+ makeScreenshot("login_password_entered");
+ WebElement nextPassBtn = driver.findElement(By.cssSelector("#passwordNext button"));
+ mouseMoveAndClick(nextPassBtn);
+ logger.info("Password submitted.");
+ randomSleep(1200, 2500);
+ }
+
+ // Имитация ручного ввода текста с задержкой между символами
+ private void typeWithDelay(WebElement element, String text) {
+ Actions actions = new Actions(driver);
+ for (char c : text.toCharArray()) {
+ actions.sendKeys(element, String.valueOf(c)).perform();
+ randomSleep(90, 220); // Слегка увеличен диапазон для большей вариативности
+ }
+ }
+
+ // Имитация движения мыши и клик
+ private void mouseMoveAndClick(WebElement element) {
+ try {
+ Actions actions = new Actions(driver);
+ actions.moveToElement(element).pause(Duration.ofMillis(200 + random.nextInt(400))).click().perform();
+ randomSleep(150, 400);
+ } catch (Exception e) {
+ logger.warn("Mouse move/click imitation failed, fallback to direct click: {}", e.getMessage());
+ element.click();
+ }
+ }
+
+ // Рандомная задержка
+ private void randomSleep(int minMs, int maxMs) {
+ try {
+ Thread.sleep(minMs + random.nextInt(maxMs - minMs + 1));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ // Имитация скроллинга страницы
+ private void randomScroll() {
+ JavascriptExecutor js = (JavascriptExecutor) driver;
+ long pageHeight = (Long) js.executeScript("return Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );");
+ int scrollAmount = random.nextInt((int) (pageHeight / 3)) + (int) (pageHeight / 5) ; // Прокрутка на случайную величину от 1/5 до ~1/2 высоты
+ String scrollScript = String.format("window.scrollBy(0, %d);", scrollAmount * (random.nextBoolean() ? 1 : -1)); // Случайное направление
+
+ if (pageHeight > 0) { // Только если есть что скроллить
+ js.executeScript(scrollScript);
+ logger.debug("Scrolled by a random amount.");
+ } else {
+ logger.debug("Page height is 0, skipping scroll.");
+ }
+ }
+
+ public void handlePostLoginPopups() {
+ logger.info("Handling post-login popups (e.g., 'Not now')...");
+ // Пример обработки кнопки "Not now" (адаптировано из GoogleRegistrationHandler)
+ try {
+ WebElement notNowButton = shortWait.until(ExpectedConditions.elementToBeClickable(
+ By.xpath("//button[.//span[text()='Not now' or text()='Не сейчас']]"))); // Добавил 'Не сейчас'
+ logger.info("Found 'Not now' button. Clicking...");
+ notNowButton.click();
+ makeScreenshot("login_not_now_clicked");
+ sleep(2000);
+ } catch (TimeoutException e) {
+ logger.info("'Not now' button not found or not clickable within the short wait time.");
+ makeScreenshot("login_not_now_not_found");
+ }
+ // Сюда можно добавить обработку других специфичных всплывающих окон после логина
+ }
+
+ // Вспомогательные методы (можно вынести в утилитарный класс, если их много или они общие)
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Sleep interrupted", e);
+ }
+ }
+
+ private void makeScreenshot(String name) {
+ try {
+ File screenshotFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
+ Path destinationDir = Path.of("screenshots");
+ Files.createDirectories(destinationDir);
+ String timestamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmssSSS").format(new java.util.Date());
+ Path destinationPath = destinationDir.resolve(name + "_" + timestamp + ".png");
+ Files.copy(screenshotFile.toPath(), destinationPath, StandardCopyOption.REPLACE_EXISTING);
+ logger.debug("Screenshot saved: {}", destinationPath);
+ } catch (IOException | WebDriverException e) {
+ logger.error("Failed to take or save screenshot '{}': {}", name, e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/proxy/ProxyManager.java b/src/main/java/com/google/accountgen/proxy/ProxyManager.java
index c34baaf..8ea25ed 100644
--- a/src/main/java/com/google/accountgen/proxy/ProxyManager.java
+++ b/src/main/java/com/google/accountgen/proxy/ProxyManager.java
@@ -2,8 +2,10 @@ package com.google.accountgen.proxy;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.accountgen.config.AppProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -24,35 +26,69 @@ public class ProxyManager
private static final Logger logger = LoggerFactory.getLogger(ProxyManager.class);
- private static final String API_URL = "https://px6.link/api/cecd79b327-8f5b6b51aa-63cc268d0a/getproxy?state=active&limit=1000";
private final List proxyList = new ArrayList<>();
private final Random random = new Random();
+ private final String proxy6ApiKey;
+
@Value("${proxyLogin}")
private String proxyLogin;
@Value("${proxyPass}")
private String proxypass;
+ @Autowired
+ public ProxyManager(AppProperties appProperties) {
+ if (appProperties.getProxy6() != null && appProperties.getProxy6().getApiKey() != null && !appProperties.getProxy6().getApiKey().isEmpty()) {
+ this.proxy6ApiKey = appProperties.getProxy6().getApiKey();
+ logger.info("ProxyManager initialized with proxy6 API key.");
+ } else {
+ this.proxy6ApiKey = null;
+ logger.warn("ProxyManager: proxy6 API key is not configured in AppProperties. Proxy fetching might fail if it relies on this key.");
+ }
+ }
+
public void init()
{
+ if (this.proxy6ApiKey == null) {
+ logger.error("Cannot initialize ProxyManager: proxy6 API key is null. Please configure proxy6.api_key in application.yml");
+ throw new RuntimeException("Proxy6 API key is not configured for ProxyManager.");
+ }
+
+ String apiUrl = String.format("https://px6.link/api/%s/getproxy?state=active&limit=1000", this.proxy6ApiKey);
+ logger.info("ProxyManager: Attempting to fetch proxies from URL: {}", apiUrl);
+
try
{
- String response = sendRequest(API_URL);
+ String response = sendRequest(apiUrl);
ObjectMapper mapper = new ObjectMapper();
Map root = mapper.readValue(response, Map.class);
if (!"yes".equals(root.get("status"))) {
+ logger.error("Failed to fetch proxies from proxy6. API response: {}", response);
throw new RuntimeException("Ошибка получения прокси: " + root.get("error"));
}
Map> proxies = (Map>) root.get("list");
+ if (proxies == null || proxies.isEmpty()) {
+ logger.warn("ProxyManager: No proxies found in the API response from proxy6 (list is null or empty).");
+ proxyList.clear();
+ return;
+ }
proxyList.clear();
- for (Map proxy : proxies.values()) {
- String host = (String) proxy.get("host");
- String port = (String) proxy.get("port");
+ for (Map.Entry> entry : proxies.entrySet()) {
+ Map proxyDetails = entry.getValue();
+ String host = (String) proxyDetails.get("host");
+ String port = (String) proxyDetails.get("port");
+
if (host != null && port != null) {
proxyList.add(host + ":" + port);
}
}
+ if (!proxyList.isEmpty()) {
+ logger.info("ProxyManager initialized successfully. Loaded {} proxies.", proxyList.size());
+ } else {
+ logger.warn("ProxyManager initialized, but no valid proxy entries (host:port) were parsed from the API response.");
+ }
} catch (Exception e) {
- throw new RuntimeException(e);
+ logger.error("Failed to initialize ProxyManager while fetching or parsing proxies.", e);
+ throw new RuntimeException("Ошибка при инициализации ProxyManager: " + e.getMessage(), e);
}
}
@@ -71,6 +107,7 @@ public class ProxyManager
public String getNextProxy()
{
if (proxyList.isEmpty()) {
+ logger.warn("getNextProxy called, but proxy list is empty.");
return null;
}
return proxyList.get(random.nextInt(proxyList.size()));
@@ -79,28 +116,30 @@ public class ProxyManager
private String sendRequest(String urlString) throws Exception
{
- URL url = new URL(urlString.replace(" ", "%20")); // Кодируем пробелы на всякий случай
+ URL url = new URL(urlString.replace(" ", "%20"));
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
- con.setConnectTimeout(15000); // Таймаут соединения 15 сек
- con.setReadTimeout(15000); // Таймаут чтения 15 сек
+ con.setConnectTimeout(15000);
+ con.setReadTimeout(15000);
int status = con.getResponseCode();
- BufferedReader in = new BufferedReader(
- new InputStreamReader((status == 200) ? con.getInputStream() : con.getErrorStream(),
- StandardCharsets.UTF_8));
- String inputLine;
StringBuilder content = new StringBuilder();
- while ((inputLine = in.readLine()) != null)
- {
- content.append(inputLine);
+ try (BufferedReader in = new BufferedReader(
+ new InputStreamReader((status >= 200 && status < 300) ? con.getInputStream() : con.getErrorStream(),
+ StandardCharsets.UTF_8))) {
+ String inputLine;
+ while ((inputLine = in.readLine()) != null)
+ {
+ content.append(inputLine);
+ }
+ } finally {
+ con.disconnect();
}
- in.close();
- con.disconnect();
- if (status != 200)
+
+ if (status < 200 || status >= 300)
{
- logger.error("Ошибка HTTP запроса к API: статус {}, Ответ: {}", status, content);
+ logger.error("Ошибка HTTP запроса к API: статус {}, URL: {}, Ответ: {}", status, urlString, content);
}
return content.toString();
diff --git a/src/main/java/com/google/accountgen/registration/GoogleRegistrationHandler.java b/src/main/java/com/google/accountgen/registration/GoogleRegistrationHandler.java
index 0dbd821..987af81 100644
--- a/src/main/java/com/google/accountgen/registration/GoogleRegistrationHandler.java
+++ b/src/main/java/com/google/accountgen/registration/GoogleRegistrationHandler.java
@@ -17,6 +17,8 @@ import java.util.List;
import java.util.Objects;
import java.util.Random;
+import com.google.accountgen.service.HumanEmulationUtils;
+
/**
* Класс обработчик для регистрации аккаунта Google
*/
@@ -908,7 +910,7 @@ public class GoogleRegistrationHandler
// Очищаем поле перед вводом, на всякий случай
phoneInput.clear();
sleep(100);
- phoneInput.sendKeys(phoneNumber); // Вводим номер без кода страны
+ HumanEmulationUtils.typeLikeHuman(phoneInput, phoneNumber); // Вводим номер без кода страны
sleep(300);
// 4. Найти и нажать кнопку "Next"
@@ -1291,31 +1293,9 @@ public class GoogleRegistrationHandler
*/
private void typeWithRandomDelay(WebElement element, String text)
{
- if (element == null || text == null)
- {
- return;
- }
-
- // Очищаем поле
+ if (element == null || text == null) return;
element.clear();
-
- // Вводим символы с задержками
- for (char c : text.toCharArray())
- {
- element.sendKeys(String.valueOf(c));
-
- // Случайная задержка от 50 до 150 мс
- try
- {
- Thread.sleep(50 + random.nextInt(100));
- }
- catch (InterruptedException e)
- {
- Thread.currentThread().interrupt();
- logger.warn("Прерывание потока при имитации ввода с задержкой", e);
- break;
- }
- }
+ HumanEmulationUtils.typeLikeHuman(element, text);
}
/**
diff --git a/src/main/java/com/google/accountgen/service/AccountManager.java b/src/main/java/com/google/accountgen/service/AccountManager.java
new file mode 100644
index 0000000..3723bb2
--- /dev/null
+++ b/src/main/java/com/google/accountgen/service/AccountManager.java
@@ -0,0 +1,160 @@
+package com.google.accountgen.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class AccountManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(AccountManager.class);
+ private final String accountsDir;
+ private final DarkShoppingService darkShoppingService;
+ private final String googleAccountProductId;
+ private final int purchaseQuantity;
+ private static final String USED_MARKER = " - использован";
+
+ public AccountManager(String accountsDir, DarkShoppingService darkShoppingService, String googleAccountProductId, int purchaseQuantity) {
+ this.accountsDir = accountsDir;
+ this.darkShoppingService = darkShoppingService;
+ this.googleAccountProductId = googleAccountProductId;
+ this.purchaseQuantity = purchaseQuantity; // Минимальное количество для нового продавца - 15
+ try {
+ Files.createDirectories(Paths.get(accountsDir));
+ } catch (IOException e) {
+ logger.error("Could not create accounts directory: {}", accountsDir, e);
+ }
+ }
+
+ private Optional findAvailableAccountFile() {
+ try (Stream stream = Files.list(Paths.get(accountsDir))) {
+ return stream
+ .filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".txt"))
+ .filter(file -> {
+ try {
+ List lines = Files.readAllLines(file, StandardCharsets.UTF_8);
+ for (String line : lines) {
+ if (!line.trim().isEmpty() && !line.trim().endsWith(USED_MARKER)) {
+ return true; // Найден неиспользованный аккаунт
+ }
+ }
+ return false; // Нет неиспользованных аккаунтов
+ } catch (IOException e) {
+ logger.error("Error reading file to check for unused accounts: {}", file, e);
+ return false;
+ }
+ })
+ .findFirst();
+ } catch (IOException e) {
+ logger.error("Error listing files in accounts directory: {}", accountsDir, e);
+ return Optional.empty();
+ }
+ }
+
+ public Optional getNextAccount() {
+ Optional accountFileOpt = findAvailableAccountFile();
+
+ while (true) {
+ if (accountFileOpt.isPresent()) {
+ Path accountFile = accountFileOpt.get();
+ try {
+ List lines = Files.readAllLines(accountFile, StandardCharsets.UTF_8);
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ if (!line.trim().endsWith(USED_MARKER) && !line.trim().isEmpty()) {
+ // Поддержка форматов: email:password, email;password, email:password:add_email
+ String[] parts;
+ if (line.contains(";")) {
+ parts = line.split(";", 2);
+ } else {
+ parts = line.split(":");
+ }
+ if (parts.length == 2) {
+ String email = parts[0].trim();
+ String password = parts[1].trim().replace(USED_MARKER, "");
+ return Optional.of(new AccountDetails(email, password, null, null, accountFile, i));
+ } else if (parts.length == 3) {
+ String email = parts[0].trim();
+ String password = parts[1].trim();
+ String backupEmail = parts[2].trim().replace(USED_MARKER, "");
+ return Optional.of(new AccountDetails(email, password, backupEmail, null, accountFile, i));
+ } else if (parts.length == 4) {
+ String email = parts[0].trim();
+ String password = parts[1].trim();
+ String backupEmail = parts[2].trim();
+ String backupPassword = parts[3].trim().replace(USED_MARKER, "");
+ return Optional.of(new AccountDetails(email, password, backupEmail, backupPassword, accountFile, i));
+ } else {
+ logger.warn("Invalid account line format in {}: {}. Expected 2, 3, or 4 parts separated by colons.", accountFile, line);
+ }
+ }
+ }
+ // Все аккаунты в файле использованы или не найдены
+ logger.info("All accounts in file {} are used or file is invalid/empty. Deleting file.", accountFile);
+ Files.deleteIfExists(accountFile);
+ accountFileOpt = findAvailableAccountFile();
+ } catch (IOException e) {
+ logger.error("Error processing account file: {}", accountFile, e);
+ return Optional.empty();
+ }
+ } else {
+ logger.info("No available account files found. Attempting to purchase new accounts (Product ID: {}, Quantity: {}).", googleAccountProductId, purchaseQuantity);
+ boolean purchased = darkShoppingService.purchaseAccounts(googleAccountProductId, purchaseQuantity);
+ if (purchased) {
+ logger.info("Successfully purchased new accounts. Trying to get an account again.");
+ accountFileOpt = findAvailableAccountFile();
+ if (accountFileOpt.isEmpty()) {
+ logger.warn("Purchased accounts, but no new account file found immediately. There might be a delay or an issue.");
+ return Optional.empty();
+ }
+ } else {
+ logger.error("Failed to purchase new accounts.");
+ return Optional.empty();
+ }
+ }
+ }
+ }
+
+ public void markAccountAsUsed(AccountDetails accountDetails) {
+ try {
+ List lines = Files.readAllLines(accountDetails.filePath(), StandardCharsets.UTF_8);
+ if (accountDetails.lineNumber() < lines.size()) {
+ String line = lines.get(accountDetails.lineNumber());
+ if (!line.trim().endsWith(USED_MARKER)) {
+ lines.set(accountDetails.lineNumber(), line + USED_MARKER);
+ Files.write(accountDetails.filePath(), lines, StandardCharsets.UTF_8);
+ logger.info("Marked account {} as used in file {}", accountDetails.email(), accountDetails.filePath());
+ } else {
+ logger.warn("Account {} was already marked as used in file {}", accountDetails.email(), accountDetails.filePath());
+ }
+ } else {
+ logger.error("Line number {} is out of bounds for file {}. Cannot mark account as used.", accountDetails.lineNumber(), accountDetails.filePath());
+ }
+
+ // Проверяем, все ли аккаунты в файле использованы
+ boolean allEffectivelyUsed = true;
+ for (String currentLine : Files.readAllLines(accountDetails.filePath(), StandardCharsets.UTF_8)) {
+ if (!currentLine.trim().isEmpty() && !currentLine.trim().endsWith(USED_MARKER)) {
+ allEffectivelyUsed = false;
+ break;
+ }
+ }
+
+ if (allEffectivelyUsed) {
+ logger.info("All accounts in {} are effectively used (or file is empty). Deleting file.", accountDetails.filePath());
+ Files.deleteIfExists(accountDetails.filePath());
+ }
+
+ } catch (IOException e) {
+ logger.error("Error marking account {} as used in file {}", accountDetails.email(), accountDetails.filePath(), e);
+ }
+ }
+
+ public record AccountDetails(String email, String password, String backupEmail, String backupPassword, Path filePath, int lineNumber) {}
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/service/DarkShoppingService.java b/src/main/java/com/google/accountgen/service/DarkShoppingService.java
new file mode 100644
index 0000000..6a81683
--- /dev/null
+++ b/src/main/java/com/google/accountgen/service/DarkShoppingService.java
@@ -0,0 +1,273 @@
+package com.google.accountgen.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public class DarkShoppingService {
+
+ private static final Logger logger = LoggerFactory.getLogger(DarkShoppingService.class);
+ private static final String API_BASE_URL = "https://dark.shopping/api/v1"; // Изменено
+ private final String apiKey;
+ private final OkHttpClient httpClient;
+ private final ObjectMapper objectMapper;
+ private final String accountsDir;
+
+ public DarkShoppingService(String apiKey, String accountsDir) {
+ this.apiKey = apiKey;
+ this.httpClient = new OkHttpClient();
+ this.objectMapper = new ObjectMapper();
+ this.accountsDir = accountsDir;
+ // Создаем директорию, если ее нет
+ try {
+ Files.createDirectories(Paths.get(accountsDir));
+ } catch (IOException e) {
+ logger.error("Could not create accounts directory: {}", accountsDir, e);
+ // Можно выбросить кастомное исключение или обработать иначе
+ }
+ }
+
+ // Метод для получения product_id (если потребуется)
+ public Optional findGoogleAccountProductId(String groupId) {
+ Request request = new Request.Builder()
+ .url(API_BASE_URL + "/groups/" + groupId + "/products")
+ .header("Authorization", "Bearer " + apiKey)
+ .header("Accept", "application/json")
+ .build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ logger.error("Failed to get products for group {}: {} - {}", groupId, response.code(), response.body() != null ? response.body().string() : "Empty body");
+ return Optional.empty();
+ }
+
+ String responseBody = response.body().string();
+ logger.debug("Products response: {}", responseBody);
+ // Здесь нужно будет распарсить JSON и найти нужный product_id
+ // Примерная структура ответа:
+ // { "success": true, "data": [ { "id": "...", "name": "Google Account...", ... }, ... ] }
+ // Вам нужно будет найти товар с подходящим именем, например, содержащим "Google"
+ // и вернуть его "id". Этот код нужно будет дописать после получения реального ответа API.
+ // Пока возвращаем Optional.empty()
+ return Optional.empty(); // ЗАГЛУШКА
+ } catch (IOException e) {
+ logger.error("Error fetching products for group {}", groupId, e);
+ return Optional.empty();
+ }
+ }
+
+
+ public boolean purchaseAccounts(String productId, int quantity) {
+ logger.info("Attempting to purchase {} accounts for product ID: {}", quantity, productId);
+
+ RequestBody formBody = new FormBody.Builder()
+ .add("key", apiKey) // Добавлен apiKey как POST параметр
+ .add("product", productId) // Изменено с product_id на product
+ .add("quantity", String.valueOf(quantity))
+ // .add("email", "your_email_for_notifications@example.com") // Опционально, в документации это send_email_copy (boolean)
+ .build();
+
+ Request request = new Request.Builder()
+ .url(API_BASE_URL + "/order/create") // Изменен URL
+ // .header("Authorization", "Bearer " + apiKey) // Удалена авторизация через заголовок
+ .header("Accept", "application/json")
+ .post(formBody)
+ .build();
+
+ // Добавим логирование URL и тела запроса
+ logger.info("Sending POST request to: {}", request.url());
+ if (request.body() instanceof FormBody) {
+ FormBody actualFormBody = (FormBody) request.body();
+ StringBuilder formBodyContent = new StringBuilder();
+ for (int i = 0; i < actualFormBody.size(); i++) {
+ formBodyContent.append(actualFormBody.encodedName(i))
+ .append("=")
+ .append(actualFormBody.encodedValue(i))
+ .append(i < actualFormBody.size() - 1 ? "&" : "");
+ }
+ logger.info("Request body: {}", formBodyContent.toString());
+ }
+
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ // Сохраняем тело ответа, чтобы прочитать его один раз
+ ResponseBody responseBodyObj = response.body();
+ String responseBodyString = (responseBodyObj != null) ? responseBodyObj.string() : null;
+
+ // Логируем HTTP статус и тело ответа ВСЕГДА
+ logger.info("Order creation response status: {}, body: {}", response.code(), responseBodyString);
+
+ if (!response.isSuccessful() || responseBodyString == null) {
+ // Это сообщение теперь будет более информативным из-за лога выше
+ logger.error("Failed to create order. HTTP status: {}, Body: {}", response.code(), responseBodyString);
+ return false;
+ }
+
+ // logger.debug("Order creation response: {}", responseBodyString); // Уже залогировано выше как INFO
+ Map orderResponse = objectMapper.readValue(responseBodyString, Map.class);
+
+ if (Boolean.TRUE.equals(orderResponse.get("success"))) {
+ Map orderData = (Map) orderResponse.get("data");
+ String orderId = String.valueOf(orderData.get("id"));
+ logger.info("Order created. Order ID: {}", orderId);
+
+ // Проверяем, есть ли ссылка на скачивание сразу
+ if ("ok".equalsIgnoreCase(String.valueOf(orderData.get("status"))) && orderData.containsKey("link")) {
+ String downloadLink = (String) orderData.get("link");
+ logger.info("Order processed immediately. Download link: {}", downloadLink);
+ return downloadAndSaveAccounts(downloadLink, orderId);
+ } else if ("pending".equalsIgnoreCase(String.valueOf(orderData.get("status")))) {
+ logger.info("Order is pending. Will try to fetch details later. Order ID: {}", orderId);
+ // Пауза перед запросом статуса/деталей заказа
+ try {
+ Thread.sleep(10000); // 10 секунд, можно настроить
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Delay interrupted", e);
+ }
+ return fetchOrderDownloadLink(orderId);
+ } else {
+ logger.error("Order status is not 'ok' or 'pending', or link is missing. Order data: {}", orderData);
+ return false;
+ }
+ } else {
+ Object errorDetails = orderResponse.get("data");
+ String errorMessage = "Unknown API error during order creation.";
+ if (errorDetails instanceof Map) {
+ Map, ?> errorDataMap = (Map, ?>) errorDetails;
+ if (errorDataMap.containsKey("message")) {
+ errorMessage = (String) errorDataMap.get("message");
+ }
+ logger.error("Order creation was not successful. API Error: '{}'. Full error data: {}", errorMessage, errorDetails);
+ } else {
+ // Fallback if 'data' is not as expected or missing, but success is false
+ logger.error("Order creation was not successful: {}. Full API response: {}", errorMessage, orderResponse);
+ }
+ return false;
+ }
+ } catch (IOException e) {
+ logger.error("Error creating order", e);
+ return false;
+ }
+ }
+
+ // Новый метод для получения ссылки на скачивание заказа
+ private boolean fetchOrderDownloadLink(String orderId) {
+ logger.info("Fetching download link for order ID: {}", orderId);
+
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(API_BASE_URL + "/order/download").newBuilder();
+ urlBuilder.addQueryParameter("key", apiKey);
+ urlBuilder.addQueryParameter("id", orderId);
+
+ Request request = new Request.Builder()
+ .url(urlBuilder.build())
+ .header("Accept", "application/json")
+ .get() // Используем GET
+ .build();
+
+ logger.info("Requesting order download link: {}", request.url());
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ String responseBodyString = response.body() != null ? response.body().string() : null;
+ logger.info("Fetch order download link response status: {}, body: {}", response.code(), responseBodyString);
+
+ if (!response.isSuccessful() || responseBodyString == null) {
+ logger.error("Failed to get order download link for {}: {} - {}", orderId, response.code(), responseBodyString);
+ return false;
+ }
+
+ Map downloadResponse = objectMapper.readValue(responseBodyString, Map.class);
+
+ if (Boolean.TRUE.equals(downloadResponse.get("success"))) {
+ Map data = (Map) downloadResponse.get("data");
+ if (data != null && data.containsKey("link")) {
+ String downloadLink = (String) data.get("link");
+ logger.info("Successfully fetched download link for order {}: {}", orderId, downloadLink);
+ return downloadAndSaveAccounts(downloadLink, orderId); // Используем существующий метод для скачивания
+ } else {
+ // Это может произойти, если заказ все еще не готов, или API вернуло неожиданный формат
+ logger.error("Download link not found in response for order {}. Data: {}", orderId, data);
+ // Можно добавить логику повторных попыток или проверки статуса через order/status
+ // Например, если API может вернуть статус заказа через этот же эндпоинт, это нужно учесть
+ // В документации 'order/download' должен возвращать 'link' при успехе
+ // Если API может вернуть здесь {"success":true, "data":{"status":"pending"}}, нужна доп. обработка
+ return false;
+ }
+ } else {
+ Object errorDetails = downloadResponse.get("data");
+ String errorMessage = "Failed to fetch download link.";
+ if (errorDetails instanceof Map) {
+ Map, ?> errorDataMap = (Map, ?>) errorDetails;
+ if (errorDataMap.containsKey("message")) {
+ errorMessage = (String) errorDataMap.get("message");
+ }
+ }
+ logger.error("Fetching download link was not successful for order {}. API Error: '{}'. Full error data: {}", orderId, errorMessage, errorDetails);
+ return false;
+ }
+
+ } catch (IOException e) {
+ logger.error("Error fetching download link for order {}", orderId, e);
+ return false;
+ }
+ }
+
+ private boolean downloadAndSaveAccounts(String fileUrl, String orderId) {
+ Request request = new Request.Builder()
+ .url(fileUrl) // Используем полный URL как есть
+ // .header("Authorization", "Bearer " + apiKey) // В документации не указано, что для скачивания файла нужен ключ API, если ссылка уже получена.
+ // Если загрузка по прямой ссылке требует авторизации, ее нужно будет добавить.
+ // Обычно, если ссылка временная/подписанная, доп. авторизация не нужна.
+ .build();
+
+ logger.info("Downloading account data from URL: {}", fileUrl);
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (!response.isSuccessful() || response.body() == null) {
+ logger.error("Failed to download account file from {}: {} - {}", fileUrl, response.code(), response.body() != null ? response.body().string() : "Empty body");
+ return false;
+ }
+
+ try (InputStream inputStream = response.body().byteStream()) {
+ Path filePath = Paths.get(accountsDir, "order_" + orderId + "_accounts.txt");
+ try (FileOutputStream outputStream = new FileOutputStream(filePath.toFile())) {
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytesRead);
+ }
+ }
+ logger.info("Accounts data saved to {}", filePath);
+ return true;
+ }
+
+ } catch (IOException e) {
+ logger.error("Error downloading or saving account file from {}", fileUrl, e);
+ return false;
+ }
+ }
+
+ private boolean saveAccountsFromText(String textData, String orderId) {
+ Path filePath = Paths.get(accountsDir, "order_" + orderId + "_accounts.txt");
+ try {
+ Files.writeString(filePath, textData);
+ logger.info("Accounts data saved to {}", filePath);
+ return true;
+ } catch (IOException e) {
+ logger.error("Error saving accounts data to {}", filePath, e);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/service/EmailService.java b/src/main/java/com/google/accountgen/service/EmailService.java
new file mode 100644
index 0000000..02cead3
--- /dev/null
+++ b/src/main/java/com/google/accountgen/service/EmailService.java
@@ -0,0 +1,115 @@
+package com.google.accountgen.service;
+
+import jakarta.mail.*;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.search.FromTerm;
+import jakarta.mail.search.SearchTerm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class EmailService {
+
+ private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
+
+ private final String host;
+ private final int port;
+ private final String username;
+ private final String password;
+ private final boolean imapSsl;
+
+ // Паттерн для извлечения 6-8 значного кода Google
+ private static final Pattern GOOGLE_CODE_PATTERN = Pattern.compile("\\\\b(\\\\d{6,8})\\\\b");
+
+ public EmailService(String host, int port, String username, String password, boolean imapSsl) {
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ this.imapSsl = imapSsl;
+ }
+
+ /**
+ * Ищет и извлекает код подтверждения Google из писем.
+ * @param targetEmail Адрес резервной почты, на которую было отправлено письмо.
+ * @param targetPassword Пароль от резервной почты.
+ * @return Код подтверждения или null, если не найден.
+ */
+ public String fetchGoogleVerificationCode(String targetEmail, String targetPassword) {
+ if (host == null || host.isEmpty() || targetEmail == null || targetEmail.isEmpty()) {
+ logger.warn("IMAP host or target email is not configured. Cannot fetch verification code.");
+ return null;
+ }
+
+ Properties props = new Properties();
+ props.put("mail.store.protocol", "imap");
+ props.put("mail.imap.host", host);
+ props.put("mail.imap.port", String.valueOf(port));
+
+ if (imapSsl) {
+ props.put("mail.imap.ssl.enable", "true");
+ // Можно добавить specific ciphers if needed, e.g.:
+ // props.put("mail.imap.ssl.ciphersuites", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
+ } else {
+ props.put("mail.imap.starttls.enable", "true"); // Предполагаем STARTTLS если не SSL
+ }
+
+ Session emailSession = Session.getInstance(props);
+ // emailSession.setDebug(true); // для отладки
+
+ try (Store store = emailSession.getStore("imap")) {
+ store.connect(this.username, this.password); // Используем общие креды для подключения к серверу
+ // если для каждого ящика свои, то targetEmail, targetPassword
+
+ try (Folder inbox = store.getFolder("INBOX")) {
+ inbox.open(Folder.READ_ONLY);
+
+ // Ищем письма от Google
+ SearchTerm sender = new FromTerm(new InternetAddress("no-reply@accounts.google.com"));
+ Message[] messages = inbox.search(sender);
+
+ logger.info("Found {} messages from no-reply@accounts.google.com", messages.length);
+
+ // Ищем код в последних сообщениях
+ for (int i = messages.length - 1; i >= 0 && i >= messages.length - 5; i--) { // Проверяем последние 5 писем
+ Message message = messages[i];
+ logger.debug("Processing message: {}", message.getSubject());
+
+ String content = "";
+ Object msgContent = message.getContent();
+ if (msgContent instanceof String) {
+ content = (String) msgContent;
+ } else if (msgContent instanceof Multipart) {
+ Multipart multipart = (Multipart) msgContent;
+ for (int j = 0; j < multipart.getCount(); j++) {
+ BodyPart bodyPart = multipart.getBodyPart(j);
+ if (bodyPart.isMimeType("text/plain")) {
+ content = (String) bodyPart.getContent();
+ break;
+ }
+ // Можно добавить обработку text/html, если потребуется
+ }
+ }
+
+ Matcher matcher = GOOGLE_CODE_PATTERN.matcher(content);
+ if (matcher.find()) {
+ String code = matcher.group(1);
+ logger.info("Found Google verification code: {}", code);
+ return code;
+ }
+ }
+ logger.warn("Google verification code not found in recent emails from no-reply@accounts.google.com");
+ }
+ } catch (NoSuchProviderException e) {
+ logger.error("IMAP provider not found", e);
+ } catch (MessagingException e) {
+ logger.error("Messaging exception while fetching email", e);
+ } catch (Exception e) {
+ logger.error("Unexpected error while fetching email", e);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/service/FingerprintUtils.java b/src/main/java/com/google/accountgen/service/FingerprintUtils.java
new file mode 100644
index 0000000..6d15503
--- /dev/null
+++ b/src/main/java/com/google/accountgen/service/FingerprintUtils.java
@@ -0,0 +1,45 @@
+package com.google.accountgen.service;
+
+import java.util.List;
+import java.util.Random;
+
+public class FingerprintUtils {
+ private static final List USER_AGENTS = List.of(
+ // Популярные user-agent'ы Chrome/Windows/Mac/Linux
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
+ );
+ private static final List LANGS = List.of(
+ "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
+ "en-US,en;q=0.9,ru;q=0.8",
+ "en-GB,en;q=0.9,ru;q=0.8",
+ "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
+ "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"
+ );
+ private static final List TIMEZONES = List.of(
+ "Europe/Moscow", "Europe/Istanbul", "Europe/Berlin", "America/New_York", "Asia/Almaty", "Asia/Yekaterinburg"
+ );
+ private static final List WINDOW_SIZES = List.of(
+ "1920,1080", "1366,768", "1600,900", "1536,864", "1440,900", "1280,800"
+ );
+ private static final Random RANDOM = new Random();
+
+ public static String getRandomUserAgent() {
+ return USER_AGENTS.get(RANDOM.nextInt(USER_AGENTS.size()));
+ }
+
+ public static String getRandomLang() {
+ return LANGS.get(RANDOM.nextInt(LANGS.size()));
+ }
+
+ public static String getRandomTimezone() {
+ return TIMEZONES.get(RANDOM.nextInt(TIMEZONES.size()));
+ }
+
+ public static String getRandomWindowSize() {
+ return WINDOW_SIZES.get(RANDOM.nextInt(WINDOW_SIZES.size()));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/service/HumanEmulationUtils.java b/src/main/java/com/google/accountgen/service/HumanEmulationUtils.java
new file mode 100644
index 0000000..7d0857e
--- /dev/null
+++ b/src/main/java/com/google/accountgen/service/HumanEmulationUtils.java
@@ -0,0 +1,66 @@
+package com.google.accountgen.service;
+
+import org.openqa.selenium.*;
+import java.util.Random;
+
+public class HumanEmulationUtils {
+ private static final Random RANDOM = new Random();
+
+ // Ввод текста с задержками, ошибками и backspace
+ public static void typeLikeHuman(WebElement element, String text) {
+ StringBuilder current = new StringBuilder();
+ for (char c : text.toCharArray()) {
+ // Иногда делаем ошибку (опечатку)
+ if (RANDOM.nextDouble() < 0.07) {
+ char typo = (char) (c + 1); // простая опечатка
+ element.sendKeys(String.valueOf(typo));
+ sleep(50, 120);
+ element.sendKeys(Keys.BACK_SPACE);
+ sleep(50, 120);
+ }
+ element.sendKeys(String.valueOf(c));
+ current.append(c);
+ // Иногда жмём backspace
+ if (current.length() > 2 && RANDOM.nextDouble() < 0.04) {
+ element.sendKeys(Keys.BACK_SPACE);
+ current.deleteCharAt(current.length() - 1);
+ sleep(50, 120);
+ element.sendKeys(String.valueOf(c));
+ current.append(c);
+ }
+ sleep(70, 180);
+ }
+ }
+
+ // Случайная задержка
+ public static void sleep(int minMs, int maxMs) {
+ try {
+ Thread.sleep(minMs + RANDOM.nextInt(maxMs - minMs + 1));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ // Случайное движение мыши (эмуляция через JS)
+ public static void randomMouseMove(WebDriver driver) {
+ if (!(driver instanceof JavascriptExecutor)) return;
+ int x = 50 + RANDOM.nextInt(800);
+ int y = 50 + RANDOM.nextInt(500);
+ String script = "var ev = new MouseEvent('mousemove', {clientX: " + x + ", clientY: " + y + ", bubbles: true}); document.body.dispatchEvent(ev);";
+ ((JavascriptExecutor) driver).executeScript(script);
+ sleep(80, 200);
+ }
+
+ // Случайный клик по неключевому элементу
+ public static void randomClick(WebDriver driver) {
+ try {
+ java.util.List divs = driver.findElements(By.tagName("div"));
+ if (!divs.isEmpty()) {
+ WebElement el = divs.get(RANDOM.nextInt(divs.size()));
+ ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", el);
+ sleep(50, 150);
+ el.click();
+ }
+ } catch (Exception ignored) {}
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/service/TwoCaptchaService.java b/src/main/java/com/google/accountgen/service/TwoCaptchaService.java
new file mode 100644
index 0000000..7df8d99
--- /dev/null
+++ b/src/main/java/com/google/accountgen/service/TwoCaptchaService.java
@@ -0,0 +1,128 @@
+package com.google.accountgen.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.accountgen.config.AppProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.net.URIBuilder;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+@Service
+@Slf4j
+public class TwoCaptchaService {
+
+ private static final String API_URL_REQUEST = "http://2captcha.com/in.php";
+ private static final String API_URL_RESPONSE = "http://2captcha.com/res.php";
+ private static final int MAX_RETRIES = 20; // Max retries for checking captcha status
+ private static final long RETRY_DELAY_MS = 5000; // Delay between retries
+
+ private final String apiKey;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ public TwoCaptchaService(AppProperties appProperties) {
+ this.apiKey = appProperties.getCaptchaApiKey();
+ }
+
+ /**
+ * Solves a reCAPTCHA v2.
+ *
+ * @param siteKey The sitekey from the page.
+ * @param pageUrl The URL of the page where the captcha is present.
+ * @return The g-recaptcha-response token, or null if solving failed.
+ * @throws IOException If an I/O error occurs during HTTP communication.
+ * @throws InterruptedException If the thread is interrupted while waiting.
+ * @throws URISyntaxException If there is an error in the URI syntax.
+ */
+ public String solveRecaptchaV2(String siteKey, String pageUrl) throws IOException, InterruptedException, URISyntaxException {
+ log.info("Attempting to solve reCAPTCHA v2 with sitekey: {} for URL: {}", siteKey, pageUrl);
+
+ if (apiKey == null || apiKey.isEmpty()) {
+ log.error("2Captcha API key is not configured.");
+ return null;
+ }
+
+ String requestId;
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ URI uri = new URIBuilder(API_URL_REQUEST)
+ .addParameter("key", apiKey)
+ .addParameter("method", "userrecaptcha")
+ .addParameter("googlekey", siteKey)
+ .addParameter("pageurl", pageUrl)
+ .addParameter("json", "1") // Request response in JSON format
+ .build();
+
+ HttpGet request = new HttpGet(uri);
+ log.debug("Sending request to 2Captcha: {}", uri);
+
+ requestId = httpClient.execute(request, response -> {
+ String responseBody = EntityUtils.toString(response.getEntity());
+ log.debug("2Captcha request response: {}", responseBody);
+ JsonNode rootNode = objectMapper.readTree(responseBody);
+ if (rootNode.path("status").asInt() == 1) {
+ return rootNode.path("request").asText();
+ } else {
+ String errorText = rootNode.path("error_text").asText("Unknown error from 2Captcha API (in.php)");
+ log.error("Error from 2Captcha (in.php): {}", errorText);
+ throw new IOException("Error from 2Captcha (in.php): " + errorText);
+ }
+ });
+ }
+
+ log.info("2Captcha request ID: {}", requestId);
+
+ // Poll for the result
+ for (int i = 0; i < MAX_RETRIES; i++) {
+ Thread.sleep(RETRY_DELAY_MS);
+ final int currentAttempt = i + 1;
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ URI uri = new URIBuilder(API_URL_RESPONSE)
+ .addParameter("key", apiKey)
+ .addParameter("action", "get")
+ .addParameter("id", requestId)
+ .addParameter("json", "1") // Request response in JSON format
+ .build();
+
+ HttpGet request = new HttpGet(uri);
+ log.debug("Polling 2Captcha for result: {}", uri);
+
+ String solution = httpClient.execute(request, response -> {
+ String responseBody = EntityUtils.toString(response.getEntity());
+ log.debug("2Captcha poll response: {}", responseBody);
+ JsonNode rootNode = objectMapper.readTree(responseBody);
+ if (rootNode.path("status").asInt() == 1) {
+ return rootNode.path("request").asText();
+ } else {
+ String errorText = rootNode.path("request").asText(); // Sometimes error is in 'request' field
+ if ("CAPCHA_NOT_READY".equals(errorText)) {
+ log.info("Captcha not ready yet, retrying... (Attempt {}/{})", currentAttempt, MAX_RETRIES);
+ return null; // Special case for retry
+ } else {
+ log.error("Error or unexpected response from 2Captcha (res.php): {}", errorText);
+ // Consider specific error codes like ERROR_CAPTCHA_UNSOLVABLE
+ if ("ERROR_CAPTCHA_UNSOLVABLE".equals(errorText)) {
+ throw new IOException("2Captcha reported captcha as unsolvable.");
+ }
+ throw new IOException("Error or unexpected response from 2Captcha (res.php): " + errorText);
+ }
+ }
+ });
+
+ if (solution != null) {
+ log.info("reCAPTCHA solved successfully. Token: {}", solution);
+ return solution;
+ }
+ }
+ }
+ log.error("Failed to solve reCAPTCHA after {} retries.", MAX_RETRIES);
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/accountgen/youtube/YouTubeMusicHandler.java b/src/main/java/com/google/accountgen/youtube/YouTubeMusicHandler.java
index 2062e4e..2849427 100644
--- a/src/main/java/com/google/accountgen/youtube/YouTubeMusicHandler.java
+++ b/src/main/java/com/google/accountgen/youtube/YouTubeMusicHandler.java
@@ -438,4 +438,26 @@ public class YouTubeMusicHandler
return null;
}
+
+ /**
+ * Кликает по первому видео/треку на главной странице YouTube Music
+ * (используется после логина для генерации cookies/POT)
+ * @return true если клик успешен, иначе false
+ */
+ public boolean playFirstTrackOrVideo() {
+ try {
+ logger.info("Пробую кликнуть по первому видео/треку на YouTube Music...");
+ // Явное ожидание появления кнопки play у первого видео/трека
+ WebElement playButton = wait.until(ExpectedConditions.elementToBeClickable(
+ By.cssSelector("ytmusic-responsive-list-item-renderer ytmusic-play-button-renderer")
+ ));
+ playButton.click();
+ logger.info("Клик по первому видео/треку выполнен успешно.");
+ Thread.sleep(2000); // Дать время на запуск воспроизведения
+ return true;
+ } catch (Exception e) {
+ logger.warn("Не удалось кликнуть по первому видео/треку: {}", e.getMessage());
+ return false;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8905026..0322aa3 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,7 +1,7 @@
#sms_activate_api_key: cd64e325c15813edc722867BB6171B94
-sms_activate_api_key:
+sms_activate_api_key:
-captcha_api_key: deeb0d134a47e88dffab6dc48a5a0906
+captcha_api_key: 399fac0452b879b2dbf586fdaa919d8d
captcha_service_type: TWOCAPTCHA
retry_count: 3
@@ -21,4 +21,32 @@ sms_service: SMSACTIVATE
account_count: 30
delay_between_attempts: 5000
-default_country: 0
\ No newline at end of file
+default_country: 0
+
+darkshopping:
+ api_key: "209e0176b46e059bd7bec1e70c7d1811ec4d7c9a"
+ product_id: "113421"
+ purchase_quantity: 1
+
+accounts:
+ directory: "accounts"
+
+imap:
+ host: "imap.example.com"
+ port: 993
+ username: "your-email@example.com"
+ password: "your-password"
+ ssl: true
+
+proxy6:
+ api_key: "3147c5e1c1-05102f380c-5e16a83021"
+
+app:
+ useProxy: true
+ accounts:
+ directory: accounts
+ cookies:
+ directory: accounts_cookies
+
+ # ... другие существующие app свойства ...
+ # ... остальные существующие свойства ...
\ No newline at end of file
diff --git a/src/main/resources/static/js/stealth.min.js b/src/main/resources/static/js/stealth.min.js
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/src/main/resources/static/js/stealth.min.js
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/target/classes/application.yml b/target/classes/application.yml
index 79db725..224be48 100644
--- a/target/classes/application.yml
+++ b/target/classes/application.yml
@@ -1,7 +1,6 @@
-#sms_activate_api_key: cd64e325c15813edc722867BB6171B94
-sms_activate_api_key: pPmJIKmh7RepulVySCPf
+sms_activate_api_key:
-captcha_api_key: deeb0d134a47e88dffab6dc48a5a0906
+captcha_api_key: 399fac0452b879b2dbf586fdaa919d8d
captcha_service_type: TWOCAPTCHA
retry_count: 3
@@ -21,4 +20,32 @@ sms_service: SMSACTIVATE
account_count: 30
delay_between_attempts: 5000
-default_country: 0
\ No newline at end of file
+default_country: 0
+
+darkshopping:
+ api_key: "ВАШ_API_КЛЮЧ"
+ product_id: "113421"
+ purchase_quantity: 1
+
+accounts:
+ directory: "accounts"
+
+imap:
+ host: "imap.example.com"
+ port: 993
+ username: "your-email@example.com"
+ password: "your-password"
+ ssl: true
+
+proxy6:
+ api_key: "ВАШ_API_КЛЮЧ"
+
+app:
+ useProxy: true
+ accounts:
+ directory: accounts
+ cookies:
+ directory: accounts_cookies
+
+ # ... другие существующие app свойства ...
+ # ... остальные существующие свойства ...
\ No newline at end of file
diff --git a/target/classes/com/google/accountgen/AccountProcessingService.class b/target/classes/com/google/accountgen/AccountProcessingService.class
new file mode 100644
index 0000000..60b9174
Binary files /dev/null and b/target/classes/com/google/accountgen/AccountProcessingService.class differ
diff --git a/target/classes/com/google/accountgen/Task.class b/target/classes/com/google/accountgen/Task.class
index d991597..573a882 100644
Binary files a/target/classes/com/google/accountgen/Task.class and b/target/classes/com/google/accountgen/Task.class differ
diff --git a/target/classes/com/google/accountgen/account/AccountResult.class b/target/classes/com/google/accountgen/account/AccountResult.class
index 0d03e04..2a7ffba 100644
Binary files a/target/classes/com/google/accountgen/account/AccountResult.class and b/target/classes/com/google/accountgen/account/AccountResult.class differ
diff --git a/target/classes/com/google/accountgen/account/GoogleAccount.class b/target/classes/com/google/accountgen/account/GoogleAccount.class
index 4b9ed77..3f924f5 100644
Binary files a/target/classes/com/google/accountgen/account/GoogleAccount.class and b/target/classes/com/google/accountgen/account/GoogleAccount.class differ
diff --git a/target/classes/com/google/accountgen/account/GoogleAccountGenerationResult.class b/target/classes/com/google/accountgen/account/GoogleAccountGenerationResult.class
index ab2ef27..80f1592 100644
Binary files a/target/classes/com/google/accountgen/account/GoogleAccountGenerationResult.class and b/target/classes/com/google/accountgen/account/GoogleAccountGenerationResult.class differ
diff --git a/target/classes/com/google/accountgen/account/GoogleAccountGeneratorImpl.class b/target/classes/com/google/accountgen/account/GoogleAccountGeneratorImpl.class
index 737814c..dae1cdb 100644
Binary files a/target/classes/com/google/accountgen/account/GoogleAccountGeneratorImpl.class and b/target/classes/com/google/accountgen/account/GoogleAccountGeneratorImpl.class differ
diff --git a/target/classes/com/google/accountgen/account/UserData.class b/target/classes/com/google/accountgen/account/UserData.class
index 40aa8bf..1064e51 100644
Binary files a/target/classes/com/google/accountgen/account/UserData.class and b/target/classes/com/google/accountgen/account/UserData.class differ
diff --git a/target/classes/com/google/accountgen/captcha/CaptchaSolver$ServiceType.class b/target/classes/com/google/accountgen/captcha/CaptchaSolver$ServiceType.class
index 84e9f9f..ec09250 100644
Binary files a/target/classes/com/google/accountgen/captcha/CaptchaSolver$ServiceType.class and b/target/classes/com/google/accountgen/captcha/CaptchaSolver$ServiceType.class differ
diff --git a/target/classes/com/google/accountgen/config/AppProperties$AccountsProps.class b/target/classes/com/google/accountgen/config/AppProperties$AccountsProps.class
new file mode 100644
index 0000000..c5b868e
Binary files /dev/null and b/target/classes/com/google/accountgen/config/AppProperties$AccountsProps.class differ
diff --git a/target/classes/com/google/accountgen/config/AppProperties$Cookies.class b/target/classes/com/google/accountgen/config/AppProperties$Cookies.class
new file mode 100644
index 0000000..e9a2098
Binary files /dev/null and b/target/classes/com/google/accountgen/config/AppProperties$Cookies.class differ
diff --git a/target/classes/com/google/accountgen/config/AppProperties$DarkShoppingProps.class b/target/classes/com/google/accountgen/config/AppProperties$DarkShoppingProps.class
new file mode 100644
index 0000000..4cdb935
Binary files /dev/null and b/target/classes/com/google/accountgen/config/AppProperties$DarkShoppingProps.class differ
diff --git a/target/classes/com/google/accountgen/config/AppProperties$ImapProps.class b/target/classes/com/google/accountgen/config/AppProperties$ImapProps.class
new file mode 100644
index 0000000..79606ff
Binary files /dev/null and b/target/classes/com/google/accountgen/config/AppProperties$ImapProps.class differ
diff --git a/target/classes/com/google/accountgen/config/AppProperties$Proxy6Props.class b/target/classes/com/google/accountgen/config/AppProperties$Proxy6Props.class
new file mode 100644
index 0000000..52da3f9
Binary files /dev/null and b/target/classes/com/google/accountgen/config/AppProperties$Proxy6Props.class differ
diff --git a/target/classes/com/google/accountgen/config/AppProperties.class b/target/classes/com/google/accountgen/config/AppProperties.class
new file mode 100644
index 0000000..91a0d91
Binary files /dev/null and b/target/classes/com/google/accountgen/config/AppProperties.class differ
diff --git a/target/classes/com/google/accountgen/cookies/CookieManager$1.class b/target/classes/com/google/accountgen/cookies/CookieManager$1.class
new file mode 100644
index 0000000..292bea0
Binary files /dev/null and b/target/classes/com/google/accountgen/cookies/CookieManager$1.class differ
diff --git a/target/classes/com/google/accountgen/cookies/CookieManager.class b/target/classes/com/google/accountgen/cookies/CookieManager.class
index b0d75b9..abe2c90 100644
Binary files a/target/classes/com/google/accountgen/cookies/CookieManager.class and b/target/classes/com/google/accountgen/cookies/CookieManager.class differ
diff --git a/target/classes/com/google/accountgen/login/GoogleLoginHandler.class b/target/classes/com/google/accountgen/login/GoogleLoginHandler.class
new file mode 100644
index 0000000..d525fa3
Binary files /dev/null and b/target/classes/com/google/accountgen/login/GoogleLoginHandler.class differ
diff --git a/target/classes/com/google/accountgen/proxy/ProxyManager.class b/target/classes/com/google/accountgen/proxy/ProxyManager.class
index ff79050..9a1a4ec 100644
Binary files a/target/classes/com/google/accountgen/proxy/ProxyManager.class and b/target/classes/com/google/accountgen/proxy/ProxyManager.class differ
diff --git a/target/classes/com/google/accountgen/registration/GoogleRegistrationHandler.class b/target/classes/com/google/accountgen/registration/GoogleRegistrationHandler.class
index 134a16e..1666a29 100644
Binary files a/target/classes/com/google/accountgen/registration/GoogleRegistrationHandler.class and b/target/classes/com/google/accountgen/registration/GoogleRegistrationHandler.class differ
diff --git a/target/classes/com/google/accountgen/service/AccountManager$AccountDetails.class b/target/classes/com/google/accountgen/service/AccountManager$AccountDetails.class
new file mode 100644
index 0000000..b7c1b71
Binary files /dev/null and b/target/classes/com/google/accountgen/service/AccountManager$AccountDetails.class differ
diff --git a/target/classes/com/google/accountgen/service/AccountManager.class b/target/classes/com/google/accountgen/service/AccountManager.class
new file mode 100644
index 0000000..f924f5d
Binary files /dev/null and b/target/classes/com/google/accountgen/service/AccountManager.class differ
diff --git a/target/classes/com/google/accountgen/service/DarkShoppingService.class b/target/classes/com/google/accountgen/service/DarkShoppingService.class
new file mode 100644
index 0000000..e9bdd86
Binary files /dev/null and b/target/classes/com/google/accountgen/service/DarkShoppingService.class differ
diff --git a/target/classes/com/google/accountgen/service/EmailService.class b/target/classes/com/google/accountgen/service/EmailService.class
new file mode 100644
index 0000000..fd48e4a
Binary files /dev/null and b/target/classes/com/google/accountgen/service/EmailService.class differ
diff --git a/target/classes/com/google/accountgen/service/FingerprintUtils.class b/target/classes/com/google/accountgen/service/FingerprintUtils.class
new file mode 100644
index 0000000..e04c9a4
Binary files /dev/null and b/target/classes/com/google/accountgen/service/FingerprintUtils.class differ
diff --git a/target/classes/com/google/accountgen/service/HumanEmulationUtils.class b/target/classes/com/google/accountgen/service/HumanEmulationUtils.class
new file mode 100644
index 0000000..0f5e9a4
Binary files /dev/null and b/target/classes/com/google/accountgen/service/HumanEmulationUtils.class differ
diff --git a/target/classes/com/google/accountgen/service/TwoCaptchaService.class b/target/classes/com/google/accountgen/service/TwoCaptchaService.class
new file mode 100644
index 0000000..d56b0fd
Binary files /dev/null and b/target/classes/com/google/accountgen/service/TwoCaptchaService.class differ
diff --git a/target/classes/com/google/accountgen/sms/SmsActivateService.class b/target/classes/com/google/accountgen/sms/SmsActivateService.class
index 6cf7838..64962d4 100644
Binary files a/target/classes/com/google/accountgen/sms/SmsActivateService.class and b/target/classes/com/google/accountgen/sms/SmsActivateService.class differ
diff --git a/target/classes/com/google/accountgen/user/RandomUserGenerator.class b/target/classes/com/google/accountgen/user/RandomUserGenerator.class
index 1b0f4e4..370e89b 100644
Binary files a/target/classes/com/google/accountgen/user/RandomUserGenerator.class and b/target/classes/com/google/accountgen/user/RandomUserGenerator.class differ
diff --git a/target/classes/com/google/accountgen/user/UserData$Gender.class b/target/classes/com/google/accountgen/user/UserData$Gender.class
index 9397206..b705f79 100644
Binary files a/target/classes/com/google/accountgen/user/UserData$Gender.class and b/target/classes/com/google/accountgen/user/UserData$Gender.class differ
diff --git a/target/classes/com/google/accountgen/user/UserData.class b/target/classes/com/google/accountgen/user/UserData.class
index 582f6dc..1995adb 100644
Binary files a/target/classes/com/google/accountgen/user/UserData.class and b/target/classes/com/google/accountgen/user/UserData.class differ
diff --git a/target/classes/com/google/accountgen/youtube/YouTubeMusicHandler.class b/target/classes/com/google/accountgen/youtube/YouTubeMusicHandler.class
index 404ab8b..465cb21 100644
Binary files a/target/classes/com/google/accountgen/youtube/YouTubeMusicHandler.class and b/target/classes/com/google/accountgen/youtube/YouTubeMusicHandler.class differ
diff --git a/target/classes/static/js/stealth.min.js b/target/classes/static/js/stealth.min.js
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/target/classes/static/js/stealth.min.js
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/target/cookieGenerator-jar-with-dependencies.jar b/target/cookieGenerator-jar-with-dependencies.jar
index 060b5a3..a518cd5 100644
Binary files a/target/cookieGenerator-jar-with-dependencies.jar and b/target/cookieGenerator-jar-with-dependencies.jar differ
diff --git a/target/cookieGenerator.jar b/target/cookieGenerator.jar
index 2ff0403..5bd2ca4 100644
Binary files a/target/cookieGenerator.jar and b/target/cookieGenerator.jar differ
diff --git a/target/cookieGenerator.jar.original b/target/cookieGenerator.jar.original
index 6e43f77..9e9b0e4 100644
Binary files a/target/cookieGenerator.jar.original and b/target/cookieGenerator.jar.original differ
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
index 1a065af..0661117 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -1,20 +1,36 @@
-com\google\accountgen\user\UserData.class
com\google\accountgen\CookieGenerator.class
com\google\accountgen\captcha\CaptchaSolver$ServiceType.class
com\google\accountgen\sms\SmsActivateService$ActivationResult.class
com\google\accountgen\registration\GoogleRegistrationSelectors.class
-com\google\accountgen\registration\GoogleRegistrationHandler.class
+com\google\accountgen\login\GoogleLoginHandler.class
com\google\accountgen\user\RandomUserGenerator.class
+com\google\accountgen\service\AccountManager.class
+com\google\accountgen\config\AppProperties$DarkShoppingProps.class
+com\google\accountgen\captcha\CaptchaSolver.class
+com\google\accountgen\account\GoogleAccountGenerationResult.class
+com\google\accountgen\youtube\YouTubeMusicHandler.class
+com\google\accountgen\service\AccountManager$AccountDetails.class
+com\google\accountgen\account\AccountResult.class
+com\google\accountgen\user\UserData$Gender.class
+com\google\accountgen\service\DarkShoppingService.class
+com\google\accountgen\cookies\CookieManager$1.class
+com\google\accountgen\service\TwoCaptchaService.class
+com\google\accountgen\account\GoogleAccount.class
+com\google\accountgen\user\UserData.class
+com\google\accountgen\config\AppProperties$Proxy6Props.class
+com\google\accountgen\service\FingerprintUtils.class
+com\google\accountgen\config\AppProperties.class
+com\google\accountgen\registration\GoogleRegistrationHandler.class
com\google\accountgen\account\GoogleAccountGeneratorImpl.class
com\google\accountgen\sms\SmsActivateService.class
com\google\accountgen\cookies\CookieManager.class
-com\google\accountgen\captcha\CaptchaSolver.class
+com\google\accountgen\service\EmailService.class
+com\google\accountgen\config\AppProperties$Cookies.class
com\google\accountgen\account\GoogleAccountGenerator.class
com\google\accountgen\Task.class
-com\google\accountgen\account\GoogleAccountGenerationResult.class
-com\google\accountgen\youtube\YouTubeMusicHandler.class
-com\google\accountgen\account\AccountResult.class
com\google\accountgen\proxy\ProxyManager.class
-com\google\accountgen\user\UserData$Gender.class
-com\google\accountgen\account\GoogleAccount.class
+com\google\accountgen\service\HumanEmulationUtils.class
+com\google\accountgen\AccountProcessingService.class
+com\google\accountgen\config\AppProperties$ImapProps.class
+com\google\accountgen\config\AppProperties$AccountsProps.class
com\google\accountgen\account\UserData.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
index 4837fd7..bbb37ab 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -1,17 +1,26 @@
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\Task.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\registration\GoogleRegistrationSelectors.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\youtube\YouTubeMusicHandler.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\user\RandomUserGenerator.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\cookies\CookieManager.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\proxy\ProxyManager.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccountGeneratorImpl.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\UserData.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\sms\SmsActivateService.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\user\UserData.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccount.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccountGenerator.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\AccountResult.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccountGenerationResult.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\CookieGenerator.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\captcha\CaptchaSolver.java
-C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\registration\GoogleRegistrationHandler.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\CookieGenerator.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\login\GoogleLoginHandler.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\config\AppProperties.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\account\GoogleAccount.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\AccountProcessingService.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\account\AccountResult.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\service\DarkShoppingService.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\Task.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\registration\GoogleRegistrationHandler.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\service\EmailService.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\cookies\CookieManager.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\sms\SmsActivateService.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\user\UserData.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\account\GoogleAccountGeneratorImpl.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\captcha\CaptchaSolver.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\account\GoogleAccountGenerationResult.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\service\HumanEmulationUtils.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\registration\GoogleRegistrationSelectors.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\service\FingerprintUtils.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\account\GoogleAccountGenerator.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\proxy\ProxyManager.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\service\TwoCaptchaService.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\service\AccountManager.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\youtube\YouTubeMusicHandler.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\account\UserData.java
+C:\Projects\generator2\cookieGenerator (2)\src\main\java\com\google\accountgen\user\RandomUserGenerator.java