Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
# main.py (
|
2 |
import os
|
3 |
import re
|
4 |
import logging
|
@@ -41,10 +41,7 @@ except ImportError:
|
|
41 |
DEFAULT_PARSER = 'html.parser'
|
42 |
|
43 |
# --- Logging Setup ---
|
44 |
-
logging.basicConfig(
|
45 |
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
46 |
-
level=logging.INFO
|
47 |
-
)
|
48 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
49 |
logging.getLogger("telegram.ext").setLevel(logging.INFO)
|
50 |
logging.getLogger('telegram.bot').setLevel(logging.INFO)
|
@@ -61,17 +58,9 @@ ptb_app: Optional[Application] = None
|
|
61 |
# --- Environment Variable Loading & Configuration ---
|
62 |
logger.info("Attempting to load secrets and configuration...")
|
63 |
def get_secret(secret_name):
|
64 |
-
"""Gets a secret from environment variables and logs status safely."""
|
65 |
value = os.environ.get(secret_name)
|
66 |
-
if value:
|
67 |
-
|
68 |
-
# Safely log the beginning of the value
|
69 |
-
log_length = min(len(value), 8)
|
70 |
-
value_start = value[:log_length]
|
71 |
-
logger.info(f"Secret '{secret_name}': {status} (Value starts with: {value_start}...)")
|
72 |
-
else:
|
73 |
-
status = "Not Found"
|
74 |
-
logger.warning(f"Secret '{secret_name}': {status}") # Changed to warning for not found
|
75 |
return value
|
76 |
|
77 |
TELEGRAM_TOKEN = get_secret('TELEGRAM_TOKEN')
|
@@ -79,20 +68,18 @@ OPENROUTER_API_KEY = get_secret('OPENROUTER_API_KEY')
|
|
79 |
URLTOTEXT_API_KEY = get_secret('URLTOTEXT_API_KEY')
|
80 |
SUPADATA_API_KEY = get_secret('SUPADATA_API_KEY')
|
81 |
APIFY_API_TOKEN = get_secret('APIFY_API_TOKEN')
|
82 |
-
WEBHOOK_SECRET = get_secret('WEBHOOK_SECRET')
|
83 |
|
84 |
OPENROUTER_MODEL = os.environ.get("OPENROUTER_MODEL", "deepseek/deepseek-chat-v3-0324:free")
|
85 |
APIFY_ACTOR_ID = os.environ.get("APIFY_ACTOR_ID", "karamelo/youtube-transcripts")
|
86 |
|
87 |
-
# Critical checks remain the same
|
88 |
if not TELEGRAM_TOKEN: logger.critical("β FATAL: TELEGRAM_TOKEN not found."); raise RuntimeError("Exiting: Telegram token missing.")
|
89 |
if not OPENROUTER_API_KEY: logger.error("β ERROR: OPENROUTER_API_KEY not found. Summarization will fail.")
|
90 |
|
91 |
-
|
92 |
-
if not
|
93 |
-
if not
|
94 |
-
if not
|
95 |
-
if not WEBHOOK_SECRET: logger.info("Optional secret 'WEBHOOK_SECRET' not found. Webhook security disabled.") # Explicit info log
|
96 |
|
97 |
logger.info("Secret loading and configuration check finished.")
|
98 |
logger.info(f"Using OpenRouter Model: {OPENROUTER_MODEL}")
|
@@ -101,9 +88,7 @@ logger.info(f"Using Apify Actor (via REST): {APIFY_ACTOR_ID}")
|
|
101 |
_apify_token_exists = bool(APIFY_API_TOKEN)
|
102 |
|
103 |
# --- Retry Decorator ---
|
104 |
-
@retry( stop=stop_after_attempt(4), wait=wait_exponential(multiplier=1, min=2, max=15),
|
105 |
-
retry=retry_if_exception_type((NetworkError, RetryAfter, TimedOut, BadRequest)),
|
106 |
-
before_sleep=before_sleep_log(logger, logging.WARNING), reraise=True )
|
107 |
async def retry_bot_operation(func, *args, **kwargs):
|
108 |
try: return await func(*args, **kwargs)
|
109 |
except BadRequest as e:
|
@@ -295,12 +280,29 @@ async def generate_summary(text: str, summary_type: str) -> str:
|
|
295 |
if len(text) > MAX_INPUT_LENGTH: logger.warning(f"Input length ({len(text)}) exceeds limit ({MAX_INPUT_LENGTH}). Truncating."); text = text[:MAX_INPUT_LENGTH] + "... (Content truncated)"
|
296 |
full_prompt = f"{prompt}\n\n{text}"
|
297 |
headers = { "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json" }; payload = { "model": OPENROUTER_MODEL, "messages": [{"role": "user", "content": full_prompt}] }; openrouter_api_endpoint = "https://openrouter.ai/api/v1/chat/completions"
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
try:
|
299 |
-
async with httpx.AsyncClient(timeout=
|
300 |
-
logger.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
301 |
if response.status_code == 200:
|
302 |
try:
|
303 |
data = response.json()
|
|
|
304 |
if data.get("choices") and isinstance(data["choices"], list) and len(data["choices"]) > 0:
|
305 |
message = data["choices"][0].get("message")
|
306 |
if message and isinstance(message, dict):
|
@@ -311,14 +313,25 @@ async def generate_summary(text: str, summary_type: str) -> str:
|
|
311 |
else: logger.error(f"Unexpected choices structure in OpenRouter resp: {data.get('choices')}. Full: {data}"); return "Sorry, could not parse AI response (choices)."
|
312 |
except json.JSONDecodeError: logger.error(f"Failed JSON decode OpenRouter. Status:{response.status_code}. Resp:{response.text[:500]}"); return "Sorry, failed to understand AI response."
|
313 |
except Exception as e: logger.error(f"Error processing OpenRouter success response: {e}", exc_info=True); return "Sorry, error processing AI response."
|
|
|
314 |
elif response.status_code == 401: logger.error("OpenRouter API key invalid (401)."); return "Error: AI model configuration key is invalid."
|
315 |
elif response.status_code == 402: logger.error("OpenRouter Payment Required (402)."); return "Sorry, AI service limits/payment issue."
|
316 |
elif response.status_code == 429: logger.warning("OpenRouter Rate Limit Exceeded (429)."); return "Sorry, AI model is busy. Try again."
|
317 |
elif response.status_code == 500: logger.error(f"OpenRouter Internal Server Error (500). Resp:{response.text[:500]}"); return "Sorry, AI service internal error."
|
318 |
else: logger.error(f"Unexpected status {response.status_code} from OpenRouter. Resp:{response.text[:500]}"); return f"Sorry, AI service returned unexpected status ({response.status_code})."
|
319 |
-
|
320 |
-
except httpx.
|
321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
|
323 |
# --- Background Task Processing ---
|
324 |
async def process_summary_task(
|
@@ -482,7 +495,7 @@ async def lifespan(app: Starlette):
|
|
482 |
except Exception as e: logger.warning(f"Could not delete webhook: {e}"); await asyncio.sleep(1)
|
483 |
space_host = os.environ.get("SPACE_HOST"); webhook_path = "/webhook"; full_webhook_url = None
|
484 |
if space_host:
|
485 |
-
protocol = "https
|
486 |
if full_webhook_url:
|
487 |
logger.info(f"Setting webhook: {full_webhook_url}"); set_webhook_args = { "url": full_webhook_url, "allowed_updates": Update.ALL_TYPES, "drop_pending_updates": True }
|
488 |
if WEBHOOK_SECRET: set_webhook_args["secret_token"] = WEBHOOK_SECRET; logger.info("Using webhook secret.")
|
|
|
1 |
+
# main.py (Increasing timeout and adding logging in generate_summary)
|
2 |
import os
|
3 |
import re
|
4 |
import logging
|
|
|
41 |
DEFAULT_PARSER = 'html.parser'
|
42 |
|
43 |
# --- Logging Setup ---
|
44 |
+
logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO )
|
|
|
|
|
|
|
45 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
46 |
logging.getLogger("telegram.ext").setLevel(logging.INFO)
|
47 |
logging.getLogger('telegram.bot').setLevel(logging.INFO)
|
|
|
58 |
# --- Environment Variable Loading & Configuration ---
|
59 |
logger.info("Attempting to load secrets and configuration...")
|
60 |
def get_secret(secret_name):
|
|
|
61 |
value = os.environ.get(secret_name)
|
62 |
+
if value: status = "Found"; log_length = min(len(value), 8); value_start = value[:log_length]; logger.info(f"Secret '{secret_name}': {status} (Value starts with: {value_start}...)")
|
63 |
+
else: status = "Not Found"; logger.warning(f"Secret '{secret_name}': {status}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
return value
|
65 |
|
66 |
TELEGRAM_TOKEN = get_secret('TELEGRAM_TOKEN')
|
|
|
68 |
URLTOTEXT_API_KEY = get_secret('URLTOTEXT_API_KEY')
|
69 |
SUPADATA_API_KEY = get_secret('SUPADATA_API_KEY')
|
70 |
APIFY_API_TOKEN = get_secret('APIFY_API_TOKEN')
|
71 |
+
WEBHOOK_SECRET = get_secret('WEBHOOK_SECRET')
|
72 |
|
73 |
OPENROUTER_MODEL = os.environ.get("OPENROUTER_MODEL", "deepseek/deepseek-chat-v3-0324:free")
|
74 |
APIFY_ACTOR_ID = os.environ.get("APIFY_ACTOR_ID", "karamelo/youtube-transcripts")
|
75 |
|
|
|
76 |
if not TELEGRAM_TOKEN: logger.critical("β FATAL: TELEGRAM_TOKEN not found."); raise RuntimeError("Exiting: Telegram token missing.")
|
77 |
if not OPENROUTER_API_KEY: logger.error("β ERROR: OPENROUTER_API_KEY not found. Summarization will fail.")
|
78 |
|
79 |
+
if not URLTOTEXT_API_KEY: pass
|
80 |
+
if not SUPADATA_API_KEY: pass
|
81 |
+
if not APIFY_API_TOKEN: pass
|
82 |
+
if not WEBHOOK_SECRET: logger.info("Optional secret 'WEBHOOK_SECRET' not found. Webhook security disabled.")
|
|
|
83 |
|
84 |
logger.info("Secret loading and configuration check finished.")
|
85 |
logger.info(f"Using OpenRouter Model: {OPENROUTER_MODEL}")
|
|
|
88 |
_apify_token_exists = bool(APIFY_API_TOKEN)
|
89 |
|
90 |
# --- Retry Decorator ---
|
91 |
+
@retry( stop=stop_after_attempt(4), wait=wait_exponential(multiplier=1, min=2, max=15), retry=retry_if_exception_type((NetworkError, RetryAfter, TimedOut, BadRequest)), before_sleep=before_sleep_log(logger, logging.WARNING), reraise=True )
|
|
|
|
|
92 |
async def retry_bot_operation(func, *args, **kwargs):
|
93 |
try: return await func(*args, **kwargs)
|
94 |
except BadRequest as e:
|
|
|
280 |
if len(text) > MAX_INPUT_LENGTH: logger.warning(f"Input length ({len(text)}) exceeds limit ({MAX_INPUT_LENGTH}). Truncating."); text = text[:MAX_INPUT_LENGTH] + "... (Content truncated)"
|
281 |
full_prompt = f"{prompt}\n\n{text}"
|
282 |
headers = { "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json" }; payload = { "model": OPENROUTER_MODEL, "messages": [{"role": "user", "content": full_prompt}] }; openrouter_api_endpoint = "https://openrouter.ai/api/v1/chat/completions"
|
283 |
+
|
284 |
+
# *** FIX: Increase timeout and add logging ***
|
285 |
+
# Set a longer timeout (e.g., 180 seconds = 3 minutes)
|
286 |
+
api_timeout = 180.0
|
287 |
+
response = None # Initialize response variable
|
288 |
+
|
289 |
try:
|
290 |
+
async with httpx.AsyncClient(timeout=api_timeout) as client:
|
291 |
+
logger.info(f"Sending request to OpenRouter ({OPENROUTER_MODEL}) with timeout {api_timeout}s...")
|
292 |
+
response = await client.post(openrouter_api_endpoint, headers=headers, json=payload)
|
293 |
+
# Check response immediately after await returns
|
294 |
+
if response:
|
295 |
+
logger.info(f"Received response from OpenRouter. Status code: {response.status_code}")
|
296 |
+
else:
|
297 |
+
# This case should technically not happen if await returns, but good for debugging
|
298 |
+
logger.error("No response received from OpenRouter after await completed (unexpected).")
|
299 |
+
return "Sorry, communication with the AI service failed unexpectedly."
|
300 |
+
|
301 |
+
# Process the response (status code check and JSON parsing)
|
302 |
if response.status_code == 200:
|
303 |
try:
|
304 |
data = response.json()
|
305 |
+
# ... (rest of the success processing logic remains the same)
|
306 |
if data.get("choices") and isinstance(data["choices"], list) and len(data["choices"]) > 0:
|
307 |
message = data["choices"][0].get("message")
|
308 |
if message and isinstance(message, dict):
|
|
|
313 |
else: logger.error(f"Unexpected choices structure in OpenRouter resp: {data.get('choices')}. Full: {data}"); return "Sorry, could not parse AI response (choices)."
|
314 |
except json.JSONDecodeError: logger.error(f"Failed JSON decode OpenRouter. Status:{response.status_code}. Resp:{response.text[:500]}"); return "Sorry, failed to understand AI response."
|
315 |
except Exception as e: logger.error(f"Error processing OpenRouter success response: {e}", exc_info=True); return "Sorry, error processing AI response."
|
316 |
+
# ... (rest of the error status code handling remains the same)
|
317 |
elif response.status_code == 401: logger.error("OpenRouter API key invalid (401)."); return "Error: AI model configuration key is invalid."
|
318 |
elif response.status_code == 402: logger.error("OpenRouter Payment Required (402)."); return "Sorry, AI service limits/payment issue."
|
319 |
elif response.status_code == 429: logger.warning("OpenRouter Rate Limit Exceeded (429)."); return "Sorry, AI model is busy. Try again."
|
320 |
elif response.status_code == 500: logger.error(f"OpenRouter Internal Server Error (500). Resp:{response.text[:500]}"); return "Sorry, AI service internal error."
|
321 |
else: logger.error(f"Unexpected status {response.status_code} from OpenRouter. Resp:{response.text[:500]}"); return f"Sorry, AI service returned unexpected status ({response.status_code})."
|
322 |
+
|
323 |
+
except httpx.TimeoutException:
|
324 |
+
logger.error(f"Timeout error ({api_timeout}s) connecting to OpenRouter API.")
|
325 |
+
return f"Sorry, the request to the AI model timed out after {api_timeout} seconds. The content might be too long or the service busy. Please try again later or with shorter content."
|
326 |
+
except httpx.RequestError as e:
|
327 |
+
logger.error(f"Request error connecting to OpenRouter API: {e}")
|
328 |
+
return "Sorry, there was an error connecting to the AI model service."
|
329 |
+
except Exception as e:
|
330 |
+
# Catch any other unexpected errors during the request/response cycle
|
331 |
+
logger.error(f"Unexpected error in generate_summary (OpenRouter request phase): {e}", exc_info=True)
|
332 |
+
# Log response status if available
|
333 |
+
if response: logger.error(f"--> Last response status before error: {response.status_code}")
|
334 |
+
return "Sorry, an unexpected error occurred while trying to generate the summary."
|
335 |
|
336 |
# --- Background Task Processing ---
|
337 |
async def process_summary_task(
|
|
|
495 |
except Exception as e: logger.warning(f"Could not delete webhook: {e}"); await asyncio.sleep(1)
|
496 |
space_host = os.environ.get("SPACE_HOST"); webhook_path = "/webhook"; full_webhook_url = None
|
497 |
if space_host:
|
498 |
+
protocol = "https"; host = space_host.split('://')[-1]; full_webhook_url = f"{protocol}://{host.rstrip('/')}{webhook_path}" # Corrected URL construction
|
499 |
if full_webhook_url:
|
500 |
logger.info(f"Setting webhook: {full_webhook_url}"); set_webhook_args = { "url": full_webhook_url, "allowed_updates": Update.ALL_TYPES, "drop_pending_updates": True }
|
501 |
if WEBHOOK_SECRET: set_webhook_args["secret_token"] = WEBHOOK_SECRET; logger.info("Using webhook secret.")
|