echo_bot.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  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, group by group (the way
  45. # the UI's one-click "join group" works). Swap for selective joins if
  46. # your bot should only be present in some rooms.
  47. await bot.set_presence(PresenceStatus.ONLINE)
  48. for group in await bot.list_room_groups():
  49. joined = await bot.join_room_group(group.id)
  50. if joined:
  51. print(f"joined group {group.name!r}: {joined}")
  52. # Run until interrupted.
  53. stop = asyncio.Event()
  54. loop = asyncio.get_running_loop()
  55. for sig in (signal.SIGINT, signal.SIGTERM):
  56. with contextlib.suppress(NotImplementedError):
  57. loop.add_signal_handler(sig, stop.set)
  58. await bot.run(until=stop)
  59. print("stopped")
  60. if __name__ == "__main__":
  61. asyncio.run(main())