import os
import time
import telebot
import logging
from flask import Flask, request, abort
import db
import keyboards as kb
import weather_api as wa
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Инициализация переменных окружения
TG_TOKEN = os.getenv("TG_TOKEN")
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")
ADMIN_ID = os.getenv("ADMIN_ID")
WEBHOOK_HOST = os.getenv("WEBHOOK_HOST") # e.g., https://my-bot.com
WEBHOOK_PORT = int(os.getenv("WEBHOOK_PORT", 8080))
if not TG_TOKEN or not WEATHER_API_KEY:
logger.error(
"Не заданы переменные окружения TG_TOKEN или WEATHER_API_KEY!")
exit(1)
if not WEBHOOK_HOST:
logger.error(
"Не задана переменная WEBHOOK_HOST! (например: https://mydomain.com)")
exit(1)
# Формируем URL вебхука (скрываем токен в пути для безопасности)
WEBHOOK_PATH = f"/{TG_TOKEN}/"
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
# Инициализация БД
db.init_db()
# Инициализация бота
bot = telebot.TeleBot(TG_TOKEN)
# Инициализация Flask приложения
app = Flask(__name__)
def get_user_city(user_id):
user = db.get_user(user_id)
if user and user['default_city']:
return user['default_city']
return None
@bot.message_handler(commands=['start', 'help'])
def send_welcome(message):
user_id = message.from_user.id
username = message.from_user.username
# Добавляем юзера в БД, если его еще нет
db.add_user(user_id, username)
welcome_text = (
"Привет! 🌍 Я твой погодный бот.\n\n"
"Я могу показывать текущую погоду и прогноз на несколько дней.\n"
"Для удобства давай установим твой город по умолчанию.")
user_city = get_user_city(user_id)
if user_city:
welcome_text += f"\n\nТвой текущий город: {user_city}"
bot.send_message(message.chat.id,
welcome_text,
reply_markup=kb.get_main_menu(),
parse_mode="HTML")
else:
bot.send_message(message.chat.id, welcome_text, parse_mode="HTML")
msg = bot.send_message(
message.chat.id,
"Напиши название города, который хочешь установить по умолчанию:",
reply_markup=kb.get_cancel_menu())
bot.register_next_step_handler(msg, process_city_input)
@bot.message_handler(commands=['stats'])
def admin_stats(message):
if ADMIN_ID and str(message.from_user.id) == str(ADMIN_ID):
count = db.get_users_count()
bot.send_message(message.chat.id,
f"📊 Уникальных пользователей в базе: {count}")
@bot.message_handler(func=lambda message: message.text == "Настройки")
def settings_menu(message):
user_city = get_user_city(message.from_user.id)
text = "⚙️ Настройки\n"
if user_city:
text += f"Текущий город: {user_city}"
else:
text += "Город по умолчанию не установлен."
bot.send_message(message.chat.id,
text,
reply_markup=kb.get_settings_menu(),
parse_mode="HTML")
@bot.message_handler(func=lambda message: message.text == "🔙 Назад в меню")
def back_to_main(message):
bot.send_message(message.chat.id,
"Возвращаемся в главное меню.",
reply_markup=kb.get_main_menu())
@bot.message_handler(func=lambda message: message.text == "Изменить город")
def change_city_prompt(message):
msg = bot.send_message(message.chat.id,
"Напиши название нового города:",
reply_markup=kb.get_cancel_menu())
bot.register_next_step_handler(msg, process_city_input)
def process_city_input(message):
if message.text == "Отмена":
bot.send_message(message.chat.id,
"Действие отменено.",
reply_markup=kb.get_main_menu())
return
city = message.text.strip()
weather_data = wa.get_current_weather(city)
if weather_data["success"]:
real_city_name = weather_data["city"]
text = (f"Город {real_city_name} найден!\n"
f"Текущая температура: {weather_data['temp']}°C\n\n"
f"Сохранить этот город как основной?")
bot.send_message(
message.chat.id,
text,
reply_markup=kb.get_city_confirm_inline(real_city_name),
parse_mode="HTML")
else:
msg = bot.send_message(
message.chat.id,
f"❌ {weather_data['error']}\nПопробуй ввести название еще раз или нажми 'Отмена'.",
reply_markup=kb.get_cancel_menu())
bot.register_next_step_handler(msg, process_city_input)
@bot.callback_query_handler(
func=lambda call: call.data.startswith('save_city:'))
def callback_save_city(call):
city_name = call.data.split(':', 1)[1]
user_id = call.from_user.id
db.update_default_city(user_id, city_name)
bot.edit_message_text(chat_id=call.message.chat.id,
message_id=call.message.message_id,
text=f"✅ Город {city_name} успешно сохранен!",
parse_mode="HTML")
bot.send_message(call.message.chat.id,
"Открываю главное меню:",
reply_markup=kb.get_main_menu())
bot.answer_callback_query(call.id)
@bot.callback_query_handler(func=lambda call: call.data == 'cancel_city')
def callback_cancel_city(call):
bot.edit_message_text(chat_id=call.message.chat.id,
message_id=call.message.message_id,
text="❌ Изменение города отменено.")
bot.send_message(call.message.chat.id,
"Возвращаемся в главное меню:",
reply_markup=kb.get_main_menu())
bot.answer_callback_query(call.id)
@bot.message_handler(
func=lambda message: message.text in
["Текущая погода", "Прогноз на 3 дня", "Прогноз на 5 дней"])
def weather_requests(message):
user_city = get_user_city(message.from_user.id)
if not user_city:
msg = bot.send_message(message.chat.id,
"Сначала укажите город. Напишите название:",
reply_markup=kb.get_cancel_menu())
bot.register_next_step_handler(msg, process_city_input)
return
bot.send_chat_action(message.chat.id, 'typing')
if message.text == "Текущая погода":
data = wa.get_current_weather(user_city)
if data["success"]:
answer = f"🌡 Погода в городе {data['city']}:\n"
answer += f"Температура: {data['temp']}°C\n"
answer += f"На улице: {data['desc']}."
bot.send_message(message.chat.id, answer, parse_mode="HTML")
else:
bot.send_message(message.chat.id, f"❌ Ошибка: {data['error']}")
elif message.text == "Прогноз на 3 дня" or message.text == "Прогноз на 5 дней":
days = 3 if "3" in message.text else 5
data = wa.get_forecast(user_city, days)
if data["success"]:
answer = f"📅 Прогноз погоды для {data['city']} на {days} дн.:\n\n"
for day in data["forecast"]:
answer += f"🔹 {day['date']}\n"
answer += f" Мин: {day['min_temp']}°C | Макс: {day['max_temp']}°C\n"
answer += f" {day['desc']}\n\n"
bot.send_message(message.chat.id, answer, parse_mode="HTML")
else:
bot.send_message(message.chat.id, f"❌ Ошибка: {data['error']}")
@bot.message_handler(func=lambda message: True)
def unknown_text(message):
city = message.text.strip()
bot.send_chat_action(message.chat.id, 'typing')
data = wa.get_current_weather(city)
if data["success"]:
answer = f"🌡 Текущая погода (разовый запрос) в {data['city']}:\n"
answer += f"Температура: {data['temp']}°C\n"
answer += f"На улице: {data['desc']}."
bot.send_message(message.chat.id, answer, parse_mode="HTML")
else:
bot.send_message(
message.chat.id,
f"❌ {data['error']}\nИспользуйте меню или введите корректное название города.",
reply_markup=kb.get_main_menu())
# --- ВЕБХУКИ (Flask) ---
@app.route(WEBHOOK_PATH, methods=['POST'])
def webhook():
if request.headers.get('content-type') == 'application/json':
json_string = request.get_data().decode('utf-8')
update = telebot.types.Update.de_json(json_string)
bot.process_new_updates([update])
return '', 200
else:
abort(403)
if __name__ == "__main__":
logger.info("Удаляем старый вебхук...")
bot.remove_webhook()
time.sleep(1)
logger.info(f"Устанавливаем новый вебхук: {WEBHOOK_URL}")
bot.set_webhook(url=WEBHOOK_URL)
logger.info(f"Flask сервер запущен на порту {WEBHOOK_PORT}")
# Для продакшена лучше использовать Gunicorn, но для Docker часто достаточно и встроенного (или Waitress)
app.run(host='0.0.0.0', port=WEBHOOK_PORT)