channels.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  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. messages.append(
  129. MessageUserResponse(
  130. **{
  131. **message.model_dump(),
  132. "reactions": Messages.get_reactions_by_message_id(message.id),
  133. "user": UserNameResponse(**users[message.user_id].model_dump()),
  134. }
  135. )
  136. )
  137. return messages
  138. ############################
  139. # PostNewMessage
  140. ############################
  141. async def send_notification(webui_url, channel, message, active_user_ids):
  142. users = get_users_with_access("read", channel.access_control)
  143. for user in users:
  144. if user.id in active_user_ids:
  145. continue
  146. else:
  147. if user.settings:
  148. webhook_url = user.settings.ui.get("notifications", {}).get(
  149. "webhook_url", None
  150. )
  151. if webhook_url:
  152. post_webhook(
  153. webhook_url,
  154. f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
  155. {
  156. "action": "channel",
  157. "message": message.content,
  158. "title": channel.name,
  159. "url": f"{webui_url}/channels/{channel.id}",
  160. },
  161. )
  162. @router.post("/{id}/messages/post", response_model=Optional[MessageModel])
  163. async def post_new_message(
  164. request: Request,
  165. id: str,
  166. form_data: MessageForm,
  167. background_tasks: BackgroundTasks,
  168. user=Depends(get_verified_user),
  169. ):
  170. channel = Channels.get_channel_by_id(id)
  171. if not channel:
  172. raise HTTPException(
  173. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  174. )
  175. if user.role != "admin" and not has_access(
  176. user.id, type="read", access_control=channel.access_control
  177. ):
  178. raise HTTPException(
  179. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  180. )
  181. try:
  182. message = Messages.insert_new_message(form_data, channel.id, user.id)
  183. if message:
  184. event_data = {
  185. "channel_id": channel.id,
  186. "message_id": message.id,
  187. "data": {
  188. "type": "message",
  189. "data": {
  190. **message.model_dump(),
  191. "user": UserNameResponse(**user.model_dump()).model_dump(),
  192. },
  193. },
  194. "user": UserNameResponse(**user.model_dump()).model_dump(),
  195. "channel": channel.model_dump(),
  196. }
  197. await sio.emit(
  198. "channel-events",
  199. event_data,
  200. to=f"channel:{channel.id}",
  201. )
  202. active_user_ids = get_user_ids_from_room(f"channel:{channel.id}")
  203. background_tasks.add_task(
  204. send_notification,
  205. request.app.state.config.WEBUI_URL,
  206. channel,
  207. message,
  208. active_user_ids,
  209. )
  210. return MessageModel(**message.model_dump())
  211. except Exception as e:
  212. log.exception(e)
  213. raise HTTPException(
  214. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  215. )
  216. ############################
  217. # GetChannelThreadMessages
  218. ############################
  219. @router.get(
  220. "/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse]
  221. )
  222. async def get_channel_thread_messages(
  223. id: str,
  224. message_id: str,
  225. skip: int = 0,
  226. limit: int = 50,
  227. user=Depends(get_verified_user),
  228. ):
  229. channel = Channels.get_channel_by_id(id)
  230. if not channel:
  231. raise HTTPException(
  232. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  233. )
  234. if user.role != "admin" and not has_access(
  235. user.id, type="read", access_control=channel.access_control
  236. ):
  237. raise HTTPException(
  238. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  239. )
  240. message_list = Messages.get_messages_by_parent_id(id, message_id, skip, limit)
  241. users = {}
  242. messages = []
  243. for message in message_list:
  244. if message.user_id not in users:
  245. user = Users.get_user_by_id(message.user_id)
  246. users[message.user_id] = user
  247. messages.append(
  248. MessageUserResponse(
  249. **{
  250. **message.model_dump(),
  251. "reactions": Messages.get_reactions_by_message_id(message.id),
  252. "user": UserNameResponse(**users[message.user_id].model_dump()),
  253. }
  254. )
  255. )
  256. return messages
  257. ############################
  258. # UpdateMessageById
  259. ############################
  260. @router.post(
  261. "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
  262. )
  263. async def update_message_by_id(
  264. id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user)
  265. ):
  266. channel = Channels.get_channel_by_id(id)
  267. if not channel:
  268. raise HTTPException(
  269. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  270. )
  271. if user.role != "admin" and not has_access(
  272. user.id, type="read", access_control=channel.access_control
  273. ):
  274. raise HTTPException(
  275. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  276. )
  277. message = Messages.get_message_by_id(message_id)
  278. if not message:
  279. raise HTTPException(
  280. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  281. )
  282. if message.channel_id != id:
  283. raise HTTPException(
  284. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  285. )
  286. try:
  287. message = Messages.update_message_by_id(message_id, form_data)
  288. message = Messages.get_message_by_id(message_id)
  289. if message:
  290. await sio.emit(
  291. "channel-events",
  292. {
  293. "channel_id": channel.id,
  294. "message_id": message.id,
  295. "data": {
  296. "type": "message:update",
  297. "data": {
  298. **message.model_dump(),
  299. "user": UserNameResponse(**user.model_dump()).model_dump(),
  300. },
  301. },
  302. "user": UserNameResponse(**user.model_dump()).model_dump(),
  303. "channel": channel.model_dump(),
  304. },
  305. to=f"channel:{channel.id}",
  306. )
  307. return MessageModel(**message.model_dump())
  308. except Exception as e:
  309. log.exception(e)
  310. raise HTTPException(
  311. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  312. )
  313. ############################
  314. # AddReactionToMessage
  315. ############################
  316. class ReactionForm(BaseModel):
  317. name: str
  318. @router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool)
  319. async def add_reaction_to_message(
  320. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  321. ):
  322. channel = Channels.get_channel_by_id(id)
  323. if not channel:
  324. raise HTTPException(
  325. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  326. )
  327. if user.role != "admin" and not has_access(
  328. user.id, type="read", access_control=channel.access_control
  329. ):
  330. raise HTTPException(
  331. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  332. )
  333. message = Messages.get_message_by_id(message_id)
  334. if not message:
  335. raise HTTPException(
  336. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  337. )
  338. if message.channel_id != id:
  339. raise HTTPException(
  340. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  341. )
  342. try:
  343. Messages.add_reaction_to_message(message_id, user.id, form_data.name)
  344. message = Messages.get_message_by_id(message_id)
  345. await sio.emit(
  346. "channel-events",
  347. {
  348. "channel_id": channel.id,
  349. "message_id": message.id,
  350. "data": {
  351. "type": "message:reaction",
  352. "data": {
  353. **message.model_dump(),
  354. "user": UserNameResponse(**user.model_dump()).model_dump(),
  355. "name": form_data.name,
  356. },
  357. },
  358. "user": UserNameResponse(**user.model_dump()).model_dump(),
  359. "channel": channel.model_dump(),
  360. },
  361. to=f"channel:{channel.id}",
  362. )
  363. return True
  364. except Exception as e:
  365. log.exception(e)
  366. raise HTTPException(
  367. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  368. )
  369. ############################
  370. # RemoveReactionById
  371. ############################
  372. @router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool)
  373. async def remove_reaction_by_id_and_user_id_and_name(
  374. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  375. ):
  376. channel = Channels.get_channel_by_id(id)
  377. if not channel:
  378. raise HTTPException(
  379. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  380. )
  381. if user.role != "admin" and not has_access(
  382. user.id, type="read", access_control=channel.access_control
  383. ):
  384. raise HTTPException(
  385. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  386. )
  387. message = Messages.get_message_by_id(message_id)
  388. if not message:
  389. raise HTTPException(
  390. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  391. )
  392. if message.channel_id != id:
  393. raise HTTPException(
  394. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  395. )
  396. try:
  397. Messages.remove_reaction_by_id_and_user_id_and_name(
  398. message_id, user.id, form_data.name
  399. )
  400. message = Messages.get_message_by_id(message_id)
  401. await sio.emit(
  402. "channel-events",
  403. {
  404. "channel_id": channel.id,
  405. "message_id": message.id,
  406. "data": {
  407. "type": "message:reaction",
  408. "data": {
  409. **message.model_dump(),
  410. "user": UserNameResponse(**user.model_dump()).model_dump(),
  411. "name": form_data.name,
  412. },
  413. },
  414. "user": UserNameResponse(**user.model_dump()).model_dump(),
  415. "channel": channel.model_dump(),
  416. },
  417. to=f"channel:{channel.id}",
  418. )
  419. return True
  420. except Exception as e:
  421. log.exception(e)
  422. raise HTTPException(
  423. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  424. )
  425. ############################
  426. # DeleteMessageById
  427. ############################
  428. @router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
  429. async def delete_message_by_id(
  430. id: str, message_id: str, user=Depends(get_verified_user)
  431. ):
  432. channel = Channels.get_channel_by_id(id)
  433. if not channel:
  434. raise HTTPException(
  435. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  436. )
  437. if user.role != "admin" and not has_access(
  438. user.id, type="read", access_control=channel.access_control
  439. ):
  440. raise HTTPException(
  441. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  442. )
  443. message = Messages.get_message_by_id(message_id)
  444. if not message:
  445. raise HTTPException(
  446. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  447. )
  448. if message.channel_id != id:
  449. raise HTTPException(
  450. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  451. )
  452. try:
  453. Messages.delete_message_by_id(message_id)
  454. await sio.emit(
  455. "channel-events",
  456. {
  457. "channel_id": channel.id,
  458. "message_id": message.id,
  459. "data": {
  460. "type": "message:delete",
  461. "data": {
  462. **message.model_dump(),
  463. "user": UserNameResponse(**user.model_dump()).model_dump(),
  464. },
  465. },
  466. "user": UserNameResponse(**user.model_dump()).model_dump(),
  467. "channel": channel.model_dump(),
  468. },
  469. to=f"channel:{channel.id}",
  470. )
  471. return True
  472. except Exception as e:
  473. log.exception(e)
  474. raise HTTPException(
  475. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  476. )