All checks were successful
CI / lint-and-test (push) Successful in 38s
- Simplified the assignment of `bot_username` in `config.py` for better clarity. - Removed redundant import statement in `run.py` to streamline the code. - Enhanced formatting in `group_duty_pin.py` and test files for improved readability and consistency. - No functional changes were made; the focus was on code style and organization.
101 lines
3.1 KiB
Python
101 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
|
|
from duty_teller.utils.http_client import safe_urlopen
|
|
|
|
|
|
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)
|
|
|
|
|
|
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"])
|