Загрузка данных


from dataclasses import dataclass
from typing import Optional, Tuple
import logging

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import (
    TimeoutException,
    WebDriverException,
    NoSuchElementException,
    ElementClickInterceptedException,
)


Locator = Tuple[str, str]


@dataclass(frozen=True)
class StickerBotConfig:
    chat_url: str
    sticker_button: str                 # CSS selector кнопки со стикерами
    sticker_item: str                   # CSS selector нужного стикера
    send_button: Optional[str] = None   # если нужен отдельный шаг "Отправить"
    timeout: int = 20


class StickerSender:
    def init(self, config: StickerBotConfig, headless: bool = False):
        self.config = config
        self.logger = logging.getLogger(self.class.name)

        options = Options()
        if headless:
            options.add_argument("--headless=new")

        options.add_argument("--window-size=1280,900")
        options.add_argument("--disable-gpu")
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")

        # Если chromedriver уже в PATH, Service() можно не указывать.
        self.driver: WebDriver = webdriver.Chrome(options=options)
        self.wait = WebDriverWait(self.driver, self.config.timeout)

    def enter(self):
        return self

    def exit(self, exc_type, exc, tb):
        self.close()

    def open_chat(self) -> None:
        self.driver.get(self.config.chat_url)
        self._wait_for_page_ready()

    def send_sticker(self) -> bool:
        try:
            self._click((By.CSS_SELECTOR, self.config.sticker_button), "кнопка стикеров")
            self._click((By.CSS_SELECTOR, self.config.sticker_item), "стикер")

            if self.config.send_button:
                self._click((By.CSS_SELECTOR, self.config.send_button), "кнопка отправки")

            self.logger.info("Стикер успешно отправлен.")
            return True

        except TimeoutException:
            self.logger.error("Не удалось дождаться нужного элемента на странице.")
        except (WebDriverException, NoSuchElementException, ElementClickInterceptedException) as e:
            self.logger.error("Ошибка отправки стикера: %s", e)
        return False

    def close(self) -> None:
        if getattr(self, "driver", None):
            self.driver.quit()

    def _wait_for_page_ready(self) -> None:
        self.wait.until(lambda d: d.execute_script("return document.readyState") == "complete")

    def _click(self, locator: Locator, name: str) -> None:
        element = self.wait.until(EC.element_to_be_clickable(locator))
        self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)

        try:
            element.click()
        except ElementClickInterceptedException:
            # На случай перекрытия элементом-оверлеем
            self.driver.execute_script("arguments[0].click();", element)

        self.logger.debug("Нажата %s.", name)


if name == "main":
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    )

    config = StickerBotConfig(
        chat_url="https://example.com/chat",
        sticker_button="button.stickers",
        sticker_item='div[data-sticker-id="123"]',
        send_button=None,
        timeout=20,
    )

    with StickerSender(config, headless=False) as bot:
        bot.open_chat()
        bot.send_sticker()