channels.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  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. user: UserNameResponse
  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. replies = Messages.get_replies_by_message_id(message.id)
  151. latest_reply_at = replies[0].created_at if replies else None
  152. messages.append(
  153. MessageUserResponse(
  154. **{
  155. **message.model_dump(),
  156. "reply_count": len(replies),
  157. "latest_reply_at": latest_reply_at,
  158. "reactions": Messages.get_reactions_by_message_id(message.id),
  159. "user": UserNameResponse(**users[message.user_id].model_dump()),
  160. }
  161. )
  162. )
  163. return messages
  164. ############################
  165. # PostNewMessage
  166. ############################
  167. async def send_notification(name, webui_url, channel, message, active_user_ids):
  168. users = get_users_with_access("read", channel.access_control)
  169. for user in users:
  170. if user.id not in active_user_ids:
  171. if user.settings:
  172. webhook_url = user.settings.ui.get("notifications", {}).get(
  173. "webhook_url", None
  174. )
  175. if webhook_url:
  176. await post_webhook(
  177. name,
  178. webhook_url,
  179. f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
  180. {
  181. "action": "channel",
  182. "message": message.content,
  183. "title": channel.name,
  184. "url": f"{webui_url}/channels/{channel.id}",
  185. },
  186. )
  187. return True
  188. async def model_response_handler(request, channel, message, user):
  189. MODELS = {
  190. model["id"]: model
  191. for model in get_filtered_models(await get_all_models(request, user=user), user)
  192. }
  193. mentions = extract_mentions(message.content)
  194. message_content = replace_mentions(message.content)
  195. # check if any of the mentions are models
  196. model_mentions = [mention for mention in mentions if mention["id_type"] == "M"]
  197. if not model_mentions:
  198. return False
  199. for mention in model_mentions:
  200. model_id = mention["id"]
  201. model = MODELS.get(model_id, None)
  202. if model:
  203. try:
  204. # reverse to get in chronological order
  205. thread_messages = Messages.get_messages_by_parent_id(
  206. channel.id,
  207. message.parent_id if message.parent_id else message.id,
  208. )[::-1]
  209. response_message, channel = await new_message_handler(
  210. request,
  211. channel.id,
  212. MessageForm(
  213. **{
  214. "parent_id": (
  215. message.parent_id if message.parent_id else message.id
  216. ),
  217. "content": f"",
  218. "data": {},
  219. "meta": {
  220. "model_id": model_id,
  221. "model_name": model.get("name", model_id),
  222. },
  223. }
  224. ),
  225. user,
  226. )
  227. thread_history = []
  228. message_users = {}
  229. for thread_message in thread_messages:
  230. message_user = None
  231. if thread_message.user_id not in message_users:
  232. message_user = Users.get_user_by_id(thread_message.user_id)
  233. message_users[thread_message.user_id] = message_user
  234. else:
  235. message_user = message_users[thread_message.user_id]
  236. if thread_message.meta and thread_message.meta.get(
  237. "model_id", None
  238. ):
  239. # If the message was sent by a model, use the model name
  240. message_model_id = thread_message.meta.get("model_id", None)
  241. message_model = MODELS.get(message_model_id, None)
  242. username = (
  243. message_model.get("name", message_model_id)
  244. if message_model
  245. else message_model_id
  246. )
  247. else:
  248. username = message_user.name if message_user else "Unknown"
  249. thread_history.append(
  250. f"{username}: {replace_mentions(thread_message.content)}"
  251. )
  252. system_message = {
  253. "role": "system",
  254. "content": f"You are {model.get('name', model_id)}, an AI assistant participating in a threaded conversation. Be helpful, concise, and conversational."
  255. + (
  256. f"Here's the thread history:\n\n{''.join([f'{msg}' for msg in thread_history])}\n\nContinue the conversation naturally, addressing the most recent message while being aware of the full context."
  257. if thread_history
  258. else ""
  259. ),
  260. }
  261. form_data = {
  262. "model": model_id,
  263. "messages": [
  264. system_message,
  265. {
  266. "role": "user",
  267. "content": f"{user.name if user else 'User'}: {message_content}",
  268. },
  269. ],
  270. "stream": False,
  271. }
  272. res = await generate_chat_completion(
  273. request,
  274. form_data=form_data,
  275. user=user,
  276. )
  277. if res:
  278. await update_message_by_id(
  279. channel.id,
  280. response_message.id,
  281. MessageForm(
  282. **{
  283. "content": res["choices"][0]["message"]["content"],
  284. "meta": {
  285. "done": True,
  286. },
  287. }
  288. ),
  289. user,
  290. )
  291. except Exception as e:
  292. log.info(e)
  293. pass
  294. return True
  295. async def new_message_handler(
  296. request: Request, id: str, form_data: MessageForm, user=Depends(get_verified_user)
  297. ):
  298. channel = Channels.get_channel_by_id(id)
  299. if not channel:
  300. raise HTTPException(
  301. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  302. )
  303. if user.role != "admin" and not has_access(
  304. user.id, type="write", access_control=channel.access_control, strict=False
  305. ):
  306. raise HTTPException(
  307. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  308. )
  309. try:
  310. message = Messages.insert_new_message(form_data, channel.id, user.id)
  311. if message:
  312. event_data = {
  313. "channel_id": channel.id,
  314. "message_id": message.id,
  315. "data": {
  316. "type": "message",
  317. "data": MessageUserResponse(
  318. **{
  319. **message.model_dump(),
  320. "reply_count": 0,
  321. "latest_reply_at": None,
  322. "reactions": Messages.get_reactions_by_message_id(
  323. message.id
  324. ),
  325. "user": UserNameResponse(**user.model_dump()),
  326. }
  327. ).model_dump(),
  328. },
  329. "user": UserNameResponse(**user.model_dump()).model_dump(),
  330. "channel": channel.model_dump(),
  331. }
  332. await sio.emit(
  333. "channel-events",
  334. event_data,
  335. to=f"channel:{channel.id}",
  336. )
  337. if message.parent_id:
  338. # If this message is a reply, emit to the parent message as well
  339. parent_message = Messages.get_message_by_id(message.parent_id)
  340. if parent_message:
  341. await sio.emit(
  342. "channel-events",
  343. {
  344. "channel_id": channel.id,
  345. "message_id": parent_message.id,
  346. "data": {
  347. "type": "message:reply",
  348. "data": MessageUserResponse(
  349. **{
  350. **parent_message.model_dump(),
  351. "user": UserNameResponse(
  352. **Users.get_user_by_id(
  353. parent_message.user_id
  354. ).model_dump()
  355. ),
  356. }
  357. ).model_dump(),
  358. },
  359. "user": UserNameResponse(**user.model_dump()).model_dump(),
  360. "channel": channel.model_dump(),
  361. },
  362. to=f"channel:{channel.id}",
  363. )
  364. return MessageModel(**message.model_dump()), channel
  365. except Exception as e:
  366. log.exception(e)
  367. raise HTTPException(
  368. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  369. )
  370. @router.post("/{id}/messages/post", response_model=Optional[MessageModel])
  371. async def post_new_message(
  372. request: Request,
  373. id: str,
  374. form_data: MessageForm,
  375. background_tasks: BackgroundTasks,
  376. user=Depends(get_verified_user),
  377. ):
  378. try:
  379. message, channel = await new_message_handler(request, id, form_data, user)
  380. active_user_ids = get_user_ids_from_room(f"channel:{channel.id}")
  381. async def background_handler():
  382. await model_response_handler(request, channel, message, user)
  383. await send_notification(
  384. request.app.state.WEBUI_NAME,
  385. request.app.state.config.WEBUI_URL,
  386. channel,
  387. message,
  388. active_user_ids,
  389. )
  390. background_tasks.add_task(background_handler)
  391. return message
  392. except HTTPException as e:
  393. raise e
  394. except Exception as e:
  395. log.exception(e)
  396. raise HTTPException(
  397. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  398. )
  399. ############################
  400. # GetChannelMessage
  401. ############################
  402. @router.get("/{id}/messages/{message_id}", response_model=Optional[MessageUserResponse])
  403. async def get_channel_message(
  404. id: str, message_id: str, user=Depends(get_verified_user)
  405. ):
  406. channel = Channels.get_channel_by_id(id)
  407. if not channel:
  408. raise HTTPException(
  409. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  410. )
  411. if user.role != "admin" and not has_access(
  412. user.id, type="read", access_control=channel.access_control
  413. ):
  414. raise HTTPException(
  415. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  416. )
  417. message = Messages.get_message_by_id(message_id)
  418. if not message:
  419. raise HTTPException(
  420. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  421. )
  422. if message.channel_id != id:
  423. raise HTTPException(
  424. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  425. )
  426. return MessageUserResponse(
  427. **{
  428. **message.model_dump(),
  429. "user": UserNameResponse(
  430. **Users.get_user_by_id(message.user_id).model_dump()
  431. ),
  432. }
  433. )
  434. ############################
  435. # GetChannelThreadMessages
  436. ############################
  437. @router.get(
  438. "/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse]
  439. )
  440. async def get_channel_thread_messages(
  441. id: str,
  442. message_id: str,
  443. skip: int = 0,
  444. limit: int = 50,
  445. user=Depends(get_verified_user),
  446. ):
  447. channel = Channels.get_channel_by_id(id)
  448. if not channel:
  449. raise HTTPException(
  450. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  451. )
  452. if user.role != "admin" and not has_access(
  453. user.id, type="read", access_control=channel.access_control
  454. ):
  455. raise HTTPException(
  456. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  457. )
  458. message_list = Messages.get_messages_by_parent_id(id, message_id, skip, limit)
  459. users = {}
  460. messages = []
  461. for message in message_list:
  462. if message.user_id not in users:
  463. user = Users.get_user_by_id(message.user_id)
  464. users[message.user_id] = user
  465. messages.append(
  466. MessageUserResponse(
  467. **{
  468. **message.model_dump(),
  469. "reply_count": 0,
  470. "latest_reply_at": None,
  471. "reactions": Messages.get_reactions_by_message_id(message.id),
  472. "user": UserNameResponse(**users[message.user_id].model_dump()),
  473. }
  474. )
  475. )
  476. return messages
  477. ############################
  478. # UpdateMessageById
  479. ############################
  480. @router.post(
  481. "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
  482. )
  483. async def update_message_by_id(
  484. id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user)
  485. ):
  486. channel = Channels.get_channel_by_id(id)
  487. if not channel:
  488. raise HTTPException(
  489. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  490. )
  491. message = Messages.get_message_by_id(message_id)
  492. if not message:
  493. raise HTTPException(
  494. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  495. )
  496. if message.channel_id != id:
  497. raise HTTPException(
  498. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  499. )
  500. if (
  501. user.role != "admin"
  502. and message.user_id != user.id
  503. and not has_access(user.id, type="read", access_control=channel.access_control)
  504. ):
  505. raise HTTPException(
  506. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  507. )
  508. try:
  509. message = Messages.update_message_by_id(message_id, form_data)
  510. message = Messages.get_message_by_id(message_id)
  511. if message:
  512. await sio.emit(
  513. "channel-events",
  514. {
  515. "channel_id": channel.id,
  516. "message_id": message.id,
  517. "data": {
  518. "type": "message:update",
  519. "data": MessageUserResponse(
  520. **{
  521. **message.model_dump(),
  522. "user": UserNameResponse(
  523. **user.model_dump()
  524. ).model_dump(),
  525. }
  526. ).model_dump(),
  527. },
  528. "user": UserNameResponse(**user.model_dump()).model_dump(),
  529. "channel": channel.model_dump(),
  530. },
  531. to=f"channel:{channel.id}",
  532. )
  533. return MessageModel(**message.model_dump())
  534. except Exception as e:
  535. log.exception(e)
  536. raise HTTPException(
  537. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  538. )
  539. ############################
  540. # AddReactionToMessage
  541. ############################
  542. class ReactionForm(BaseModel):
  543. name: str
  544. @router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool)
  545. async def add_reaction_to_message(
  546. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  547. ):
  548. channel = Channels.get_channel_by_id(id)
  549. if not channel:
  550. raise HTTPException(
  551. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  552. )
  553. if user.role != "admin" and not has_access(
  554. user.id, type="write", access_control=channel.access_control, strict=False
  555. ):
  556. raise HTTPException(
  557. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  558. )
  559. message = Messages.get_message_by_id(message_id)
  560. if not message:
  561. raise HTTPException(
  562. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  563. )
  564. if message.channel_id != id:
  565. raise HTTPException(
  566. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  567. )
  568. try:
  569. Messages.add_reaction_to_message(message_id, user.id, form_data.name)
  570. message = Messages.get_message_by_id(message_id)
  571. await sio.emit(
  572. "channel-events",
  573. {
  574. "channel_id": channel.id,
  575. "message_id": message.id,
  576. "data": {
  577. "type": "message:reaction:add",
  578. "data": {
  579. **message.model_dump(),
  580. "user": UserNameResponse(
  581. **Users.get_user_by_id(message.user_id).model_dump()
  582. ).model_dump(),
  583. "name": form_data.name,
  584. },
  585. },
  586. "user": UserNameResponse(**user.model_dump()).model_dump(),
  587. "channel": channel.model_dump(),
  588. },
  589. to=f"channel:{channel.id}",
  590. )
  591. return True
  592. except Exception as e:
  593. log.exception(e)
  594. raise HTTPException(
  595. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  596. )
  597. ############################
  598. # RemoveReactionById
  599. ############################
  600. @router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool)
  601. async def remove_reaction_by_id_and_user_id_and_name(
  602. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  603. ):
  604. channel = Channels.get_channel_by_id(id)
  605. if not channel:
  606. raise HTTPException(
  607. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  608. )
  609. if user.role != "admin" and not has_access(
  610. user.id, type="write", access_control=channel.access_control, strict=False
  611. ):
  612. raise HTTPException(
  613. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  614. )
  615. message = Messages.get_message_by_id(message_id)
  616. if not message:
  617. raise HTTPException(
  618. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  619. )
  620. if message.channel_id != id:
  621. raise HTTPException(
  622. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  623. )
  624. try:
  625. Messages.remove_reaction_by_id_and_user_id_and_name(
  626. message_id, user.id, form_data.name
  627. )
  628. message = Messages.get_message_by_id(message_id)
  629. await sio.emit(
  630. "channel-events",
  631. {
  632. "channel_id": channel.id,
  633. "message_id": message.id,
  634. "data": {
  635. "type": "message:reaction:remove",
  636. "data": {
  637. **message.model_dump(),
  638. "user": UserNameResponse(
  639. **Users.get_user_by_id(message.user_id).model_dump()
  640. ).model_dump(),
  641. "name": form_data.name,
  642. },
  643. },
  644. "user": UserNameResponse(**user.model_dump()).model_dump(),
  645. "channel": channel.model_dump(),
  646. },
  647. to=f"channel:{channel.id}",
  648. )
  649. return True
  650. except Exception as e:
  651. log.exception(e)
  652. raise HTTPException(
  653. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  654. )
  655. ############################
  656. # DeleteMessageById
  657. ############################
  658. @router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
  659. async def delete_message_by_id(
  660. id: str, message_id: str, user=Depends(get_verified_user)
  661. ):
  662. channel = Channels.get_channel_by_id(id)
  663. if not channel:
  664. raise HTTPException(
  665. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  666. )
  667. message = Messages.get_message_by_id(message_id)
  668. if not message:
  669. raise HTTPException(
  670. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  671. )
  672. if message.channel_id != id:
  673. raise HTTPException(
  674. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  675. )
  676. if (
  677. user.role != "admin"
  678. and message.user_id != user.id
  679. and not has_access(
  680. user.id, type="write", access_control=channel.access_control, strict=False
  681. )
  682. ):
  683. raise HTTPException(
  684. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  685. )
  686. try:
  687. Messages.delete_message_by_id(message_id)
  688. await sio.emit(
  689. "channel-events",
  690. {
  691. "channel_id": channel.id,
  692. "message_id": message.id,
  693. "data": {
  694. "type": "message:delete",
  695. "data": {
  696. **message.model_dump(),
  697. "user": UserNameResponse(**user.model_dump()).model_dump(),
  698. },
  699. },
  700. "user": UserNameResponse(**user.model_dump()).model_dump(),
  701. "channel": channel.model_dump(),
  702. },
  703. to=f"channel:{channel.id}",
  704. )
  705. if message.parent_id:
  706. # If this message is a reply, emit to the parent message as well
  707. parent_message = Messages.get_message_by_id(message.parent_id)
  708. if parent_message:
  709. await sio.emit(
  710. "channel-events",
  711. {
  712. "channel_id": channel.id,
  713. "message_id": parent_message.id,
  714. "data": {
  715. "type": "message:reply",
  716. "data": MessageUserResponse(
  717. **{
  718. **parent_message.model_dump(),
  719. "user": UserNameResponse(
  720. **Users.get_user_by_id(
  721. parent_message.user_id
  722. ).model_dump()
  723. ),
  724. }
  725. ).model_dump(),
  726. },
  727. "user": UserNameResponse(**user.model_dump()).model_dump(),
  728. "channel": channel.model_dump(),
  729. },
  730. to=f"channel:{channel.id}",
  731. )
  732. return True
  733. except Exception as e:
  734. log.exception(e)
  735. raise HTTPException(
  736. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  737. )