Add files via upload

This commit is contained in:
imletbruh
2026-09-08 18:46:51 +05:00
committed by GitHub
parent 134fab488b
commit f23944d4c5
4 changed files with 86 additions and 21 deletions
+2
View File
@@ -306,6 +306,7 @@ class APIClient:
wait = self._rate_limit_wait_seconds(resp.headers, body) wait = self._rate_limit_wait_seconds(resp.headers, body)
wait = wait if wait is not None else DEFAULT_429_WAIT_SECONDS wait = wait if wait is not None else DEFAULT_429_WAIT_SECONDS
wait = min(wait, MAX_RATE_LIMIT_WAIT_SECONDS) wait = min(wait, MAX_RATE_LIMIT_WAIT_SECONDS)
last_error = f"rate limited (429), waited {wait:.0f}s"
logger.warning(f"Batch rate-limited (429), waiting {wait:.0f}s (attempt {attempt + 1})") logger.warning(f"Batch rate-limited (429), waiting {wait:.0f}s (attempt {attempt + 1})")
await asyncio.sleep(wait) await asyncio.sleep(wait)
continue continue
@@ -357,6 +358,7 @@ class APIClient:
wait = self._rate_limit_wait_seconds(resp.headers, body) wait = self._rate_limit_wait_seconds(resp.headers, body)
wait = wait if wait is not None else DEFAULT_429_WAIT_SECONDS wait = wait if wait is not None else DEFAULT_429_WAIT_SECONDS
wait = min(wait, MAX_RATE_LIMIT_WAIT_SECONDS) wait = min(wait, MAX_RATE_LIMIT_WAIT_SECONDS)
last_error = f"rate limited (429), waited {wait:.0f}s"
logger.warning(f"GET {path} rate-limited (429), waiting {wait:.0f}s") logger.warning(f"GET {path} rate-limited (429), waiting {wait:.0f}s")
await asyncio.sleep(wait) await asyncio.sleep(wait)
continue continue
+18 -13
View File
@@ -68,18 +68,22 @@ class AuthMiddleware(BaseMiddleware):
event: TelegramObject, event: TelegramObject,
data: dict[str, Any], data: dict[str, Any],
) -> Any: ) -> Any:
from_user = getattr(event, "from_user", None) # `event` is the raw Update; `.event` resolves it to whichever concrete
if from_user is None: # sub-object is actually set (message, callback_query, chat_member, ...).
cq = getattr(event, "callback_query", None) try:
if cq is not None: actual_event = event.event
from_user = cq.from_user except Exception:
else: actual_event = event
msg = getattr(event, "message", None) from_user = getattr(actual_event, "from_user", None)
if msg is not None:
from_user = msg.from_user
if from_user is None: if from_user is None:
return await handler(event, data) # Unknown identity (e.g. channel_post, poll, message_reaction) — fail
# closed instead of letting it through unauthenticated.
logger.warning(
f"AuthMiddleware: update without from_user "
f"(type={getattr(event, 'event_type', '?')}), denying"
)
return None
if from_user.id != self._admin_user_id: if from_user.id != self._admin_user_id:
chat_id = None chat_id = None
@@ -267,8 +271,8 @@ class AutoBumpBot:
f"⏰ Автоподнятие каждые <b>{_format_interval(interval)}</b>" f"⏰ Автоподнятие каждые <b>{_format_interval(interval)}</b>"
), ),
) )
except TelegramBadRequest as e: except Exception as e:
logger.error(f"Failed to send start message: {e}") logger.error(f"Failed to send start message: {e}", exc_info=True)
await message.answer("❌ Ошибка отправки сообщения. Попробуйте /start снова.") await message.answer("❌ Ошибка отправки сообщения. Попробуйте /start снова.")
# ─── Add Thread ───────────────────────────────────────────── # ─── Add Thread ─────────────────────────────────────────────
@@ -783,12 +787,13 @@ class AutoBumpBot:
f"✅ BUMP SUCCESS | Thread: {result.thread_id} | " f"✅ BUMP SUCCESS | Thread: {result.thread_id} | "
f"Status: {result.status.value} | Message: {result.message}" f"Status: {result.status.value} | Message: {result.message}"
) )
await self._db.update_last_bumped(result.thread_id) await self._db.record_bump_success(result.thread_id)
else: else:
logger.error( logger.error(
f"❌ BUMP FAILED | Thread: {result.thread_id} | " f"❌ BUMP FAILED | Thread: {result.thread_id} | "
f"Status: {result.status.value} | Message: {result.message}" f"Status: {result.status.value} | Message: {result.message}"
) )
await self._db.record_bump_failure(result.thread_id)
total = len(results) total = len(results)
await self._db.increment_bump_stats(success_count, total) await self._db.increment_bump_stats(success_count, total)
+1 -1
View File
@@ -94,7 +94,7 @@ class Config:
scheduling = SchedulingConfig( scheduling = SchedulingConfig(
bump_interval_minutes=interval_minutes, bump_interval_minutes=interval_minutes,
bump_delay_seconds=float(os.getenv("BUMP_DELAY_SECONDS", "2")), bump_delay_seconds=float(os.getenv("BUMP_DELAY_SECONDS", "1")),
enable_auto_bump=os.getenv("ENABLE_AUTO_BUMP", "true").lower() == "true", enable_auto_bump=os.getenv("ENABLE_AUTO_BUMP", "true").lower() == "true",
scheduler_tick_seconds=tick, scheduler_tick_seconds=tick,
) )
+65 -7
View File
@@ -73,15 +73,32 @@ class Database:
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
title TEXT NOT NULL, title TEXT NOT NULL,
last_bumped TEXT, last_bumped TEXT,
last_attempt TEXT,
fail_streak INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP created_at TEXT DEFAULT CURRENT_TIMESTAMP
) )
""") """)
# Migration for DBs created before last_attempt/fail_streak existed.
async with self._connection.execute("PRAGMA table_info(threads)") as cursor:
existing_columns = {row[1] for row in await cursor.fetchall()}
if "last_attempt" not in existing_columns:
await self._connection.execute("ALTER TABLE threads ADD COLUMN last_attempt TEXT")
if "fail_streak" not in existing_columns:
await self._connection.execute(
"ALTER TABLE threads ADD COLUMN fail_streak INTEGER NOT NULL DEFAULT 0"
)
await self._connection.execute(""" await self._connection.execute("""
CREATE INDEX IF NOT EXISTS idx_last_bumped CREATE INDEX IF NOT EXISTS idx_last_bumped
ON threads(last_bumped) ON threads(last_bumped)
""") """)
await self._connection.execute("""
CREATE INDEX IF NOT EXISTS idx_last_attempt
ON threads(last_attempt)
""")
await self._connection.execute(""" await self._connection.execute("""
CREATE TABLE IF NOT EXISTS settings ( CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY, key TEXT PRIMARY KEY,
@@ -228,7 +245,12 @@ class Database:
raise raise
async def get_threads_to_bump(self, interval_minutes: float) -> list[Thread]: async def get_threads_to_bump(self, interval_minutes: float) -> list[Thread]:
"""Threads due for a bump: the interval has passed since the last one.""" """Threads due for a bump: the interval has passed since the last attempt.
Threads that keep failing back off exponentially (interval * 2**fail_streak,
capped at 60x) so a permanently broken thread doesn't get retried on every
single tick and burn through the batch rate limit.
"""
self._ensure_connected() self._ensure_connected()
if interval_minutes < 0: if interval_minutes < 0:
raise ValueError("interval_minutes must be non-negative") raise ValueError("interval_minutes must be non-negative")
@@ -236,9 +258,20 @@ class Database:
async with self._connection.execute( async with self._connection.execute(
""" """
SELECT id, title, last_bumped FROM threads SELECT id, title, last_bumped FROM threads
WHERE last_bumped IS NULL WHERE last_attempt IS NULL
OR datetime(last_bumped, '+' || ? || ' minutes') <= datetime('now') OR datetime(
ORDER BY last_bumped ASC NULLS FIRST last_attempt,
'+' || (? * CASE
WHEN fail_streak <= 0 THEN 1
WHEN fail_streak = 1 THEN 2
WHEN fail_streak = 2 THEN 4
WHEN fail_streak = 3 THEN 8
WHEN fail_streak = 4 THEN 16
WHEN fail_streak = 5 THEN 32
ELSE 60
END) || ' minutes'
) <= datetime('now')
ORDER BY last_attempt ASC NULLS FIRST
""", """,
(interval_minutes,), (interval_minutes,),
) as cursor: ) as cursor:
@@ -248,16 +281,41 @@ class Database:
logger.error(f"Error fetching threads to bump: {e}") logger.error(f"Error fetching threads to bump: {e}")
raise raise
async def update_last_bumped(self, thread_id: str) -> None: async def record_bump_success(self, thread_id: str) -> None:
"""Mark a thread as successfully bumped and reset its failure backoff."""
self._ensure_connected() self._ensure_connected()
try: try:
await self._connection.execute( await self._connection.execute(
"UPDATE threads SET last_bumped = datetime('now') WHERE id = ?", """
UPDATE threads SET
last_bumped = datetime('now'),
last_attempt = datetime('now'),
fail_streak = 0
WHERE id = ?
""",
(thread_id,), (thread_id,),
) )
await self._connection.commit() await self._connection.commit()
except Exception as e: except Exception as e:
logger.error(f"Error updating last_bumped for thread {thread_id}: {e}") logger.error(f"Error recording bump success for thread {thread_id}: {e}")
raise
async def record_bump_failure(self, thread_id: str) -> None:
"""Record a failed bump attempt and increase its retry backoff."""
self._ensure_connected()
try:
await self._connection.execute(
"""
UPDATE threads SET
last_attempt = datetime('now'),
fail_streak = fail_streak + 1
WHERE id = ?
""",
(thread_id,),
)
await self._connection.commit()
except Exception as e:
logger.error(f"Error recording bump failure for thread {thread_id}: {e}")
raise raise
async def delete_thread(self, thread_id: str) -> bool: async def delete_thread(self, thread_id: str) -> bool: