fix some
This commit is contained in:
+11
-3
@@ -1124,8 +1124,9 @@ class BotInterface:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
chat_id = params.chat_id or callback.message.chat.id
|
||||||
msg_mock = SimpleNamespace(
|
msg_mock = SimpleNamespace(
|
||||||
chat=SimpleNamespace(id=params.chat_id),
|
chat=SimpleNamespace(id=chat_id),
|
||||||
bot=callback.message.bot,
|
bot=callback.message.bot,
|
||||||
)
|
)
|
||||||
await interface.background_tasks.cancel_task(tid)
|
await interface.background_tasks.cancel_task(tid)
|
||||||
@@ -1133,7 +1134,7 @@ class BotInterface:
|
|||||||
task_id=tid,
|
task_id=tid,
|
||||||
coro=interface._run_twitch(tid, params, msg_mock),
|
coro=interface._run_twitch(tid, params, msg_mock),
|
||||||
task_type="twitch_irc",
|
task_type="twitch_irc",
|
||||||
metadata={"type": "twitch_irc", "channel": params.channel, "chat_id": params.chat_id},
|
metadata={"type": "twitch_irc", "channel": params.channel, "chat_id": chat_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
@@ -2062,11 +2063,14 @@ class BotInterface:
|
|||||||
from services.irc_service import TwitchIRCClient
|
from services.irc_service import TwitchIRCClient
|
||||||
|
|
||||||
irc = TwitchIRCClient(params.channel, params.target_username)
|
irc = TwitchIRCClient(params.channel, params.target_username)
|
||||||
|
url_tasks: set = set()
|
||||||
|
|
||||||
async def on_url(url, username):
|
async def on_url(url, username):
|
||||||
if params.stream_offline:
|
if params.stream_offline:
|
||||||
return # не обрабатываем ссылки пока стрим оффлайн
|
return
|
||||||
task = asyncio.create_task(self._process_url(url, username, params, message))
|
task = asyncio.create_task(self._process_url(url, username, params, message))
|
||||||
|
url_tasks.add(task)
|
||||||
|
task.add_done_callback(url_tasks.discard)
|
||||||
task.add_done_callback(
|
task.add_done_callback(
|
||||||
lambda t: logger.error(f"URL processing failed: {t.exception()}") if not t.cancelled() and t.exception() else None
|
lambda t: logger.error(f"URL processing failed: {t.exception()}") if not t.cancelled() and t.exception() else None
|
||||||
)
|
)
|
||||||
@@ -2098,6 +2102,10 @@ class BotInterface:
|
|||||||
logger.error(f"IRC error [{params.channel}]: {e}")
|
logger.error(f"IRC error [{params.channel}]: {e}")
|
||||||
finally:
|
finally:
|
||||||
watcher.cancel()
|
watcher.cancel()
|
||||||
|
for t in list(url_tasks):
|
||||||
|
t.cancel()
|
||||||
|
if url_tasks:
|
||||||
|
await asyncio.gather(*url_tasks, return_exceptions=True)
|
||||||
await irc.disconnect()
|
await irc.disconnect()
|
||||||
if self.storage:
|
if self.storage:
|
||||||
await self.storage.save_task(task_id, params)
|
await self.storage.save_task(task_id, params)
|
||||||
|
|||||||
@@ -106,7 +106,10 @@ class BotApplication:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Активные задачи ставим на паузу и перезапускаем
|
# Активные задачи ставим на паузу и перезапускаем
|
||||||
|
# Если стрим был оффлайн — сохраняем этот статус
|
||||||
params.paused = True
|
params.paused = True
|
||||||
|
if params.stream_offline:
|
||||||
|
logger.info(f"Task {task_id}: stream was offline, keeping paused")
|
||||||
await self.interface.task_manager.add_task(task_id, params)
|
await self.interface.task_manager.add_task(task_id, params)
|
||||||
|
|
||||||
if params.task_type == "twitch_irc" and params.chat_id:
|
if params.task_type == "twitch_irc" and params.chat_id:
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class TaskStorage:
|
|||||||
"min_ctr": params.min_ctr,
|
"min_ctr": params.min_ctr,
|
||||||
"max_ctr": params.max_ctr,
|
"max_ctr": params.max_ctr,
|
||||||
"started_at": params.started_at.isoformat() if params.started_at else None,
|
"started_at": params.started_at.isoformat() if params.started_at else None,
|
||||||
|
"stream_offline": params.stream_offline,
|
||||||
|
# pending_visits и total_planned_visits — runtime, не сохраняем
|
||||||
}
|
}
|
||||||
|
|
||||||
def _dict_to_params(self, data: dict) -> TaskParams:
|
def _dict_to_params(self, data: dict) -> TaskParams:
|
||||||
@@ -85,6 +87,7 @@ class TaskStorage:
|
|||||||
max_series=data.get("max_series", data.get("visits_per_link", 1)),
|
max_series=data.get("max_series", data.get("visits_per_link", 1)),
|
||||||
min_ctr=data.get("min_ctr", 0.8),
|
min_ctr=data.get("min_ctr", 0.8),
|
||||||
max_ctr=data.get("max_ctr", 1.0),
|
max_ctr=data.get("max_ctr", 1.0),
|
||||||
|
stream_offline=data.get("stream_offline", False),
|
||||||
)
|
)
|
||||||
if data.get("started_at"):
|
if data.get("started_at"):
|
||||||
try:
|
try:
|
||||||
|
|||||||
+11
-10
@@ -21,16 +21,9 @@ from services.socks5_to_http_proxy import Socks5ProxyPool
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_current_proxy_ip: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
def _get_real_ip() -> Optional[str]:
|
|
||||||
return _current_proxy_ip
|
|
||||||
|
|
||||||
|
|
||||||
def _set_real_ip(ip: Optional[str]) -> None:
|
def _set_real_ip(ip: Optional[str]) -> None:
|
||||||
global _current_proxy_ip
|
import threading
|
||||||
_current_proxy_ip = ip
|
threading.current_thread()._proxy_ip = ip
|
||||||
|
|
||||||
|
|
||||||
def _patch_camoufox():
|
def _patch_camoufox():
|
||||||
@@ -63,7 +56,9 @@ def _patch_camoufox():
|
|||||||
_orig_ip = _ci.public_ip
|
_orig_ip = _ci.public_ip
|
||||||
|
|
||||||
def _patched_ip(proxy_string: str = None) -> str:
|
def _patched_ip(proxy_string: str = None) -> str:
|
||||||
ip = _get_real_ip()
|
# Вызывается из thread pool — используем thread-local атрибут
|
||||||
|
import threading
|
||||||
|
ip = getattr(threading.current_thread(), "_proxy_ip", None)
|
||||||
if ip:
|
if ip:
|
||||||
logger.debug(f"public_ip() intercepted → returning {ip}")
|
logger.debug(f"public_ip() intercepted → returning {ip}")
|
||||||
return ip
|
return ip
|
||||||
@@ -140,6 +135,7 @@ class BrowserService:
|
|||||||
|
|
||||||
proxy_locale = None
|
proxy_locale = None
|
||||||
|
|
||||||
|
try:
|
||||||
if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]:
|
if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]:
|
||||||
proxy_config = await self.socks5_pool.get_proxy_config(proxy)
|
proxy_config = await self.socks5_pool.get_proxy_config(proxy)
|
||||||
use_socks5 = True
|
use_socks5 = True
|
||||||
@@ -158,6 +154,11 @@ class BrowserService:
|
|||||||
proxy_config = None
|
proxy_config = None
|
||||||
_set_real_ip(None)
|
_set_real_ip(None)
|
||||||
logger.info("Direct connection")
|
logger.info("Direct connection")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# Гарантируем освобождение прокси даже при отмене задачи
|
||||||
|
if proxy:
|
||||||
|
await self.proxy_manager.release_proxy(proxy, success=False)
|
||||||
|
raise
|
||||||
|
|
||||||
if proxy_config:
|
if proxy_config:
|
||||||
camoufox_kwargs = dict(
|
camoufox_kwargs = dict(
|
||||||
|
|||||||
@@ -373,8 +373,13 @@ class Socks5ProxyPool:
|
|||||||
"""Получает или создает конфигурацию HTTP прокси для SOCKS5."""
|
"""Получает или создает конфигурацию HTTP прокси для SOCKS5."""
|
||||||
key = socks5_proxy.id
|
key = socks5_proxy.id
|
||||||
|
|
||||||
|
# Быстрая проверка под lock'ом
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
if key not in self._proxies:
|
if key in self._proxies:
|
||||||
|
self._last_used[key] = asyncio.get_running_loop().time()
|
||||||
|
return self._proxies[key].proxy_config_for_browser
|
||||||
|
|
||||||
|
# proxy.start() — медленная операция, запускаем вне lock'а
|
||||||
proxy = Socks5ToHttpProxy(
|
proxy = Socks5ToHttpProxy(
|
||||||
socks5_host=socks5_proxy.ip,
|
socks5_host=socks5_proxy.ip,
|
||||||
socks5_port=int(socks5_proxy.port),
|
socks5_port=int(socks5_proxy.port),
|
||||||
@@ -382,10 +387,14 @@ class Socks5ProxyPool:
|
|||||||
password=socks5_proxy.password
|
password=socks5_proxy.password
|
||||||
)
|
)
|
||||||
await proxy.start()
|
await proxy.start()
|
||||||
self._proxies[key] = proxy
|
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
# Добавляем под lock'ом (двойная проверка — другая задача могла успеть)
|
||||||
self._last_used[key] = loop.time()
|
async with self._lock:
|
||||||
|
if key not in self._proxies:
|
||||||
|
self._proxies[key] = proxy
|
||||||
|
else:
|
||||||
|
await proxy.stop() # уже создан другой задачей
|
||||||
|
self._last_used[key] = asyncio.get_running_loop().time()
|
||||||
return self._proxies[key].proxy_config_for_browser
|
return self._proxies[key].proxy_config_for_browser
|
||||||
|
|
||||||
async def release(self, socks5_proxy):
|
async def release(self, socks5_proxy):
|
||||||
|
|||||||
Reference in New Issue
Block a user