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