Скачивание профилей с dark.shopping, вход в аккаунты, сбор cookies и POT
This commit is contained in:
0
.gitignore.txt
Normal file
0
.gitignore.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
markarenderwrvekx@gmail.com:seFFX1LvcMp4:arturkozlov99w5f1b2@mail.com:password
|
0
chromedriver
Normal file
0
chromedriver
Normal file
30
pom.xml
30
pom.xml
@ -63,6 +63,13 @@
|
|||||||
<version>${jackson.version}</version>
|
<version>${jackson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Зависимость для поддержки Java Time API в Jackson -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- HTTP клиент для API запросов -->
|
<!-- HTTP клиент для API запросов -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||||
@ -112,6 +119,29 @@
|
|||||||
<artifactId>selenium-devtools-v135</artifactId>
|
<artifactId>selenium-devtools-v135</artifactId>
|
||||||
<version>${selenium.version}</version>
|
<version>${selenium.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Зависимости для работы с почтой -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.mail</groupId>
|
||||||
|
<artifactId>jakarta.mail-api</artifactId>
|
||||||
|
<version>2.1.3</version> <!-- Используйте актуальную версию -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.angus</groupId>
|
||||||
|
<artifactId>angus-mail</artifactId>
|
||||||
|
<version>2.0.3</version> <!-- Используйте актуальную версию -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>1.2.83</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.checkerframework</groupId>
|
||||||
|
<artifactId>checker-qual</artifactId>
|
||||||
|
<version>3.42.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -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<AccountManager.AccountDetails> 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<Path> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
// import org.springframework.scheduling.annotation.Scheduled; // Закомментировано
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -62,7 +62,7 @@ public class Task
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Scheduled(fixedRate = 24 * 60 * 60 * 1000)
|
// @Scheduled(fixedRate = 24 * 60 * 60 * 1000) // Закомментировано
|
||||||
@Async
|
@Async
|
||||||
public void generateProfileMeta()
|
public void generateProfileMeta()
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,6 @@ import org.openqa.selenium.By;
|
|||||||
import org.openqa.selenium.PageLoadStrategy;
|
import org.openqa.selenium.PageLoadStrategy;
|
||||||
import org.openqa.selenium.TimeoutException;
|
import org.openqa.selenium.TimeoutException;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.openqa.selenium.chrome.ChromeDriver;
|
|
||||||
import org.openqa.selenium.chrome.ChromeOptions;
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
import org.openqa.selenium.logging.LogEntry;
|
import org.openqa.selenium.logging.LogEntry;
|
||||||
import org.openqa.selenium.logging.LogType;
|
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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -38,6 +38,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Реализация генератора аккаунтов Google
|
* Реализация генератора аккаунтов Google
|
||||||
@ -438,105 +439,73 @@ public class GoogleAccountGeneratorImpl implements GoogleAccountGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Настраивает и создает экземпляр ChromeDriver
|
* Настраивает и создает экземпляр обычного ChromeDriver
|
||||||
*
|
*
|
||||||
* @return настроенный веб-драйвер
|
* @return настроенный веб-драйвер
|
||||||
*/
|
*/
|
||||||
private WebDriver setupChromeDriver()
|
private WebDriver setupChromeDriver()
|
||||||
{
|
{
|
||||||
logger.info("Настройка и запуск ChromeDriver");
|
logger.info("Настройка и запуск обычного ChromeDriver");
|
||||||
|
|
||||||
try
|
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();
|
ChromeOptions options = new ChromeOptions();
|
||||||
// Если Chrome установлен НЕ в стандартное место, раскомментируйте и укажите путь:
|
|
||||||
// options.setBinary("C:\\Путь\\К\\Вашему\\chrome.exe");
|
|
||||||
|
|
||||||
// Используем EAGER стратегию загрузки страницы, чтобы не ждать полной загрузки ресурсов
|
|
||||||
options.setPageLoadStrategy(PageLoadStrategy.EAGER);
|
options.setPageLoadStrategy(PageLoadStrategy.EAGER);
|
||||||
|
// Уникальный fingerprint
|
||||||
// Добавляем User-Agent
|
options.addArguments("--user-agent=" + com.google.accountgen.service.FingerprintUtils.getRandomUserAgent());
|
||||||
options.addArguments(
|
String windowSize = com.google.accountgen.service.FingerprintUtils.getRandomWindowSize();
|
||||||
"--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
|
options.addArguments("--window-size=" + windowSize);
|
||||||
|
options.addArguments("--lang=" + com.google.accountgen.service.FingerprintUtils.getRandomLang());
|
||||||
// Создаём уникальный временный профиль для Chrome
|
|
||||||
Path tempProfile = Files.createTempDirectory("chrome-profile-");
|
Path tempProfile = Files.createTempDirectory("chrome-profile-");
|
||||||
options.addArguments("--user-data-dir=" + tempProfile.toAbsolutePath());
|
options.addArguments("--user-data-dir=" + tempProfile.toAbsolutePath());
|
||||||
logger.info("Используется временный профиль Chrome: {}", tempProfile);
|
logger.info("Используется временный профиль Chrome: {}", tempProfile);
|
||||||
|
|
||||||
// Настройка логирования браузера для захвата консольных логов
|
|
||||||
LoggingPreferences logPrefs = new LoggingPreferences();
|
LoggingPreferences logPrefs = new LoggingPreferences();
|
||||||
logPrefs.enable(LogType.BROWSER, java.util.logging.Level.ALL); // Захват всех консольных логов
|
logPrefs.enable(LogType.BROWSER, Level.ALL);
|
||||||
// Отключаем Performance лог, т.к. он был закомментирован
|
logPrefs.enable(LogType.PERFORMANCE, Level.OFF);
|
||||||
logPrefs.enable(LogType.PERFORMANCE, java.util.logging.Level.OFF);
|
|
||||||
options.setCapability("goog:loggingPrefs", logPrefs);
|
options.setCapability("goog:loggingPrefs", logPrefs);
|
||||||
logger.info("Настроено логирование браузера (консоль).");
|
options.addArguments("--no-sandbox");
|
||||||
|
options.addArguments("--disable-dev-shm-usage");
|
||||||
// --- ОТКЛЮЧАЕМ HEADLESS ---
|
options.addArguments("--disable-extensions");
|
||||||
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("--disable-blink-features=AutomationControlled");
|
options.addArguments("--disable-blink-features=AutomationControlled");
|
||||||
options.addArguments("--disable-notifications");
|
options.addArguments("--disable-notifications");
|
||||||
options.addArguments("--disable-popup-blocking");
|
options.addArguments("--disable-popup-blocking");
|
||||||
options.addArguments("--log-level=3");
|
options.addArguments("--log-level=3");
|
||||||
options.addArguments("--silent");
|
options.addArguments("--silent");
|
||||||
|
options.addArguments("--disable-infobars");
|
||||||
// --- ДОБАВЛЕННЫЕ ОПЦИИ ДЛЯ МАСКИРОВКИ ---
|
|
||||||
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
|
|
||||||
if (useProxy && proxyManager.hasProxies())
|
if (useProxy && proxyManager.hasProxies())
|
||||||
{
|
{
|
||||||
chosenProxy = proxyManager.getNextProxy();
|
chosenProxy = proxyManager.getNextProxy();
|
||||||
if (chosenProxy == null) {
|
if (chosenProxy != null)
|
||||||
logger.error("Не удалось получить ни одного подходящего прокси с proxy6.net. Проверьте фильтры IP/тип или наличие активных прокси.");
|
{
|
||||||
throw new RuntimeException("Нет доступных прокси для использования. Проверьте настройки proxy6.net и фильтры в ProxyManager.");
|
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);
|
|
||||||
}
|
}
|
||||||
|
else if (useProxy)
|
||||||
|
{
|
||||||
// Создаем и возвращаем драйвер
|
logger.warn("Прокси включены в конфигурации, но ProxyManager не имеет доступных прокси.");
|
||||||
logger.info("Попытка создания экземпляра ChromeDriver с опциями...");
|
}
|
||||||
|
logger.info("Создаём обычный ChromeDriver");
|
||||||
WebDriver driver = new ChromeDriver(options);
|
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 успешно создан.");
|
logger.info("Экземпляр ChromeDriver успешно создан.");
|
||||||
|
|
||||||
return driver;
|
return driver;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.error("Критическая ошибка при настройке или запуске ChromeDriver", e);
|
logger.error("Критическая ошибка при настройке или запуске ChromeDriver", e);
|
||||||
throw new RuntimeException("Не удалось инициализировать ChromeDriver", e); // Пробрасываем исключение дальше
|
throw new RuntimeException("Не удалось инициализировать ChromeDriver", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,19 @@ import org.openqa.selenium.WebDriver;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.Objects;
|
||||||
import java.util.Set;
|
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 браузера
|
* Класс для управления cookies браузера
|
||||||
@ -14,6 +25,26 @@ import java.util.Set;
|
|||||||
public class CookieManager
|
public class CookieManager
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CookieManager.class);
|
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
|
* Экспортирует cookies в формате Netscape
|
||||||
@ -58,4 +89,71 @@ public class CookieManager
|
|||||||
cookie.getValue());
|
cookie.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void saveCookiesToJson(WebDriver driver, String accountIdentifier) {
|
||||||
|
Path cookieFile = Paths.get(cookiesStorageDir, accountIdentifier + "_cookies.json");
|
||||||
|
Set<Cookie> 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<Cookie> cookies = objectMapper.readValue(jsonCookies, new TypeReference<Set<Cookie>>() {});
|
||||||
|
|
||||||
|
// Перед загрузкой 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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<WebElement> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,10 @@ package com.google.accountgen.proxy;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.MappingIterator;
|
import com.fasterxml.jackson.databind.MappingIterator;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.accountgen.config.AppProperties;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@ -24,35 +26,69 @@ public class ProxyManager
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ProxyManager.class);
|
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<String> proxyList = new ArrayList<>();
|
private final List<String> proxyList = new ArrayList<>();
|
||||||
private final Random random = new Random();
|
private final Random random = new Random();
|
||||||
|
private final String proxy6ApiKey;
|
||||||
|
|
||||||
@Value("${proxyLogin}")
|
@Value("${proxyLogin}")
|
||||||
private String proxyLogin;
|
private String proxyLogin;
|
||||||
@Value("${proxyPass}")
|
@Value("${proxyPass}")
|
||||||
private String 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()
|
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
|
try
|
||||||
{
|
{
|
||||||
String response = sendRequest(API_URL);
|
String response = sendRequest(apiUrl);
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
Map<String, Object> root = mapper.readValue(response, Map.class);
|
Map<String, Object> root = mapper.readValue(response, Map.class);
|
||||||
if (!"yes".equals(root.get("status"))) {
|
if (!"yes".equals(root.get("status"))) {
|
||||||
|
logger.error("Failed to fetch proxies from proxy6. API response: {}", response);
|
||||||
throw new RuntimeException("Ошибка получения прокси: " + root.get("error"));
|
throw new RuntimeException("Ошибка получения прокси: " + root.get("error"));
|
||||||
}
|
}
|
||||||
Map<String, Map<String, Object>> proxies = (Map<String, Map<String, Object>>) root.get("list");
|
Map<String, Map<String, Object>> proxies = (Map<String, Map<String, Object>>) 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();
|
proxyList.clear();
|
||||||
for (Map<String, Object> proxy : proxies.values()) {
|
for (Map.Entry<String, Map<String, Object>> entry : proxies.entrySet()) {
|
||||||
String host = (String) proxy.get("host");
|
Map<String, Object> proxyDetails = entry.getValue();
|
||||||
String port = (String) proxy.get("port");
|
String host = (String) proxyDetails.get("host");
|
||||||
|
String port = (String) proxyDetails.get("port");
|
||||||
|
|
||||||
if (host != null && port != null) {
|
if (host != null && port != null) {
|
||||||
proxyList.add(host + ":" + port);
|
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) {
|
} 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()
|
public String getNextProxy()
|
||||||
{
|
{
|
||||||
if (proxyList.isEmpty()) {
|
if (proxyList.isEmpty()) {
|
||||||
|
logger.warn("getNextProxy called, but proxy list is empty.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return proxyList.get(random.nextInt(proxyList.size()));
|
return proxyList.get(random.nextInt(proxyList.size()));
|
||||||
@ -79,28 +116,30 @@ public class ProxyManager
|
|||||||
|
|
||||||
private String sendRequest(String urlString) throws Exception
|
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();
|
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||||
con.setRequestMethod("GET");
|
con.setRequestMethod("GET");
|
||||||
con.setConnectTimeout(15000); // Таймаут соединения 15 сек
|
con.setConnectTimeout(15000);
|
||||||
con.setReadTimeout(15000); // Таймаут чтения 15 сек
|
con.setReadTimeout(15000);
|
||||||
|
|
||||||
int status = con.getResponseCode();
|
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();
|
StringBuilder content = new StringBuilder();
|
||||||
while ((inputLine = in.readLine()) != null)
|
try (BufferedReader in = new BufferedReader(
|
||||||
{
|
new InputStreamReader((status >= 200 && status < 300) ? con.getInputStream() : con.getErrorStream(),
|
||||||
content.append(inputLine);
|
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();
|
return content.toString();
|
||||||
|
@ -17,6 +17,8 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import com.google.accountgen.service.HumanEmulationUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Класс обработчик для регистрации аккаунта Google
|
* Класс обработчик для регистрации аккаунта Google
|
||||||
*/
|
*/
|
||||||
@ -908,7 +910,7 @@ public class GoogleRegistrationHandler
|
|||||||
// Очищаем поле перед вводом, на всякий случай
|
// Очищаем поле перед вводом, на всякий случай
|
||||||
phoneInput.clear();
|
phoneInput.clear();
|
||||||
sleep(100);
|
sleep(100);
|
||||||
phoneInput.sendKeys(phoneNumber); // Вводим номер без кода страны
|
HumanEmulationUtils.typeLikeHuman(phoneInput, phoneNumber); // Вводим номер без кода страны
|
||||||
sleep(300);
|
sleep(300);
|
||||||
|
|
||||||
// 4. Найти и нажать кнопку "Next"
|
// 4. Найти и нажать кнопку "Next"
|
||||||
@ -1291,31 +1293,9 @@ public class GoogleRegistrationHandler
|
|||||||
*/
|
*/
|
||||||
private void typeWithRandomDelay(WebElement element, String text)
|
private void typeWithRandomDelay(WebElement element, String text)
|
||||||
{
|
{
|
||||||
if (element == null || text == null)
|
if (element == null || text == null) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Очищаем поле
|
|
||||||
element.clear();
|
element.clear();
|
||||||
|
HumanEmulationUtils.typeLikeHuman(element, text);
|
||||||
// Вводим символы с задержками
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
160
src/main/java/com/google/accountgen/service/AccountManager.java
Normal file
160
src/main/java/com/google/accountgen/service/AccountManager.java
Normal file
@ -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<Path> findAvailableAccountFile() {
|
||||||
|
try (Stream<Path> stream = Files.list(Paths.get(accountsDir))) {
|
||||||
|
return stream
|
||||||
|
.filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".txt"))
|
||||||
|
.filter(file -> {
|
||||||
|
try {
|
||||||
|
List<String> 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<AccountDetails> getNextAccount() {
|
||||||
|
Optional<Path> accountFileOpt = findAvailableAccountFile();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (accountFileOpt.isPresent()) {
|
||||||
|
Path accountFile = accountFileOpt.get();
|
||||||
|
try {
|
||||||
|
List<String> 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<String> 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) {}
|
||||||
|
|
||||||
|
}
|
@ -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<String> 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<String, Object> orderResponse = objectMapper.readValue(responseBodyString, Map.class);
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(orderResponse.get("success"))) {
|
||||||
|
Map<String, Object> orderData = (Map<String, Object>) 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<String, Object> downloadResponse = objectMapper.readValue(responseBodyString, Map.class);
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(downloadResponse.get("success"))) {
|
||||||
|
Map<String, Object> data = (Map<String, Object>) 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
src/main/java/com/google/accountgen/service/EmailService.java
Normal file
115
src/main/java/com/google/accountgen/service/EmailService.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.google.accountgen.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class FingerprintUtils {
|
||||||
|
private static final List<String> 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<String> 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<String> TIMEZONES = List.of(
|
||||||
|
"Europe/Moscow", "Europe/Istanbul", "Europe/Berlin", "America/New_York", "Asia/Almaty", "Asia/Yekaterinburg"
|
||||||
|
);
|
||||||
|
private static final List<String> 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()));
|
||||||
|
}
|
||||||
|
}
|
@ -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<WebElement> 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) {}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -438,4 +438,26 @@ public class YouTubeMusicHandler
|
|||||||
|
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
#sms_activate_api_key: cd64e325c15813edc722867BB6171B94
|
#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
|
captcha_service_type: TWOCAPTCHA
|
||||||
|
|
||||||
retry_count: 3
|
retry_count: 3
|
||||||
@ -21,4 +21,32 @@ sms_service: SMSACTIVATE
|
|||||||
account_count: 30
|
account_count: 30
|
||||||
delay_between_attempts: 5000
|
delay_between_attempts: 5000
|
||||||
|
|
||||||
default_country: 0
|
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 свойства ...
|
||||||
|
# ... остальные существующие свойства ...
|
1
src/main/resources/static/js/stealth.min.js
vendored
Normal file
1
src/main/resources/static/js/stealth.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
#sms_activate_api_key: cd64e325c15813edc722867BB6171B94
|
sms_activate_api_key:
|
||||||
sms_activate_api_key: pPmJIKmh7RepulVySCPf
|
|
||||||
|
|
||||||
captcha_api_key: deeb0d134a47e88dffab6dc48a5a0906
|
captcha_api_key: 399fac0452b879b2dbf586fdaa919d8d
|
||||||
captcha_service_type: TWOCAPTCHA
|
captcha_service_type: TWOCAPTCHA
|
||||||
|
|
||||||
retry_count: 3
|
retry_count: 3
|
||||||
@ -21,4 +20,32 @@ sms_service: SMSACTIVATE
|
|||||||
account_count: 30
|
account_count: 30
|
||||||
delay_between_attempts: 5000
|
delay_between_attempts: 5000
|
||||||
|
|
||||||
default_country: 0
|
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 свойства ...
|
||||||
|
# ... остальные существующие свойства ...
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/google/accountgen/config/AppProperties.class
Normal file
BIN
target/classes/com/google/accountgen/config/AppProperties.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/com/google/accountgen/service/EmailService.class
Normal file
BIN
target/classes/com/google/accountgen/service/EmailService.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
target/classes/static/js/stealth.min.js
vendored
Normal file
1
target/classes/static/js/stealth.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,20 +1,36 @@
|
|||||||
com\google\accountgen\user\UserData.class
|
|
||||||
com\google\accountgen\CookieGenerator.class
|
com\google\accountgen\CookieGenerator.class
|
||||||
com\google\accountgen\captcha\CaptchaSolver$ServiceType.class
|
com\google\accountgen\captcha\CaptchaSolver$ServiceType.class
|
||||||
com\google\accountgen\sms\SmsActivateService$ActivationResult.class
|
com\google\accountgen\sms\SmsActivateService$ActivationResult.class
|
||||||
com\google\accountgen\registration\GoogleRegistrationSelectors.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\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\account\GoogleAccountGeneratorImpl.class
|
||||||
com\google\accountgen\sms\SmsActivateService.class
|
com\google\accountgen\sms\SmsActivateService.class
|
||||||
com\google\accountgen\cookies\CookieManager.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\account\GoogleAccountGenerator.class
|
||||||
com\google\accountgen\Task.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\proxy\ProxyManager.class
|
||||||
com\google\accountgen\user\UserData$Gender.class
|
com\google\accountgen\service\HumanEmulationUtils.class
|
||||||
com\google\accountgen\account\GoogleAccount.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
|
com\google\accountgen\account\UserData.class
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\Task.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\CookieGenerator.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\registration\GoogleRegistrationSelectors.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\login\GoogleLoginHandler.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\youtube\YouTubeMusicHandler.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\config\AppProperties.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\user\RandomUserGenerator.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\account\GoogleAccount.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\cookies\CookieManager.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\AccountProcessingService.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\proxy\ProxyManager.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\account\AccountResult.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccountGeneratorImpl.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\service\DarkShoppingService.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\UserData.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\Task.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\sms\SmsActivateService.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\registration\GoogleRegistrationHandler.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\user\UserData.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\service\EmailService.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccount.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\cookies\CookieManager.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccountGenerator.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\sms\SmsActivateService.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\AccountResult.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\user\UserData.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\account\GoogleAccountGenerationResult.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\account\GoogleAccountGeneratorImpl.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\CookieGenerator.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\captcha\CaptchaSolver.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\captcha\CaptchaSolver.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\account\GoogleAccountGenerationResult.java
|
||||||
C:\IdeaProject\cookieGenerator\src\main\java\com\google\accountgen\registration\GoogleRegistrationHandler.java
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\service\HumanEmulationUtils.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\registration\GoogleRegistrationSelectors.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\service\FingerprintUtils.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\account\GoogleAccountGenerator.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\proxy\ProxyManager.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\service\TwoCaptchaService.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\service\AccountManager.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\youtube\YouTubeMusicHandler.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\account\UserData.java
|
||||||
|
C:\Projects\generator2\cookieGenerator <20> <20><><EFBFBD><EFBFBD><EFBFBD> (2)\src\main\java\com\google\accountgen\user\RandomUserGenerator.java
|
||||||
|
Reference in New Issue
Block a user