channels.py 22 KB

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