user.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. """
  2. User logic module
  3. Specialize in user features
  4. """
  5. from io import BytesIO
  6. from typing import Tuple, Type
  7. from aiogram import types
  8. from aiogram.dispatcher import FSMContext
  9. from aiogram.dispatcher.filters.state import State
  10. from aiogram.utils.exceptions import PhotoDimensions
  11. from app import config
  12. from app.models.photo import Photo
  13. from app.utils.helper import find_func_by_state_name
  14. from app.utils.httpx import HttpxWorker
  15. from app.utils.markups import NAVIGATION_BUTTONS, markups_list
  16. from app.utils.states import UserStates
  17. from . import base
  18. async def get_previous_message_and_markup(
  19. user_states: UserStates, keyboard: dict, state_messages: dict
  20. ) -> Tuple[str, types.ReplyKeyboardMarkup]:
  21. """
  22. Get previous message and markup for `back` function
  23. :param user_states: user's state obj
  24. used for getting previous state
  25. :param keyboard: all markups that have been added to the user's state data
  26. :param state_messages: messages that are linked to the states
  27. e.g. grade state -> "Выбери клас"
  28. subject state -> "Выбери предмет"
  29. :returns: previous message and previous markup
  30. """
  31. prev_msg = None
  32. while not prev_msg:
  33. prev_state = await user_states.previous()
  34. prev_markup_data = keyboard.get(prev_state)
  35. if prev_markup_data is None:
  36. continue
  37. else:
  38. prev_msg = state_messages[prev_state]
  39. prev_markup = find_func_by_state_name(prev_state, markups_list)(prev_markup_data)
  40. return prev_msg, prev_markup
  41. async def set_next_state_markup(
  42. next_state: State,
  43. keyboard: dict,
  44. set_markup: types.ReplyKeyboardMarkup,
  45. state: FSMContext,
  46. ) -> None:
  47. """
  48. Set a markup to the user's next state data (concrete - `keyboard` dictionary)
  49. Used for `back` feature
  50. :param next_state: next user's state
  51. :param keyboard: contains all markups
  52. :param set_markup: markup that will be set into the `keyboard` dict
  53. :param state: user's state obj
  54. :returns: None
  55. """
  56. keyboard[next_state.state] = [
  57. j.strip() for i in set_markup.keyboard for j in i if j not in NAVIGATION_BUTTONS
  58. ]
  59. await base.set_state_data(state, Keyboard=keyboard)
  60. async def clean_current_state_markup(
  61. current_state: str, keyboard: dict, state: FSMContext,
  62. ) -> None:
  63. """
  64. Clean a markup of the user's current state data (concrete - `keyboard` dictionary)
  65. Used for `back` feature and optimize usage of memory
  66. :param current_state: current user's state
  67. :param keyboard: contains all markups
  68. :param state: user's state obj
  69. :returns: None
  70. """
  71. del keyboard[current_state]
  72. await base.set_state_data(state, Keyboard=keyboard)
  73. async def send_solution_and_save_to_db(
  74. solution_id: int,
  75. solution_url: str,
  76. user_message: types.Message,
  77. photo_model: Photo,
  78. httpx_worker: HttpxWorker,
  79. ) -> None:
  80. """
  81. Send solution (photo) to the user and save the solution to db
  82. Used in `solution` handler
  83. :param solution_id: solution's id
  84. :param solution_url: solution's url
  85. :param user_message: user's message, used for check image's
  86. sizes and send the solution to the user
  87. :param photo_model: photo model obj
  88. :param httpx_worker: httpx obj, used for getting a solution from the solution url
  89. :returns: None
  90. """
  91. photo = await base.get_model_obj_from_db_by_id(photo_model, solution_id)
  92. if not photo:
  93. img = await httpx_worker.get(solution_url)
  94. img_content = img.content
  95. img_filename = img.url.path.split("/")[-1]
  96. try:
  97. img_id = (await user_message.answer_photo(img_content)).photo[-1].file_id
  98. except PhotoDimensions as e:
  99. img_id = (
  100. await user_message.answer_document(
  101. types.InputFile(BytesIO(img_content), filename=img_filename)
  102. )
  103. ).document.file_id
  104. img_id = config.PREFIX_WRONG_PHOTO_SIZE + img_id
  105. photo = await photo_model.create(id=solution_id, photo_id=img_id)
  106. else:
  107. img_id = photo.photo_id
  108. if img_id.startswith(config.PREFIX_WRONG_PHOTO_SIZE):
  109. await user_message.answer_document(
  110. img_id[len(config.PREFIX_WRONG_PHOTO_SIZE) :]
  111. )
  112. else:
  113. await user_message.answer_photo(img_id)