groupme.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from datetime import datetime
  2. import json
  3. import traceback
  4. import asyncio
  5. import logging
  6. import toml
  7. from fastapi import FastAPI, BackgroundTasks, Response
  8. from pydantic import BaseModel
  9. import rollbot
  10. from commands import config
  11. logging.config.fileConfig("logging.conf", disable_existing_loggers=False)
  12. with open("secrets.toml", "r") as sfile:
  13. secrets = toml.load(sfile)
  14. database_file = secrets["database_file"]
  15. groupme_bots = secrets["groupme_bots"]
  16. groupme_token = secrets["groupme_token"]
  17. groupme_admins = secrets["admins"]["origin"]
  18. group_admins = secrets["admins"]["channel"]
  19. max_msg_len = 1000
  20. split_text = "\n..."
  21. msg_slice = max_msg_len - len(split_text)
  22. class GroupMeMessage(BaseModel):
  23. sender_id: str
  24. group_id: str
  25. name: str
  26. text: str
  27. created_at: int
  28. attachments: list[dict[str, str]]
  29. class GroupMeBot(rollbot.Rollbot[GroupMeMessage]):
  30. def __init__(self):
  31. super().__init__(
  32. config.extend(rollbot.CommandConfiguration(bangs=("!",))), database_file
  33. )
  34. def read_config(self, key):
  35. cfg = secrets
  36. for part in key.split("."):
  37. cfg = cfg.get(part, None)
  38. if cfg is None:
  39. return None
  40. return cfg
  41. def parse(self, msg: GroupMeMessage):
  42. return rollbot.Message(
  43. origin_id="GROUPME",
  44. channel_id=msg.group_id,
  45. sender_id=msg.sender_id,
  46. timestamp=datetime.fromtimestamp(msg.created_at),
  47. origin_admin=msg.sender_id in groupme_admins,
  48. channel_admin=msg.sender_id in group_admins.get(msg.group_id, ()),
  49. sender_name=msg.name,
  50. text=msg.text,
  51. attachments=[rollbot.Attachment(d["type"], json.dumps(d)) for d in msg.attachments],
  52. )
  53. async def upload_image(self, data: bytes):
  54. async with self.context.request.post(
  55. "https://image.groupme.com/pictures",
  56. headers={
  57. "Content-Type": "image/png",
  58. "X-Access-Token": groupme_token,
  59. },
  60. data=data,
  61. ) as upload:
  62. upload.raise_for_status()
  63. return (await upload.json())["payload"]["url"]
  64. async def post_message(self, bot_id: str, text: str, attachments: list[dict[str, str]]):
  65. await self.context.request.post(
  66. "https://api.groupme.com/v3/bots/post",
  67. json={
  68. "bot_id": bot_id,
  69. "text": text,
  70. "attachments": attachments,
  71. },
  72. timeout=10,
  73. )
  74. async def respond(self, res: rollbot.Response):
  75. if res.origin_id != "GROUPME":
  76. self.context.logger.error(f"Unable to respond to {res.origin_id}")
  77. return
  78. bot_id = groupme_bots.get(res.channel_id, None)
  79. if bot_id is None:
  80. self.context.logger.error(f"Unable to respond to group {res.channel_id} in GroupMe")
  81. return
  82. message = ""
  83. attachments = []
  84. try:
  85. for att in res.attachments:
  86. if att.name == "image":
  87. if isinstance(att.body, bytes):
  88. attachments.append(
  89. {
  90. "type": "image",
  91. "url": await self.upload_image(att.body),
  92. }
  93. )
  94. else:
  95. attachments.append({"type": "image", "url": att.body})
  96. except:
  97. self.context.debugging = "".join(traceback.format_exc())
  98. message += "Failed to upload one or more attachments!\n"
  99. self.context.logger.exception("Failed to upload attachment")
  100. if res.text is not None:
  101. message += res.text
  102. msgs = []
  103. while len(message) > max_msg_len:
  104. msgs.append(message[:msg_slice] + split_text)
  105. message = message[msg_slice:]
  106. msgs.append(message)
  107. await self.post_message(bot_id, msgs[0], attachments)
  108. for m in msgs[1:]:
  109. await asyncio.sleep(0.1)
  110. await self.post_message(bot_id, m, [])
  111. app = FastAPI()
  112. bot = GroupMeBot()
  113. @app.on_event("startup")
  114. async def startup():
  115. await bot.on_startup()
  116. @app.on_event("shutdown")
  117. async def shutdown():
  118. await bot.on_shutdown()
  119. @app.post("/", status_code=204)
  120. async def receive(message: GroupMeMessage, tasks: BackgroundTasks):
  121. tasks.add_task(bot.on_message, message)
  122. return Response(status_code=204)