- Added `bot_username` to settings for dynamic retrieval of the bot's username. - Implemented `_resolve_bot_username` function to fetch the bot's username if not set, improving user experience in group chats. - Updated `DutyWithUser` schema to include optional `phone` and `username` fields for enhanced duty information. - Enhanced API responses to include contact details for users, ensuring better communication. - Introduced a new current duty view in the web app, displaying active duty information along with contact options. - Updated CSS styles for better presentation of contact information in duty cards. - Added unit tests to verify the inclusion of contact details in API responses and the functionality of the current duty view.
100 lines
3.1 KiB
Python
100 lines
3.1 KiB
Python
"""Application entry point: build bot Application, run HTTP server + polling."""
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import threading
|
|
import urllib.request
|
|
|
|
from telegram.ext import ApplicationBuilder
|
|
|
|
from duty_teller import config
|
|
from duty_teller.config import require_bot_token
|
|
from duty_teller.handlers import group_duty_pin, register_handlers
|
|
|
|
|
|
async def _resolve_bot_username(application) -> None:
|
|
"""If BOT_USERNAME is not set from env, resolve it via get_me()."""
|
|
if not config.BOT_USERNAME:
|
|
me = await application.bot.get_me()
|
|
config.BOT_USERNAME = (me.username or "").lower()
|
|
logger.info("Resolved BOT_USERNAME from API: %s", config.BOT_USERNAME)
|
|
from duty_teller.utils.http_client import safe_urlopen
|
|
|
|
logging.basicConfig(
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
level=logging.INFO,
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _set_default_menu_button_webapp() -> None:
|
|
if not (config.MINI_APP_BASE_URL and config.BOT_TOKEN):
|
|
return
|
|
menu_url = (config.MINI_APP_BASE_URL.rstrip("/") + "/app/").strip()
|
|
if not menu_url.startswith("https://"):
|
|
return
|
|
payload = {
|
|
"menu_button": {
|
|
"type": "web_app",
|
|
"text": "Calendar",
|
|
"web_app": {"url": menu_url},
|
|
}
|
|
}
|
|
req = urllib.request.Request(
|
|
f"https://api.telegram.org/bot{config.BOT_TOKEN}/setChatMenuButton",
|
|
data=json.dumps(payload).encode(),
|
|
headers={"Content-Type": "application/json"},
|
|
method="POST",
|
|
)
|
|
try:
|
|
with safe_urlopen(req, timeout=10) as resp:
|
|
if resp.status == 200:
|
|
logger.info("Default menu button set to Web App: %s", menu_url)
|
|
else:
|
|
logger.warning("setChatMenuButton returned %s", resp.status)
|
|
except Exception as e:
|
|
logger.warning("Could not set menu button: %s", e)
|
|
|
|
|
|
def _run_uvicorn(web_app, port: int) -> None:
|
|
import uvicorn
|
|
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
server = uvicorn.Server(
|
|
uvicorn.Config(web_app, host=config.HTTP_HOST, port=port, log_level="info"),
|
|
)
|
|
loop.run_until_complete(server.serve())
|
|
|
|
|
|
def main() -> None:
|
|
"""Build the bot and FastAPI, start uvicorn in a thread, run polling."""
|
|
require_bot_token()
|
|
# Optional: set bot menu button to open the Miniapp. Uncomment to enable:
|
|
# _set_default_menu_button_webapp()
|
|
app = (
|
|
ApplicationBuilder()
|
|
.token(config.BOT_TOKEN)
|
|
.post_init(group_duty_pin.restore_group_pin_jobs)
|
|
.post_init(_resolve_bot_username)
|
|
.build()
|
|
)
|
|
register_handlers(app)
|
|
|
|
from duty_teller.api.app import app as web_app
|
|
|
|
t = threading.Thread(
|
|
target=_run_uvicorn,
|
|
args=(web_app, config.HTTP_PORT),
|
|
daemon=True,
|
|
)
|
|
t.start()
|
|
|
|
if config.MINI_APP_SKIP_AUTH:
|
|
logger.warning(
|
|
"MINI_APP_SKIP_AUTH is set — API auth disabled (insecure); use only for dev"
|
|
)
|
|
logger.info("Bot starting (polling)... HTTP API on port %s", config.HTTP_PORT)
|
|
app.run_polling(allowed_updates=["message", "my_chat_member"])
|