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