channels.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. import json
  2. import logging
  3. from typing import Optional
  4. from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks
  5. from pydantic import BaseModel
  6. from open_webui.socket.main import sio, get_user_ids_from_room
  7. from open_webui.models.users import Users, UserNameResponse
  8. from open_webui.models.groups import Groups
  9. from open_webui.models.channels import (
  10. Channels,
  11. ChannelModel,
  12. ChannelForm,
  13. ChannelResponse,
  14. )
  15. from open_webui.models.messages import (
  16. Messages,
  17. MessageModel,
  18. MessageResponse,
  19. MessageForm,
  20. )
  21. from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
  22. from open_webui.constants import ERROR_MESSAGES
  23. from open_webui.env import SRC_LOG_LEVELS
  24. from open_webui.utils.models import (
  25. get_all_models,
  26. get_filtered_models,
  27. )
  28. from open_webui.utils.chat import generate_chat_completion
  29. from open_webui.utils.auth import get_admin_user, get_verified_user
  30. from open_webui.utils.access_control import has_access, get_users_with_access
  31. from open_webui.utils.webhook import post_webhook
  32. from open_webui.utils.channels import extract_mentions, replace_mentions
  33. log = logging.getLogger(__name__)
  34. log.setLevel(SRC_LOG_LEVELS["MODELS"])
  35. router = APIRouter()
  36. ############################
  37. # GetChatList
  38. ############################
  39. @router.get("/", response_model=list[ChannelModel])
  40. async def get_channels(user=Depends(get_verified_user)):
  41. return Channels.get_channels_by_user_id(user.id)
  42. @router.get("/list", response_model=list[ChannelModel])
  43. async def get_all_channels(user=Depends(get_verified_user)):
  44. if user.role == "admin":
  45. return Channels.get_channels()
  46. return Channels.get_channels_by_user_id(user.id)
  47. ############################
  48. # CreateNewChannel
  49. ############################
  50. @router.post("/create", response_model=Optional[ChannelModel])
  51. async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user)):
  52. try:
  53. channel = Channels.insert_new_channel(None, form_data, user.id)
  54. return ChannelModel(**channel.model_dump())
  55. except Exception as e:
  56. log.exception(e)
  57. raise HTTPException(
  58. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  59. )
  60. ############################
  61. # GetChannelById
  62. ############################
  63. @router.get("/{id}", response_model=Optional[ChannelResponse])
  64. async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
  65. channel = Channels.get_channel_by_id(id)
  66. if not channel:
  67. raise HTTPException(
  68. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  69. )
  70. if user.role != "admin" and not has_access(
  71. user.id, type="read", access_control=channel.access_control
  72. ):
  73. raise HTTPException(
  74. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  75. )
  76. write_access = has_access(
  77. user.id, type="write", access_control=channel.access_control, strict=False
  78. )
  79. return ChannelResponse(
  80. **{
  81. **channel.model_dump(),
  82. "write_access": write_access or user.role == "admin",
  83. }
  84. )
  85. ############################
  86. # UpdateChannelById
  87. ############################
  88. @router.post("/{id}/update", response_model=Optional[ChannelModel])
  89. async def update_channel_by_id(
  90. id: str, form_data: ChannelForm, user=Depends(get_admin_user)
  91. ):
  92. channel = Channels.get_channel_by_id(id)
  93. if not channel:
  94. raise HTTPException(
  95. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  96. )
  97. try:
  98. channel = Channels.update_channel_by_id(id, form_data)
  99. return ChannelModel(**channel.model_dump())
  100. except Exception as e:
  101. log.exception(e)
  102. raise HTTPException(
  103. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  104. )
  105. ############################
  106. # DeleteChannelById
  107. ############################
  108. @router.delete("/{id}/delete", response_model=bool)
  109. async def delete_channel_by_id(id: str, user=Depends(get_admin_user)):
  110. channel = Channels.get_channel_by_id(id)
  111. if not channel:
  112. raise HTTPException(
  113. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  114. )
  115. try:
  116. Channels.delete_channel_by_id(id)
  117. return True
  118. except Exception as e:
  119. log.exception(e)
  120. raise HTTPException(
  121. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  122. )
  123. ############################
  124. # GetChannelMessages
  125. ############################
  126. class MessageUserResponse(MessageResponse):
  127. pass
  128. @router.get("/{id}/messages", response_model=list[MessageUserResponse])
  129. async def get_channel_messages(
  130. id: str, skip: int = 0, limit: int = 50, user=Depends(get_verified_user)
  131. ):
  132. channel = Channels.get_channel_by_id(id)
  133. if not channel:
  134. raise HTTPException(
  135. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  136. )
  137. if user.role != "admin" and not has_access(
  138. user.id, type="read", access_control=channel.access_control
  139. ):
  140. raise HTTPException(
  141. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  142. )
  143. message_list = Messages.get_messages_by_channel_id(id, skip, limit)
  144. users = {}
  145. messages = []
  146. for message in message_list:
  147. if message.user_id not in users:
  148. user = Users.get_user_by_id(message.user_id)
  149. users[message.user_id] = user
  150. thread_replies = Messages.get_thread_replies_by_message_id(message.id)
  151. latest_thread_reply_at = (
  152. thread_replies[0].created_at if thread_replies else None
  153. )
  154. messages.append(
  155. MessageUserResponse(
  156. **{
  157. **message.model_dump(),
  158. "reply_count": len(thread_replies),
  159. "latest_reply_at": latest_thread_reply_at,
  160. "reactions": Messages.get_reactions_by_message_id(message.id),
  161. "user": UserNameResponse(**users[message.user_id].model_dump()),
  162. }
  163. )
  164. )
  165. return messages
  166. ############################
  167. # PostNewMessage
  168. ############################
  169. async def send_notification(name, webui_url, channel, message, active_user_ids):
  170. users = get_users_with_access("read", channel.access_control)
  171. for user in users:
  172. if user.id not in active_user_ids:
  173. if user.settings:
  174. webhook_url = user.settings.ui.get("notifications", {}).get(
  175. "webhook_url", None
  176. )
  177. if webhook_url:
  178. await post_webhook(
  179. name,
  180. webhook_url,
  181. f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
  182. {
  183. "action": "channel",
  184. "message": message.content,
  185. "title": channel.name,
  186. "url": f"{webui_url}/channels/{channel.id}",
  187. },
  188. )
  189. return True
  190. async def model_response_handler(request, channel, message, user):
  191. MODELS = {
  192. model["id"]: model
  193. for model in get_filtered_models(await get_all_models(request, user=user), user)
  194. }
  195. mentions = extract_mentions(message.content)
  196. message_content = replace_mentions(message.content)
  197. model_mentions = {}
  198. # check if the message is a reply to a message sent by a model
  199. if (
  200. message.reply_to_message
  201. and message.reply_to_message.meta
  202. and message.reply_to_message.meta.get("model_id", None)
  203. ):
  204. model_id = message.reply_to_message.meta.get("model_id", None)
  205. model_mentions[model_id] = {"id": model_id, "id_type": "M"}
  206. # check if any of the mentions are models
  207. for mention in mentions:
  208. if mention["id_type"] == "M" and mention["id"] not in model_mentions:
  209. model_mentions[mention["id"]] = mention
  210. if not model_mentions:
  211. return False
  212. for mention in model_mentions.values():
  213. model_id = mention["id"]
  214. model = MODELS.get(model_id, None)
  215. if model:
  216. try:
  217. # reverse to get in chronological order
  218. thread_messages = Messages.get_messages_by_parent_id(
  219. channel.id,
  220. message.parent_id if message.parent_id else message.id,
  221. )[::-1]
  222. response_message, channel = await new_message_handler(
  223. request,
  224. channel.id,
  225. MessageForm(
  226. **{
  227. "parent_id": (
  228. message.parent_id if message.parent_id else message.id
  229. ),
  230. "content": f"",
  231. "data": {},
  232. "meta": {
  233. "model_id": model_id,
  234. "model_name": model.get("name", model_id),
  235. },
  236. }
  237. ),
  238. user,
  239. )
  240. thread_history = []
  241. images = []
  242. message_users = {}
  243. for thread_message in thread_messages:
  244. message_user = None
  245. if thread_message.user_id not in message_users:
  246. message_user = Users.get_user_by_id(thread_message.user_id)
  247. message_users[thread_message.user_id] = message_user
  248. else:
  249. message_user = message_users[thread_message.user_id]
  250. if thread_message.meta and thread_message.meta.get(
  251. "model_id", None
  252. ):
  253. # If the message was sent by a model, use the model name
  254. message_model_id = thread_message.meta.get("model_id", None)
  255. message_model = MODELS.get(message_model_id, None)
  256. username = (
  257. message_model.get("name", message_model_id)
  258. if message_model
  259. else message_model_id
  260. )
  261. else:
  262. username = message_user.name if message_user else "Unknown"
  263. thread_history.append(
  264. f"{username}: {replace_mentions(thread_message.content)}"
  265. )
  266. thread_message_files = thread_message.data.get("files", [])
  267. for file in thread_message_files:
  268. if file.get("type", "") == "image":
  269. images.append(file.get("url", ""))
  270. thread_history_string = "\n\n".join(thread_history)
  271. system_message = {
  272. "role": "system",
  273. "content": f"You are {model.get('name', model_id)}, participating in a threaded conversation. Be concise and conversational."
  274. + (
  275. f"Here's the thread history:\n\n\n{thread_history_string}\n\n\nContinue the conversation naturally as {model.get('name', model_id)}, addressing the most recent message while being aware of the full context."
  276. if thread_history
  277. else ""
  278. ),
  279. }
  280. content = f"{user.name if user else 'User'}: {message_content}"
  281. if images:
  282. content = [
  283. {
  284. "type": "text",
  285. "text": content,
  286. },
  287. *[
  288. {
  289. "type": "image_url",
  290. "image_url": {
  291. "url": image,
  292. },
  293. }
  294. for image in images
  295. ],
  296. ]
  297. form_data = {
  298. "model": model_id,
  299. "messages": [
  300. system_message,
  301. {"role": "user", "content": content},
  302. ],
  303. "stream": False,
  304. }
  305. res = await generate_chat_completion(
  306. request,
  307. form_data=form_data,
  308. user=user,
  309. )
  310. if res:
  311. if res.get("choices", []) and len(res["choices"]) > 0:
  312. await update_message_by_id(
  313. channel.id,
  314. response_message.id,
  315. MessageForm(
  316. **{
  317. "content": res["choices"][0]["message"]["content"],
  318. "meta": {
  319. "done": True,
  320. },
  321. }
  322. ),
  323. user,
  324. )
  325. elif res.get("error", None):
  326. await update_message_by_id(
  327. channel.id,
  328. response_message.id,
  329. MessageForm(
  330. **{
  331. "content": f"Error: {res['error']}",
  332. "meta": {
  333. "done": True,
  334. },
  335. }
  336. ),
  337. user,
  338. )
  339. except Exception as e:
  340. log.info(e)
  341. pass
  342. return True
  343. async def new_message_handler(
  344. request: Request, id: str, form_data: MessageForm, user=Depends(get_verified_user)
  345. ):
  346. channel = Channels.get_channel_by_id(id)
  347. if not channel:
  348. raise HTTPException(
  349. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  350. )
  351. if user.role != "admin" and not has_access(
  352. user.id, type="write", access_control=channel.access_control, strict=False
  353. ):
  354. raise HTTPException(
  355. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  356. )
  357. try:
  358. message = Messages.insert_new_message(form_data, channel.id, user.id)
  359. if message:
  360. message = Messages.get_message_by_id(message.id)
  361. event_data = {
  362. "channel_id": channel.id,
  363. "message_id": message.id,
  364. "data": {
  365. "type": "message",
  366. "data": message.model_dump(),
  367. },
  368. "user": UserNameResponse(**user.model_dump()).model_dump(),
  369. "channel": channel.model_dump(),
  370. }
  371. await sio.emit(
  372. "events:channel",
  373. event_data,
  374. to=f"channel:{channel.id}",
  375. )
  376. if message.parent_id:
  377. # If this message is a reply, emit to the parent message as well
  378. parent_message = Messages.get_message_by_id(message.parent_id)
  379. if parent_message:
  380. await sio.emit(
  381. "events:channel",
  382. {
  383. "channel_id": channel.id,
  384. "message_id": parent_message.id,
  385. "data": {
  386. "type": "message:reply",
  387. "data": parent_message.model_dump(),
  388. },
  389. "user": UserNameResponse(**user.model_dump()).model_dump(),
  390. "channel": channel.model_dump(),
  391. },
  392. to=f"channel:{channel.id}",
  393. )
  394. return message, channel
  395. else:
  396. raise Exception("Error creating message")
  397. except Exception as e:
  398. log.exception(e)
  399. raise HTTPException(
  400. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  401. )
  402. @router.post("/{id}/messages/post", response_model=Optional[MessageModel])
  403. async def post_new_message(
  404. request: Request,
  405. id: str,
  406. form_data: MessageForm,
  407. background_tasks: BackgroundTasks,
  408. user=Depends(get_verified_user),
  409. ):
  410. try:
  411. message, channel = await new_message_handler(request, id, form_data, user)
  412. active_user_ids = get_user_ids_from_room(f"channel:{channel.id}")
  413. async def background_handler():
  414. await model_response_handler(request, channel, message, user)
  415. await send_notification(
  416. request.app.state.WEBUI_NAME,
  417. request.app.state.config.WEBUI_URL,
  418. channel,
  419. message,
  420. active_user_ids,
  421. )
  422. background_tasks.add_task(background_handler)
  423. return message
  424. except HTTPException as e:
  425. raise e
  426. except Exception as e:
  427. log.exception(e)
  428. raise HTTPException(
  429. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  430. )
  431. ############################
  432. # GetChannelMessage
  433. ############################
  434. @router.get("/{id}/messages/{message_id}", response_model=Optional[MessageUserResponse])
  435. async def get_channel_message(
  436. id: str, message_id: str, user=Depends(get_verified_user)
  437. ):
  438. channel = Channels.get_channel_by_id(id)
  439. if not channel:
  440. raise HTTPException(
  441. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  442. )
  443. if user.role != "admin" and not has_access(
  444. user.id, type="read", access_control=channel.access_control
  445. ):
  446. raise HTTPException(
  447. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  448. )
  449. message = Messages.get_message_by_id(message_id)
  450. if not message:
  451. raise HTTPException(
  452. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  453. )
  454. if message.channel_id != id:
  455. raise HTTPException(
  456. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  457. )
  458. return MessageUserResponse(
  459. **{
  460. **message.model_dump(),
  461. "user": UserNameResponse(
  462. **Users.get_user_by_id(message.user_id).model_dump()
  463. ),
  464. }
  465. )
  466. ############################
  467. # GetChannelThreadMessages
  468. ############################
  469. @router.get(
  470. "/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse]
  471. )
  472. async def get_channel_thread_messages(
  473. id: str,
  474. message_id: str,
  475. skip: int = 0,
  476. limit: int = 50,
  477. user=Depends(get_verified_user),
  478. ):
  479. channel = Channels.get_channel_by_id(id)
  480. if not channel:
  481. raise HTTPException(
  482. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  483. )
  484. if user.role != "admin" and not has_access(
  485. user.id, type="read", access_control=channel.access_control
  486. ):
  487. raise HTTPException(
  488. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  489. )
  490. message_list = Messages.get_messages_by_parent_id(id, message_id, skip, limit)
  491. users = {}
  492. messages = []
  493. for message in message_list:
  494. if message.user_id not in users:
  495. user = Users.get_user_by_id(message.user_id)
  496. users[message.user_id] = user
  497. messages.append(
  498. MessageUserResponse(
  499. **{
  500. **message.model_dump(),
  501. "reply_count": 0,
  502. "latest_reply_at": None,
  503. "reactions": Messages.get_reactions_by_message_id(message.id),
  504. "user": UserNameResponse(**users[message.user_id].model_dump()),
  505. }
  506. )
  507. )
  508. return messages
  509. ############################
  510. # UpdateMessageById
  511. ############################
  512. @router.post(
  513. "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
  514. )
  515. async def update_message_by_id(
  516. id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user)
  517. ):
  518. channel = Channels.get_channel_by_id(id)
  519. if not channel:
  520. raise HTTPException(
  521. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  522. )
  523. message = Messages.get_message_by_id(message_id)
  524. if not message:
  525. raise HTTPException(
  526. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  527. )
  528. if message.channel_id != id:
  529. raise HTTPException(
  530. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  531. )
  532. if (
  533. user.role != "admin"
  534. and message.user_id != user.id
  535. and not has_access(user.id, type="read", access_control=channel.access_control)
  536. ):
  537. raise HTTPException(
  538. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  539. )
  540. try:
  541. message = Messages.update_message_by_id(message_id, form_data)
  542. message = Messages.get_message_by_id(message_id)
  543. if message:
  544. await sio.emit(
  545. "events:channel",
  546. {
  547. "channel_id": channel.id,
  548. "message_id": message.id,
  549. "data": {
  550. "type": "message:update",
  551. "data": message.model_dump(),
  552. },
  553. "user": UserNameResponse(**user.model_dump()).model_dump(),
  554. "channel": channel.model_dump(),
  555. },
  556. to=f"channel:{channel.id}",
  557. )
  558. return MessageModel(**message.model_dump())
  559. except Exception as e:
  560. log.exception(e)
  561. raise HTTPException(
  562. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  563. )
  564. ############################
  565. # AddReactionToMessage
  566. ############################
  567. class ReactionForm(BaseModel):
  568. name: str
  569. @router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool)
  570. async def add_reaction_to_message(
  571. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  572. ):
  573. channel = Channels.get_channel_by_id(id)
  574. if not channel:
  575. raise HTTPException(
  576. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  577. )
  578. if user.role != "admin" and not has_access(
  579. user.id, type="write", access_control=channel.access_control, strict=False
  580. ):
  581. raise HTTPException(
  582. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  583. )
  584. message = Messages.get_message_by_id(message_id)
  585. if not message:
  586. raise HTTPException(
  587. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  588. )
  589. if message.channel_id != id:
  590. raise HTTPException(
  591. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  592. )
  593. try:
  594. Messages.add_reaction_to_message(message_id, user.id, form_data.name)
  595. message = Messages.get_message_by_id(message_id)
  596. await sio.emit(
  597. "events:channel",
  598. {
  599. "channel_id": channel.id,
  600. "message_id": message.id,
  601. "data": {
  602. "type": "message:reaction:add",
  603. "data": {
  604. **message.model_dump(),
  605. "name": form_data.name,
  606. },
  607. },
  608. "user": UserNameResponse(**user.model_dump()).model_dump(),
  609. "channel": channel.model_dump(),
  610. },
  611. to=f"channel:{channel.id}",
  612. )
  613. return True
  614. except Exception as e:
  615. log.exception(e)
  616. raise HTTPException(
  617. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  618. )
  619. ############################
  620. # RemoveReactionById
  621. ############################
  622. @router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool)
  623. async def remove_reaction_by_id_and_user_id_and_name(
  624. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  625. ):
  626. channel = Channels.get_channel_by_id(id)
  627. if not channel:
  628. raise HTTPException(
  629. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  630. )
  631. if user.role != "admin" and not has_access(
  632. user.id, type="write", access_control=channel.access_control, strict=False
  633. ):
  634. raise HTTPException(
  635. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  636. )
  637. message = Messages.get_message_by_id(message_id)
  638. if not message:
  639. raise HTTPException(
  640. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  641. )
  642. if message.channel_id != id:
  643. raise HTTPException(
  644. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  645. )
  646. try:
  647. Messages.remove_reaction_by_id_and_user_id_and_name(
  648. message_id, user.id, form_data.name
  649. )
  650. message = Messages.get_message_by_id(message_id)
  651. await sio.emit(
  652. "events:channel",
  653. {
  654. "channel_id": channel.id,
  655. "message_id": message.id,
  656. "data": {
  657. "type": "message:reaction:remove",
  658. "data": {
  659. **message.model_dump(),
  660. "name": form_data.name,
  661. },
  662. },
  663. "user": UserNameResponse(**user.model_dump()).model_dump(),
  664. "channel": channel.model_dump(),
  665. },
  666. to=f"channel:{channel.id}",
  667. )
  668. return True
  669. except Exception as e:
  670. log.exception(e)
  671. raise HTTPException(
  672. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  673. )
  674. ############################
  675. # DeleteMessageById
  676. ############################
  677. @router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
  678. async def delete_message_by_id(
  679. id: str, message_id: str, user=Depends(get_verified_user)
  680. ):
  681. channel = Channels.get_channel_by_id(id)
  682. if not channel:
  683. raise HTTPException(
  684. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  685. )
  686. message = Messages.get_message_by_id(message_id)
  687. if not message:
  688. raise HTTPException(
  689. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  690. )
  691. if message.channel_id != id:
  692. raise HTTPException(
  693. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  694. )
  695. if (
  696. user.role != "admin"
  697. and message.user_id != user.id
  698. and not has_access(
  699. user.id, type="write", access_control=channel.access_control, strict=False
  700. )
  701. ):
  702. raise HTTPException(
  703. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  704. )
  705. try:
  706. Messages.delete_message_by_id(message_id)
  707. await sio.emit(
  708. "events:channel",
  709. {
  710. "channel_id": channel.id,
  711. "message_id": message.id,
  712. "data": {
  713. "type": "message:delete",
  714. "data": {
  715. **message.model_dump(),
  716. "user": UserNameResponse(**user.model_dump()).model_dump(),
  717. },
  718. },
  719. "user": UserNameResponse(**user.model_dump()).model_dump(),
  720. "channel": channel.model_dump(),
  721. },
  722. to=f"channel:{channel.id}",
  723. )
  724. if message.parent_id:
  725. # If this message is a reply, emit to the parent message as well
  726. parent_message = Messages.get_message_by_id(message.parent_id)
  727. if parent_message:
  728. await sio.emit(
  729. "events:channel",
  730. {
  731. "channel_id": channel.id,
  732. "message_id": parent_message.id,
  733. "data": {
  734. "type": "message:reply",
  735. "data": parent_message.model_dump(),
  736. },
  737. "user": UserNameResponse(**user.model_dump()).model_dump(),
  738. "channel": channel.model_dump(),
  739. },
  740. to=f"channel:{channel.id}",
  741. )
  742. return True
  743. except Exception as e:
  744. log.exception(e)
  745. raise HTTPException(
  746. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  747. )