channels.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  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.channels import Channels, ChannelModel, ChannelForm
  9. from open_webui.models.messages import (
  10. Messages,
  11. MessageModel,
  12. MessageResponse,
  13. MessageForm,
  14. )
  15. from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
  16. from open_webui.constants import ERROR_MESSAGES
  17. from open_webui.env import SRC_LOG_LEVELS
  18. from open_webui.utils.auth import get_admin_user, get_verified_user
  19. from open_webui.utils.access_control import has_access, get_users_with_access
  20. from open_webui.utils.webhook import post_webhook
  21. log = logging.getLogger(__name__)
  22. log.setLevel(SRC_LOG_LEVELS["MODELS"])
  23. router = APIRouter()
  24. ############################
  25. # GetChatList
  26. ############################
  27. @router.get("/", response_model=list[ChannelModel])
  28. async def get_channels(user=Depends(get_verified_user)):
  29. return Channels.get_channels_by_user_id(user.id)
  30. @router.get("/list", response_model=list[ChannelModel])
  31. async def get_all_channels(user=Depends(get_verified_user)):
  32. if user.role == "admin":
  33. return Channels.get_channels()
  34. return Channels.get_channels_by_user_id(user.id)
  35. ############################
  36. # CreateNewChannel
  37. ############################
  38. @router.post("/create", response_model=Optional[ChannelModel])
  39. async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user)):
  40. try:
  41. channel = Channels.insert_new_channel(None, form_data, user.id)
  42. return ChannelModel(**channel.model_dump())
  43. except Exception as e:
  44. log.exception(e)
  45. raise HTTPException(
  46. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  47. )
  48. ############################
  49. # GetChannelById
  50. ############################
  51. @router.get("/{id}", response_model=Optional[ChannelModel])
  52. async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
  53. channel = Channels.get_channel_by_id(id)
  54. if not channel:
  55. raise HTTPException(
  56. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  57. )
  58. if user.role != "admin" and not has_access(
  59. user.id, type="read", access_control=channel.access_control
  60. ):
  61. raise HTTPException(
  62. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  63. )
  64. return ChannelModel(**channel.model_dump())
  65. ############################
  66. # UpdateChannelById
  67. ############################
  68. @router.post("/{id}/update", response_model=Optional[ChannelModel])
  69. async def update_channel_by_id(
  70. id: str, form_data: ChannelForm, user=Depends(get_admin_user)
  71. ):
  72. channel = Channels.get_channel_by_id(id)
  73. if not channel:
  74. raise HTTPException(
  75. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  76. )
  77. try:
  78. channel = Channels.update_channel_by_id(id, form_data)
  79. return ChannelModel(**channel.model_dump())
  80. except Exception as e:
  81. log.exception(e)
  82. raise HTTPException(
  83. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  84. )
  85. ############################
  86. # DeleteChannelById
  87. ############################
  88. @router.delete("/{id}/delete", response_model=bool)
  89. async def delete_channel_by_id(id: str, user=Depends(get_admin_user)):
  90. channel = Channels.get_channel_by_id(id)
  91. if not channel:
  92. raise HTTPException(
  93. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  94. )
  95. try:
  96. Channels.delete_channel_by_id(id)
  97. return True
  98. except Exception as e:
  99. log.exception(e)
  100. raise HTTPException(
  101. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  102. )
  103. ############################
  104. # GetChannelMessages
  105. ############################
  106. class MessageUserResponse(MessageResponse):
  107. user: UserNameResponse
  108. @router.get("/{id}/messages", response_model=list[MessageUserResponse])
  109. async def get_channel_messages(
  110. id: str, skip: int = 0, limit: int = 50, user=Depends(get_verified_user)
  111. ):
  112. channel = Channels.get_channel_by_id(id)
  113. if not channel:
  114. raise HTTPException(
  115. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  116. )
  117. if user.role != "admin" and not has_access(
  118. user.id, type="read", access_control=channel.access_control
  119. ):
  120. raise HTTPException(
  121. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  122. )
  123. message_list = Messages.get_messages_by_channel_id(id, skip, limit)
  124. users = {}
  125. messages = []
  126. for message in message_list:
  127. if message.user_id not in users:
  128. user = Users.get_user_by_id(message.user_id)
  129. users[message.user_id] = user
  130. replies = Messages.get_replies_by_message_id(message.id)
  131. latest_reply_at = replies[0].created_at if replies else None
  132. messages.append(
  133. MessageUserResponse(
  134. **{
  135. **message.model_dump(),
  136. "reply_count": len(replies),
  137. "latest_reply_at": latest_reply_at,
  138. "reactions": Messages.get_reactions_by_message_id(message.id),
  139. "user": UserNameResponse(**users[message.user_id].model_dump()),
  140. }
  141. )
  142. )
  143. return messages
  144. ############################
  145. # PostNewMessage
  146. ############################
  147. async def send_notification(name, webui_url, channel, message, active_user_ids):
  148. users = get_users_with_access("read", channel.access_control)
  149. for user in users:
  150. if user.id in active_user_ids:
  151. continue
  152. else:
  153. if user.settings:
  154. webhook_url = user.settings.ui.get("notifications", {}).get(
  155. "webhook_url", None
  156. )
  157. if webhook_url:
  158. await post_webhook(
  159. name,
  160. webhook_url,
  161. f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
  162. {
  163. "action": "channel",
  164. "message": message.content,
  165. "title": channel.name,
  166. "url": f"{webui_url}/channels/{channel.id}",
  167. },
  168. )
  169. @router.post("/{id}/messages/post", response_model=Optional[MessageModel])
  170. async def post_new_message(
  171. request: Request,
  172. id: str,
  173. form_data: MessageForm,
  174. background_tasks: BackgroundTasks,
  175. user=Depends(get_verified_user),
  176. ):
  177. channel = Channels.get_channel_by_id(id)
  178. if not channel:
  179. raise HTTPException(
  180. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  181. )
  182. if user.role != "admin" and not has_access(
  183. user.id, type="read", access_control=channel.access_control
  184. ):
  185. raise HTTPException(
  186. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  187. )
  188. try:
  189. message = Messages.insert_new_message(form_data, channel.id, user.id)
  190. if message:
  191. event_data = {
  192. "channel_id": channel.id,
  193. "message_id": message.id,
  194. "data": {
  195. "type": "message",
  196. "data": MessageUserResponse(
  197. **{
  198. **message.model_dump(),
  199. "reply_count": 0,
  200. "latest_reply_at": None,
  201. "reactions": Messages.get_reactions_by_message_id(
  202. message.id
  203. ),
  204. "user": UserNameResponse(**user.model_dump()),
  205. }
  206. ).model_dump(),
  207. },
  208. "user": UserNameResponse(**user.model_dump()).model_dump(),
  209. "channel": channel.model_dump(),
  210. }
  211. await sio.emit(
  212. "channel-events",
  213. event_data,
  214. to=f"channel:{channel.id}",
  215. )
  216. if message.parent_id:
  217. # If this message is a reply, emit to the parent message as well
  218. parent_message = Messages.get_message_by_id(message.parent_id)
  219. if parent_message:
  220. await sio.emit(
  221. "channel-events",
  222. {
  223. "channel_id": channel.id,
  224. "message_id": parent_message.id,
  225. "data": {
  226. "type": "message:reply",
  227. "data": MessageUserResponse(
  228. **{
  229. **parent_message.model_dump(),
  230. "user": UserNameResponse(
  231. **Users.get_user_by_id(
  232. parent_message.user_id
  233. ).model_dump()
  234. ),
  235. }
  236. ).model_dump(),
  237. },
  238. "user": UserNameResponse(**user.model_dump()).model_dump(),
  239. "channel": channel.model_dump(),
  240. },
  241. to=f"channel:{channel.id}",
  242. )
  243. active_user_ids = get_user_ids_from_room(f"channel:{channel.id}")
  244. background_tasks.add_task(
  245. send_notification,
  246. request.app.state.WEBUI_NAME,
  247. request.app.state.config.WEBUI_URL,
  248. channel,
  249. message,
  250. active_user_ids,
  251. )
  252. return MessageModel(**message.model_dump())
  253. except Exception as e:
  254. log.exception(e)
  255. raise HTTPException(
  256. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  257. )
  258. ############################
  259. # GetChannelMessage
  260. ############################
  261. @router.get("/{id}/messages/{message_id}", response_model=Optional[MessageUserResponse])
  262. async def get_channel_message(
  263. id: str, message_id: str, user=Depends(get_verified_user)
  264. ):
  265. channel = Channels.get_channel_by_id(id)
  266. if not channel:
  267. raise HTTPException(
  268. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  269. )
  270. if user.role != "admin" and not has_access(
  271. user.id, type="read", access_control=channel.access_control
  272. ):
  273. raise HTTPException(
  274. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  275. )
  276. message = Messages.get_message_by_id(message_id)
  277. if not message:
  278. raise HTTPException(
  279. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  280. )
  281. if message.channel_id != id:
  282. raise HTTPException(
  283. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  284. )
  285. return MessageUserResponse(
  286. **{
  287. **message.model_dump(),
  288. "user": UserNameResponse(
  289. **Users.get_user_by_id(message.user_id).model_dump()
  290. ),
  291. }
  292. )
  293. ############################
  294. # GetChannelThreadMessages
  295. ############################
  296. @router.get(
  297. "/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse]
  298. )
  299. async def get_channel_thread_messages(
  300. id: str,
  301. message_id: str,
  302. skip: int = 0,
  303. limit: int = 50,
  304. user=Depends(get_verified_user),
  305. ):
  306. channel = Channels.get_channel_by_id(id)
  307. if not channel:
  308. raise HTTPException(
  309. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  310. )
  311. if user.role != "admin" and not has_access(
  312. user.id, type="read", access_control=channel.access_control
  313. ):
  314. raise HTTPException(
  315. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  316. )
  317. message_list = Messages.get_messages_by_parent_id(id, message_id, skip, limit)
  318. users = {}
  319. messages = []
  320. for message in message_list:
  321. if message.user_id not in users:
  322. user = Users.get_user_by_id(message.user_id)
  323. users[message.user_id] = user
  324. messages.append(
  325. MessageUserResponse(
  326. **{
  327. **message.model_dump(),
  328. "reply_count": 0,
  329. "latest_reply_at": None,
  330. "reactions": Messages.get_reactions_by_message_id(message.id),
  331. "user": UserNameResponse(**users[message.user_id].model_dump()),
  332. }
  333. )
  334. )
  335. return messages
  336. ############################
  337. # UpdateMessageById
  338. ############################
  339. @router.post(
  340. "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
  341. )
  342. async def update_message_by_id(
  343. id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user)
  344. ):
  345. channel = Channels.get_channel_by_id(id)
  346. if not channel:
  347. raise HTTPException(
  348. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  349. )
  350. message = Messages.get_message_by_id(message_id)
  351. if not message:
  352. raise HTTPException(
  353. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  354. )
  355. if message.channel_id != id:
  356. raise HTTPException(
  357. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  358. )
  359. if (
  360. user.role != "admin"
  361. and message.user_id != user.id
  362. and not has_access(user.id, type="read", access_control=channel.access_control)
  363. ):
  364. raise HTTPException(
  365. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  366. )
  367. try:
  368. message = Messages.update_message_by_id(message_id, form_data)
  369. message = Messages.get_message_by_id(message_id)
  370. if message:
  371. await sio.emit(
  372. "channel-events",
  373. {
  374. "channel_id": channel.id,
  375. "message_id": message.id,
  376. "data": {
  377. "type": "message:update",
  378. "data": MessageUserResponse(
  379. **{
  380. **message.model_dump(),
  381. "user": UserNameResponse(
  382. **user.model_dump()
  383. ).model_dump(),
  384. }
  385. ).model_dump(),
  386. },
  387. "user": UserNameResponse(**user.model_dump()).model_dump(),
  388. "channel": channel.model_dump(),
  389. },
  390. to=f"channel:{channel.id}",
  391. )
  392. return MessageModel(**message.model_dump())
  393. except Exception as e:
  394. log.exception(e)
  395. raise HTTPException(
  396. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  397. )
  398. ############################
  399. # AddReactionToMessage
  400. ############################
  401. class ReactionForm(BaseModel):
  402. name: str
  403. @router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool)
  404. async def add_reaction_to_message(
  405. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  406. ):
  407. channel = Channels.get_channel_by_id(id)
  408. if not channel:
  409. raise HTTPException(
  410. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  411. )
  412. if user.role != "admin" and not has_access(
  413. user.id, type="read", access_control=channel.access_control
  414. ):
  415. raise HTTPException(
  416. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  417. )
  418. message = Messages.get_message_by_id(message_id)
  419. if not message:
  420. raise HTTPException(
  421. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  422. )
  423. if message.channel_id != id:
  424. raise HTTPException(
  425. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  426. )
  427. try:
  428. Messages.add_reaction_to_message(message_id, user.id, form_data.name)
  429. message = Messages.get_message_by_id(message_id)
  430. await sio.emit(
  431. "channel-events",
  432. {
  433. "channel_id": channel.id,
  434. "message_id": message.id,
  435. "data": {
  436. "type": "message:reaction:add",
  437. "data": {
  438. **message.model_dump(),
  439. "user": UserNameResponse(
  440. **Users.get_user_by_id(message.user_id).model_dump()
  441. ).model_dump(),
  442. "name": form_data.name,
  443. },
  444. },
  445. "user": UserNameResponse(**user.model_dump()).model_dump(),
  446. "channel": channel.model_dump(),
  447. },
  448. to=f"channel:{channel.id}",
  449. )
  450. return True
  451. except Exception as e:
  452. log.exception(e)
  453. raise HTTPException(
  454. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  455. )
  456. ############################
  457. # RemoveReactionById
  458. ############################
  459. @router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool)
  460. async def remove_reaction_by_id_and_user_id_and_name(
  461. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  462. ):
  463. channel = Channels.get_channel_by_id(id)
  464. if not channel:
  465. raise HTTPException(
  466. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  467. )
  468. if user.role != "admin" and not has_access(
  469. user.id, type="read", access_control=channel.access_control
  470. ):
  471. raise HTTPException(
  472. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  473. )
  474. message = Messages.get_message_by_id(message_id)
  475. if not message:
  476. raise HTTPException(
  477. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  478. )
  479. if message.channel_id != id:
  480. raise HTTPException(
  481. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  482. )
  483. try:
  484. Messages.remove_reaction_by_id_and_user_id_and_name(
  485. message_id, user.id, form_data.name
  486. )
  487. message = Messages.get_message_by_id(message_id)
  488. await sio.emit(
  489. "channel-events",
  490. {
  491. "channel_id": channel.id,
  492. "message_id": message.id,
  493. "data": {
  494. "type": "message:reaction:remove",
  495. "data": {
  496. **message.model_dump(),
  497. "user": UserNameResponse(
  498. **Users.get_user_by_id(message.user_id).model_dump()
  499. ).model_dump(),
  500. "name": form_data.name,
  501. },
  502. },
  503. "user": UserNameResponse(**user.model_dump()).model_dump(),
  504. "channel": channel.model_dump(),
  505. },
  506. to=f"channel:{channel.id}",
  507. )
  508. return True
  509. except Exception as e:
  510. log.exception(e)
  511. raise HTTPException(
  512. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  513. )
  514. ############################
  515. # DeleteMessageById
  516. ############################
  517. @router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
  518. async def delete_message_by_id(
  519. id: str, message_id: str, user=Depends(get_verified_user)
  520. ):
  521. channel = Channels.get_channel_by_id(id)
  522. if not channel:
  523. raise HTTPException(
  524. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  525. )
  526. message = Messages.get_message_by_id(message_id)
  527. if not message:
  528. raise HTTPException(
  529. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  530. )
  531. if message.channel_id != id:
  532. raise HTTPException(
  533. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  534. )
  535. if (
  536. user.role != "admin"
  537. and message.user_id != user.id
  538. and not has_access(user.id, type="read", access_control=channel.access_control)
  539. ):
  540. raise HTTPException(
  541. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  542. )
  543. try:
  544. Messages.delete_message_by_id(message_id)
  545. await sio.emit(
  546. "channel-events",
  547. {
  548. "channel_id": channel.id,
  549. "message_id": message.id,
  550. "data": {
  551. "type": "message:delete",
  552. "data": {
  553. **message.model_dump(),
  554. "user": UserNameResponse(**user.model_dump()).model_dump(),
  555. },
  556. },
  557. "user": UserNameResponse(**user.model_dump()).model_dump(),
  558. "channel": channel.model_dump(),
  559. },
  560. to=f"channel:{channel.id}",
  561. )
  562. if message.parent_id:
  563. # If this message is a reply, emit to the parent message as well
  564. parent_message = Messages.get_message_by_id(message.parent_id)
  565. if parent_message:
  566. await sio.emit(
  567. "channel-events",
  568. {
  569. "channel_id": channel.id,
  570. "message_id": parent_message.id,
  571. "data": {
  572. "type": "message:reply",
  573. "data": MessageUserResponse(
  574. **{
  575. **parent_message.model_dump(),
  576. "user": UserNameResponse(
  577. **Users.get_user_by_id(
  578. parent_message.user_id
  579. ).model_dump()
  580. ),
  581. }
  582. ).model_dump(),
  583. },
  584. "user": UserNameResponse(**user.model_dump()).model_dump(),
  585. "channel": channel.model_dump(),
  586. },
  587. to=f"channel:{channel.id}",
  588. )
  589. return True
  590. except Exception as e:
  591. log.exception(e)
  592. raise HTTPException(
  593. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  594. )