echo_bot.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """A minimal, runnable Chatto bot using the chattolib bot framework.
  2. Run it against a preview deployment::
  3. export CHATTO_BOT_KEY="cht_BK_..."
  4. export CHATTO_BASE_URL="https://next.preview.chatto.run" # optional
  5. python examples/bot/echo_bot.py
  6. The bot:
  7. * logs in with its key (used directly as a bearer token),
  8. * sets its presence to online,
  9. * joins every room it can see,
  10. * and echoes back any message that mentions it.
  11. It also reacts to presence changes and typing, to show the full event
  12. surface. Ctrl-C stops the bot cleanly.
  13. """
  14. from __future__ import annotations
  15. import asyncio
  16. import contextlib
  17. import os
  18. import signal
  19. from chattolib.bot import Bot, BotMessageEvent, BotPresenceEvent, BotTypingEvent
  20. from chattolib.types import PresenceStatus
  21. async def on_message(bot: Bot, event: BotMessageEvent) -> None:
  22. # Only answer when we're actually mentioned, to keep things quiet.
  23. if not event.is_mention:
  24. return
  25. if event.actor is not None and event.actor.id == bot.user.id:
  26. return # ignore our own echoes
  27. await bot.reply(event, f"you said: {event.body!r}")
  28. async def on_presence(bot: Bot, event: BotPresenceEvent) -> None:
  29. print(f"[presence] {event.user_id} is now {event.status.value}")
  30. async def on_typing(bot: Bot, event: BotTypingEvent) -> None:
  31. print(f"[typing] someone is typing in {event.room_id}")
  32. async def main() -> None:
  33. key = os.environ.get("CHATTO_BOT_KEY")
  34. if not key:
  35. raise SystemExit("set CHATTO_BOT_KEY to your bot key (cht_BK_...)")
  36. base_url = os.environ.get("CHATTO_BASE_URL")
  37. async with await Bot.login(key, base_url=base_url) as bot:
  38. print(f"logged in as {bot.login_name!r} (is_bot={bot.user.is_bot})")
  39. # Register handlers. `on` returns the handler, so it doubles as a
  40. # decorator if you prefer that style.
  41. bot.on("message", lambda e: on_message(bot, e))
  42. bot.on("presence", lambda e: on_presence(bot, e))
  43. bot.on("typing", lambda e: on_typing(bot, e))
  44. # Get online and join the rooms we can see.
  45. await bot.set_presence(PresenceStatus.ONLINE)
  46. for room in await bot.list_rooms():
  47. room_id = room.room.id if room.room else ""
  48. if room_id:
  49. await bot.join_room(room_id)
  50. print(f"joined {room_id}")
  51. # Run until interrupted.
  52. stop = asyncio.Event()
  53. loop = asyncio.get_running_loop()
  54. for sig in (signal.SIGINT, signal.SIGTERM):
  55. with contextlib.suppress(NotImplementedError):
  56. loop.add_signal_handler(sig, stop.set)
  57. await bot.run(until=stop)
  58. print("stopped")
  59. if __name__ == "__main__":
  60. asyncio.run(main())