Spaces:
Runtime error
Runtime error
Dmitry Beresnev
commited on
Commit
Β·
67c785a
1
Parent(s):
f74cab2
fix dockerfile and dockerfile
Browse files- Dockerfile +11 -7
- src/telegram_bot.py +88 -56
Dockerfile
CHANGED
|
@@ -2,12 +2,11 @@ FROM python:3.10-slim
|
|
| 2 |
|
| 3 |
# Set environment variables
|
| 4 |
ENV HOME=/app
|
|
|
|
| 5 |
|
| 6 |
# Create app directory and set as working dir
|
| 7 |
WORKDIR $HOME
|
| 8 |
|
| 9 |
-
ENV PYTHONPATH=$HOME
|
| 10 |
-
|
| 11 |
RUN apt-get update && apt-get install -y \
|
| 12 |
build-essential \
|
| 13 |
curl \
|
|
@@ -16,15 +15,20 @@ RUN apt-get update && apt-get install -y \
|
|
| 16 |
&& rm -rf /var/lib/apt/lists/*
|
| 17 |
|
| 18 |
COPY requirements.txt ./
|
| 19 |
-
COPY src/ ./src/
|
| 20 |
|
| 21 |
RUN pip3 install --no-cache-dir -r requirements.txt
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
| 25 |
|
| 26 |
EXPOSE 7860
|
| 27 |
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
| 2 |
|
| 3 |
# Set environment variables
|
| 4 |
ENV HOME=/app
|
| 5 |
+
ENV PYTHONPATH=$HOME
|
| 6 |
|
| 7 |
# Create app directory and set as working dir
|
| 8 |
WORKDIR $HOME
|
| 9 |
|
|
|
|
|
|
|
| 10 |
RUN apt-get update && apt-get install -y \
|
| 11 |
build-essential \
|
| 12 |
curl \
|
|
|
|
| 15 |
&& rm -rf /var/lib/apt/lists/*
|
| 16 |
|
| 17 |
COPY requirements.txt ./
|
|
|
|
| 18 |
|
| 19 |
RUN pip3 install --no-cache-dir -r requirements.txt
|
| 20 |
|
| 21 |
+
COPY src/ ./src/
|
| 22 |
+
|
| 23 |
+
ENV http_proxy=""
|
| 24 |
+
ENV https_proxy=""
|
| 25 |
|
| 26 |
EXPOSE 7860
|
| 27 |
|
| 28 |
+
# Health check for Flask app
|
| 29 |
+
HEALTHCHECK CMD curl --fail http://localhost:7860/ || exit 1
|
| 30 |
+
|
| 31 |
+
# Choose ONE of these based on your file structure:
|
| 32 |
|
| 33 |
+
# Option A: If main file is src/telegram_bot.py with Flask app
|
| 34 |
+
ENTRYPOINT ["python", "src/telegram_bot.py"]
|
src/telegram_bot.py
CHANGED
|
@@ -1,64 +1,92 @@
|
|
| 1 |
import logging
|
| 2 |
import os
|
| 3 |
-
import sys
|
| 4 |
from typing import Any
|
| 5 |
|
| 6 |
from telegram import Update
|
| 7 |
-
from telegram.ext import Application,
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
from src.financial_news_requester import fetch_comp_financial_news
|
| 10 |
from fastapi import FastAPI, Request
|
| 11 |
import uvicorn
|
| 12 |
-
import
|
| 13 |
-
|
| 14 |
|
| 15 |
logging.basicConfig(level=logging.INFO)
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
| 18 |
-
|
| 19 |
load_dotenv()
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
| 26 |
PORT = int(os.environ.get('PORT', 7860))
|
| 27 |
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
def format_news_for_telegram(news_json: list[dict[str, Any]]) -> str:
|
| 30 |
message = ""
|
| 31 |
for item in news_json:
|
| 32 |
message += (
|
| 33 |
-
f"π° <b>{item.get('headline')}</b>\n"
|
| 34 |
-
f"π {item.get('summary')}\n"
|
| 35 |
-
f"π·οΈ Source: {item.get('source')}\n"
|
| 36 |
-
f"π <a href=\"{item.get('url')}\">Read more</a>\n\n"
|
| 37 |
)
|
| 38 |
-
return message
|
| 39 |
|
| 40 |
|
| 41 |
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 42 |
-
await update.message.reply_text("Hello! I'm your Financial Bot.")
|
|
|
|
| 43 |
|
| 44 |
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 45 |
"""Handle /help command"""
|
| 46 |
-
await update.message.reply_text(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
async def run_crew(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 49 |
-
await update.message.reply_text("
|
| 50 |
try:
|
| 51 |
feed = fetch_comp_financial_news()
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
except Exception as e:
|
| 55 |
-
|
|
|
|
|
|
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
-
@app.post(f"/{
|
| 62 |
async def webhook(request: Request):
|
| 63 |
"""Handle incoming webhook from Telegram"""
|
| 64 |
try:
|
|
@@ -78,27 +106,35 @@ async def webhook(request: Request):
|
|
| 78 |
@app.get("/")
|
| 79 |
async def root():
|
| 80 |
"""Health check endpoint"""
|
| 81 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
@app.get("/set_webhook")
|
| 85 |
async def set_webhook():
|
| 86 |
"""Manually set the webhook (call this once after deployment)"""
|
| 87 |
try:
|
| 88 |
-
webhook_url = f"https://{SPACE_ID}.hf.space/{
|
| 89 |
-
|
| 90 |
-
# Use a simple HTTP request to set webhook instead of bot.set_webhook()
|
| 91 |
-
import httpx
|
| 92 |
|
| 93 |
-
|
| 94 |
|
| 95 |
-
async with httpx.AsyncClient() as client:
|
| 96 |
-
response = await client.post(set_webhook_url, json={
|
|
|
|
|
|
|
|
|
|
| 97 |
result = response.json()
|
| 98 |
|
| 99 |
if result.get("ok"):
|
|
|
|
| 100 |
return {"status": "success", "webhook_url": webhook_url, "result": result}
|
| 101 |
else:
|
|
|
|
| 102 |
return {"status": "error", "result": result}
|
| 103 |
|
| 104 |
except Exception as e:
|
|
@@ -106,37 +142,33 @@ async def set_webhook():
|
|
| 106 |
return {"status": "error", "message": str(e)}
|
| 107 |
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
app.add_handler(CommandHandler("start", start))
|
| 114 |
-
app.add_handler(CommandHandler("run", run_crew))
|
| 115 |
-
app.run_polling()
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
def test_func():
|
| 119 |
-
application = Application.builder().token(TELEGRAM_TOKEN).build()
|
| 120 |
-
application.add_handler(CommandHandler("start", start))
|
| 121 |
-
PORT = int(os.environ.get('PORT', 8000))
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
url_path=TELEGRAM_TOKEN,
|
| 128 |
-
webhook_url=f"https://{os.environ.get('SPACE_ID', SPACE_ID)}.hf.space/{TELEGRAM_TOKEN}"
|
| 129 |
-
)
|
| 130 |
|
| 131 |
|
| 132 |
if __name__ == "__main__":
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
logger.info(f"Starting bot on port {PORT}")
|
| 139 |
-
logger.info(f"Webhook URL will be: https://{SPACE_ID}.hf.space/{TELEGRAM_TOKEN}")
|
| 140 |
logger.info("After deployment, visit /set_webhook to configure the webhook")
|
| 141 |
|
| 142 |
-
uvicorn.run(app, host="0.0.0.0", port=PORT)
|
|
|
|
| 1 |
import logging
|
| 2 |
import os
|
|
|
|
| 3 |
from typing import Any
|
| 4 |
|
| 5 |
from telegram import Update
|
| 6 |
+
from telegram.ext import Application, CommandHandler, ContextTypes
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
from src.financial_news_requester import fetch_comp_financial_news
|
| 9 |
from fastapi import FastAPI, Request
|
| 10 |
import uvicorn
|
| 11 |
+
import httpx
|
|
|
|
| 12 |
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
|
|
|
| 16 |
load_dotenv()
|
| 17 |
|
| 18 |
+
# Fix 1: Use consistent token name (BOT_TOKEN is standard for HF Spaces)
|
| 19 |
+
BOT_TOKEN = os.getenv("BOT_TOKEN") or os.getenv("TELEGRAM_TOKEN")
|
| 20 |
+
if not BOT_TOKEN:
|
| 21 |
+
logger.error("BOT_TOKEN not found! Please add it in Space Settings > Repository secrets")
|
| 22 |
+
raise ValueError("BOT_TOKEN not found in environment variables")
|
| 23 |
+
|
| 24 |
+
# Fix 2: Correct SPACE_ID format
|
| 25 |
+
SPACE_ID = os.environ.get('SPACE_ID', 'researchengineering-news_sentiment_analyzer')
|
| 26 |
PORT = int(os.environ.get('PORT', 7860))
|
| 27 |
|
| 28 |
+
# Fix 3: Create application GLOBALLY so webhook can access it
|
| 29 |
+
application = Application.builder().token(BOT_TOKEN).build()
|
| 30 |
+
|
| 31 |
|
| 32 |
def format_news_for_telegram(news_json: list[dict[str, Any]]) -> str:
|
| 33 |
message = ""
|
| 34 |
for item in news_json:
|
| 35 |
message += (
|
| 36 |
+
f"π° <b>{item.get('headline', 'No headline')}</b>\n"
|
| 37 |
+
f"π {item.get('summary', 'No summary')}\n"
|
| 38 |
+
f"π·οΈ Source: {item.get('source', 'Unknown')}\n"
|
| 39 |
+
f"π <a href=\"{item.get('url', '#')}\">Read more</a>\n\n"
|
| 40 |
)
|
| 41 |
+
return message if message else "No news available."
|
| 42 |
|
| 43 |
|
| 44 |
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 45 |
+
await update.message.reply_text("Hello! I'm your Financial News Bot. Use /run to get latest news.")
|
| 46 |
+
|
| 47 |
|
| 48 |
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 49 |
"""Handle /help command"""
|
| 50 |
+
await update.message.reply_text(
|
| 51 |
+
"π€ Available commands:\n"
|
| 52 |
+
"/start - Start the bot\n"
|
| 53 |
+
"/help - Show this help\n"
|
| 54 |
+
"/run - Get latest financial news"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
|
| 58 |
async def run_crew(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| 59 |
+
await update.message.reply_text("Fetching latest financial news...")
|
| 60 |
try:
|
| 61 |
feed = fetch_comp_financial_news()
|
| 62 |
+
logger.info(f"Processed: {len(feed)} news items")
|
| 63 |
+
|
| 64 |
+
formatted_news = format_news_for_telegram(feed)
|
| 65 |
+
|
| 66 |
+
# Split message if too long (Telegram limit is 4096 characters)
|
| 67 |
+
if len(formatted_news) > 4000:
|
| 68 |
+
# Send in chunks
|
| 69 |
+
chunks = [formatted_news[i:i + 4000] for i in range(0, len(formatted_news), 4000)]
|
| 70 |
+
for chunk in chunks:
|
| 71 |
+
await update.message.reply_text(chunk, parse_mode='HTML')
|
| 72 |
+
else:
|
| 73 |
+
await update.message.reply_text(formatted_news, parse_mode='HTML')
|
| 74 |
+
|
| 75 |
except Exception as e:
|
| 76 |
+
logger.error(f"Error in run_crew: {e}")
|
| 77 |
+
await update.message.reply_text(f"Sorry, there was an error fetching news: {str(e)}")
|
| 78 |
+
|
| 79 |
|
| 80 |
+
# Add handlers to the global application
|
| 81 |
+
application.add_handler(CommandHandler("start", start))
|
| 82 |
+
application.add_handler(CommandHandler("help", help_command))
|
| 83 |
+
application.add_handler(CommandHandler("run", run_crew))
|
| 84 |
|
| 85 |
+
# Create FastAPI app
|
| 86 |
+
app = FastAPI(title="Financial News Bot", description="Telegram bot for financial news")
|
| 87 |
|
| 88 |
|
| 89 |
+
@app.post(f"/{BOT_TOKEN}")
|
| 90 |
async def webhook(request: Request):
|
| 91 |
"""Handle incoming webhook from Telegram"""
|
| 92 |
try:
|
|
|
|
| 106 |
@app.get("/")
|
| 107 |
async def root():
|
| 108 |
"""Health check endpoint"""
|
| 109 |
+
return {
|
| 110 |
+
"status": "Financial News Bot is running!",
|
| 111 |
+
"webhook_url": f"https://{SPACE_ID}.hf.space/{BOT_TOKEN[:10]}...",
|
| 112 |
+
"space_id": SPACE_ID,
|
| 113 |
+
"available_endpoints": ["/", "/set_webhook", "/webhook_info"]
|
| 114 |
+
}
|
| 115 |
|
| 116 |
|
| 117 |
@app.get("/set_webhook")
|
| 118 |
async def set_webhook():
|
| 119 |
"""Manually set the webhook (call this once after deployment)"""
|
| 120 |
try:
|
| 121 |
+
webhook_url = f"https://{SPACE_ID}.hf.space/{BOT_TOKEN}"
|
| 122 |
+
set_webhook_url = f"https://api.telegram.org/bot{BOT_TOKEN}/setWebhook"
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
logger.info(f"Setting webhook to: {webhook_url}")
|
| 125 |
|
| 126 |
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
| 127 |
+
response = await client.post(set_webhook_url, json={
|
| 128 |
+
"url": webhook_url,
|
| 129 |
+
"drop_pending_updates": True
|
| 130 |
+
})
|
| 131 |
result = response.json()
|
| 132 |
|
| 133 |
if result.get("ok"):
|
| 134 |
+
logger.info("Webhook set successfully!")
|
| 135 |
return {"status": "success", "webhook_url": webhook_url, "result": result}
|
| 136 |
else:
|
| 137 |
+
logger.error(f"Failed to set webhook: {result}")
|
| 138 |
return {"status": "error", "result": result}
|
| 139 |
|
| 140 |
except Exception as e:
|
|
|
|
| 142 |
return {"status": "error", "message": str(e)}
|
| 143 |
|
| 144 |
|
| 145 |
+
@app.get("/webhook_info")
|
| 146 |
+
async def webhook_info():
|
| 147 |
+
"""Get current webhook information"""
|
| 148 |
+
try:
|
| 149 |
+
info_url = f"https://api.telegram.org/bot{BOT_TOKEN}/getWebhookInfo"
|
| 150 |
|
| 151 |
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
| 152 |
+
response = await client.get(info_url)
|
| 153 |
+
result = response.json()
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
+
return result
|
| 156 |
+
except Exception as e:
|
| 157 |
+
logger.error(f"Error getting webhook info: {e}")
|
| 158 |
+
return {"status": "error", "message": str(e)}
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
+
@app.get("/health")
|
| 162 |
+
async def health():
|
| 163 |
+
"""Additional health check"""
|
| 164 |
+
return {"status": "healthy", "bot_token_set": bool(BOT_TOKEN)}
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
|
| 167 |
if __name__ == "__main__":
|
| 168 |
+
logger.info(f"Starting Financial News Bot on port {PORT}")
|
| 169 |
+
logger.info(f"Bot token: {BOT_TOKEN[:10]}..." if BOT_TOKEN else "No token set")
|
| 170 |
+
logger.info(f"Space ID: {SPACE_ID}")
|
| 171 |
+
logger.info(f"Webhook URL will be: https://{SPACE_ID}.hf.space/{BOT_TOKEN[:10]}...")
|
|
|
|
|
|
|
|
|
|
| 172 |
logger.info("After deployment, visit /set_webhook to configure the webhook")
|
| 173 |
|
| 174 |
+
uvicorn.run(app, host="0.0.0.0", port=PORT)
|