fpx-engine 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {fpx_engine-0.3.0/fpx_engine.egg-info → fpx_engine-0.3.2}/PKG-INFO +5 -2
  2. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/README.md +3 -0
  3. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/__init__.py +1 -0
  4. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/runner.py +20 -14
  5. fpx_engine-0.3.2/fpx/classes/runner/subclasses/chat.py +160 -0
  6. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/order.py +7 -1
  7. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/router.py +41 -11
  8. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/models/account.py +1 -0
  9. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/utils/errors.py +20 -1
  10. {fpx_engine-0.3.0 → fpx_engine-0.3.2/fpx_engine.egg-info}/PKG-INFO +5 -2
  11. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/requires.txt +1 -1
  12. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/pyproject.toml +2 -2
  13. fpx_engine-0.3.0/fpx/classes/runner/subclasses/chat.py +0 -90
  14. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/LICENSE +0 -0
  15. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/api/client.py +0 -0
  16. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/api/parsers.py +0 -0
  17. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/account.py +0 -0
  18. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/addons.py +0 -0
  19. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/category.py +0 -0
  20. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/chat.py +0 -0
  21. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/editor.py +0 -0
  22. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/lot.py +0 -0
  23. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/order.py +0 -0
  24. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/profile.py +0 -0
  25. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/review.py +0 -0
  26. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/category.py +0 -0
  27. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/handler.py +0 -0
  28. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/review.py +0 -0
  29. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/fsm.py +0 -0
  30. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/main.py +0 -0
  31. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/middlewares/request_engine.py +0 -0
  32. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/models/chat.py +0 -0
  33. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/models/lots.py +0 -0
  34. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/SOURCES.txt +0 -0
  35. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/dependency_links.txt +0 -0
  36. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/top_level.txt +0 -0
  37. {fpx_engine-0.3.0 → fpx_engine-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpx-engine
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Автоматизация работы с FunPay
5
5
  Author-email: Be My Code <gettofarmila@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/bymyforge/fpx
@@ -10,7 +10,7 @@ Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.8
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: httpx>=0.24.0
13
+ Requires-Dist: httpx[http2]>=0.24.0
14
14
  Requires-Dist: beautifulsoup4>=4.12.0
15
15
  Dynamic: license-file
16
16
 
@@ -29,6 +29,9 @@ Dynamic: license-file
29
29
  <a href="https://github.com/bymyforge/fpx" target="_blank">
30
30
  <img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white" alt="GitHub">
31
31
  </a>
32
+ <a href="https://fpx.readthedocs.io/ru/latest/" target="_blank">
33
+ <img src="https://img.shields.io/badge/Документация-00b0ff?style=for-the-badge&logo=read-the-docs&logoColor=white" alt="Read the Docs">
34
+ </a>
32
35
  <a href="https://t.me/fpx_engine" target="_blank">
33
36
  <img src="https://img.shields.io/badge/Телеграм_Чат-26A5E4?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram">
34
37
  </a>
@@ -13,6 +13,9 @@
13
13
  <a href="https://github.com/bymyforge/fpx" target="_blank">
14
14
  <img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white" alt="GitHub">
15
15
  </a>
16
+ <a href="https://fpx.readthedocs.io/ru/latest/" target="_blank">
17
+ <img src="https://img.shields.io/badge/Документация-00b0ff?style=for-the-badge&logo=read-the-docs&logoColor=white" alt="Read the Docs">
18
+ </a>
16
19
  <a href="https://t.me/fpx_engine" target="_blank">
17
20
  <img src="https://img.shields.io/badge/Телеграм_Чат-26A5E4?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram">
18
21
  </a>
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  from .main import FunPayTools
3
3
  from .classes.runner.subclasses.handler import Router
4
+ from .utils import errors as fpx_error
4
5
  from .models.chat import Message
5
6
  from .models.account import Order, CurReview
6
7
  from .models.lots import CategoryLastLot
@@ -65,21 +65,23 @@ class Runner:
65
65
  '''
66
66
  if is_background:
67
67
  asyncio.create_task(self._run_loop(timer, watch_lots, watch_chips))
68
- await self.idle()
69
68
  else:
70
69
  await self._run_loop(timer, watch_lots, watch_chips)
71
70
 
72
71
  async def _warm_up(self, watch_lots, watch_chips):
73
72
  '''Прогрев кеша'''
74
73
  await self._account.profile.get_user_data()
75
- for _ in range(2):
76
- if watch_lots:
77
- await self._category._check_lot_categories(watch_lots)
78
- if watch_chips:
79
- await self._category._check_chip_categories(watch_chips)
80
- await self._chat._update_chat_cache()
81
- await self._order._update_order_cache()
82
- await self._review._update_review_cache()
74
+ tasks = []
75
+ if watch_lots:
76
+ tasks.append(self._category._check_lot_categories(watch_lots))
77
+ if watch_chips:
78
+ tasks.append(self._category._check_chip_categories(watch_chips))
79
+ tasks.extend([
80
+ self._chat._update_chat_cache(),
81
+ self._order._update_order_cache(),
82
+ self._review._update_review_cache()
83
+ ])
84
+ await asyncio.gather(*tasks)
83
85
  self._cache_is_updated = True
84
86
  for handler in self.handler._handlers['startup']:
85
87
  asyncio.create_task(handler())
@@ -89,10 +91,14 @@ class Runner:
89
91
  if not self._cache_is_updated:
90
92
  await self._warm_up(watch_lots, watch_chips)
91
93
  return
94
+ tasks = []
92
95
  if watch_lots:
93
- await self._category._check_lot_categories(watch_lots)
96
+ tasks.append(self._category._check_lot_categories(watch_lots))
94
97
  if watch_chips:
95
- await self._category._check_chip_categories(watch_chips)
96
- await self._chat._check_chats()
97
- await self._order._check_orders()
98
- await self._review._check_reviews()
98
+ tasks.append(self._category._check_chip_categories(watch_chips))
99
+ tasks.extend([
100
+ self._chat._check_chats(),
101
+ self._order._check_orders(),
102
+ self._review._check_reviews()
103
+ ])
104
+ await asyncio.gather(*tasks)
@@ -0,0 +1,160 @@
1
+ import inspect
2
+ import logging
3
+ import asyncio
4
+
5
+ from fpx.models.chat import Message
6
+ from fpx.fsm import FSMContext
7
+ from fpx.utils import errors as fpx_err
8
+
9
+ logger = logging.getLogger("fpx.chat_runner")
10
+
11
+ class ChatRunner:
12
+ def __init__(self, runner):
13
+ self.runner = runner
14
+
15
+ async def _compare_chat_cache(self):
16
+ '''
17
+ Сравнивает старый кеш сообщений с новым, если находит отличия, выносит сообщение в список, после чего возвращает полный список
18
+ '''
19
+ result = []
20
+ if self.runner._cache['msgs'] != self.runner._cache['old_msgs']:
21
+ for message in self.runner._cache['msgs']:
22
+ if message not in self.runner._cache['old_msgs']:
23
+ stop_words = ('оплатил заказ', 'можете перейти в discord', 'написал отзыв', 'изменил отзыв', 'вернул деньги', 'подтвердил успешное выполнение')
24
+ msg_lower = message['last_msg'].lower()
25
+ if not any(word in msg_lower for word in stop_words):
26
+ result.append(Message(sender=message['sender'], chat_id=message['chat_id'], text=message['last_msg'], is_system=False))
27
+ return result
28
+
29
+ async def _update_chat_cache(self):
30
+ '''
31
+ Обновляет кеш последних чатов
32
+ '''
33
+ chats = await self.runner._account.chat.get_chats()
34
+ result = []
35
+ counter = 0
36
+ for chat in chats:
37
+ if counter > 75:
38
+ break
39
+ chat = {'sender': chat.username, 'chat_id': chat.id, 'last_msg': chat.last_msg}
40
+ result.append(chat)
41
+ counter += 1
42
+ self.runner._cache['old_msgs'] = self.runner._cache['msgs']
43
+ self.runner._cache['msgs'] = result
44
+
45
+ async def _process_message(self, message: Message, state_ctx):
46
+ if not message.text:
47
+ return False
48
+ parts = message.text.split()
49
+ if not parts:
50
+ return False
51
+ first_word = parts[0].lower()
52
+ args = parts[1:]
53
+ for cmd_handler in self.runner.handler._handlers['commands']:
54
+ target_command = cmd_handler['command']
55
+ target_command_lower = {k.lower(): v for k, v in target_command.items()}
56
+ if first_word in target_command_lower:
57
+ target_function = target_command_lower[first_word]
58
+ sig = inspect.signature(target_function)
59
+ total_params_count = len(sig.parameters)
60
+ has_state = False
61
+ for param in sig.parameters.values():
62
+ if param.annotation == FSMContext:
63
+ has_state = True
64
+ break
65
+ if has_state:
66
+ required_args = total_params_count - 2
67
+ alowed_args_count = max(0, required_args)
68
+ final_args = args[:alowed_args_count]
69
+ if len(args) < required_args:
70
+ param_names = list(sig.parameters.keys())
71
+ missing_param = param_names[2 + len(args)]
72
+ raise fpx_err.FpxCommandArgsError(target_function.__name__, missing_param)
73
+ await target_function(message, state_ctx, *final_args)
74
+ else:
75
+ required_args = total_params_count - 1
76
+ alowed_args_count = max(0, required_args)
77
+ final_args = args[:alowed_args_count]
78
+ if len(args) < required_args:
79
+ param_names = list(sig.parameters.keys())
80
+ missing_param = param_names[1 + len(args)]
81
+ raise fpx_err.FpxCommandArgsError(target_function.__name__, missing_param)
82
+ await target_function(message, *final_args)
83
+ return True
84
+ return False
85
+
86
+ async def _trigger_message_handlers(self, message):
87
+ if self.runner._account.username == message.sender:
88
+ return
89
+ msg_text = message.text.lower()
90
+ current_state = await self.runner.storage.get_state(message.chat_id)
91
+ state_ctx = FSMContext(storage=self.runner.storage, chat_id=message.chat_id)
92
+ try:
93
+ if await self._process_message(message, state_ctx):
94
+ return
95
+ except Exception as e:
96
+ if self.runner.handler._handlers['error']:
97
+ await self.runner.handler._handlers['error'](message, e)
98
+ else:
99
+ logger.debug(f'Ошибка при обработке сообщения: {e}', exc_info=True)
100
+ return
101
+ for handler in self.runner.handler._handlers['message']:
102
+ if handler['state'] != current_state:
103
+ continue
104
+ async def call_handler(h_func):
105
+ sig = inspect.signature(h_func)
106
+ has_state = False
107
+ for param in sig.parameters.values():
108
+ if param.annotation == FSMContext:
109
+ has_state = True
110
+ break
111
+ if has_state:
112
+ await h_func(message, state_ctx)
113
+ else:
114
+ await h_func(message)
115
+ if handler['state'] is not None:
116
+ await call_handler(handler['function'])
117
+ break
118
+ if handler['mapping'] is not None:
119
+ matched = False
120
+ for trigger, reply in handler['mapping'].items():
121
+ if msg_text.startswith(trigger.lower()):
122
+ formatted_reply = reply.format(
123
+ sender=message.sender,
124
+ chat_id=message.chat_id,
125
+ text=message.text
126
+ )
127
+ await message.answer(formatted_reply)
128
+ matched = True
129
+ break
130
+ if matched:
131
+ await call_handler(handler['function'])
132
+ break
133
+ filter_text = handler['filter_text']
134
+ if filter_text is None and handler['mapping'] is None:
135
+ await call_handler(handler['function'])
136
+ break
137
+ if isinstance(filter_text, str) and msg_text.startswith(filter_text.lower()):
138
+ await call_handler(handler['function'])
139
+ break
140
+
141
+ async def _check_chats(self):
142
+ await self.runner._chat._update_chat_cache()
143
+ chats = await self.runner._chat._compare_chat_cache()
144
+ if chats:
145
+ async def process_single_chat(chat_cache_obj):
146
+ try:
147
+ msg_obj = await self.runner._account.chat.get_chat_data(chat_cache_obj.chat_id)
148
+ message = msg_obj.last_message
149
+ chat_msg = Message(
150
+ sender=message['sender'],
151
+ chat_id=chat_cache_obj.chat_id,
152
+ text=chat_cache_obj.text,
153
+ is_system=message['is_system']
154
+ )
155
+ chat_msg._client = self.runner
156
+ await self._trigger_message_handlers(chat_msg)
157
+ except Exception as e:
158
+ logger.error(f"Ошибка при параллельной обработке чата {chat_cache_obj.chat_id}: {e}", exc_info=True)
159
+ tasks = [process_single_chat(chat) for chat in chats]
160
+ await asyncio.gather(*tasks)
@@ -45,12 +45,18 @@ class OrderRunner:
45
45
  matched = False
46
46
  for trigger in handler['mapping']:
47
47
  if trigger.lower() in msg_text:
48
+ order.finded_mapping = trigger
48
49
  matched = True
49
50
  break
50
51
  if not matched:
51
52
  return False
52
53
  sig = inspect.signature(h_func)
53
- if len(sig.parameters) >= 2:
54
+ has_state = False
55
+ for param in sig.parameters.values():
56
+ if param.annotation == FSMContext:
57
+ has_state = True
58
+ break
59
+ if has_state:
54
60
  await h_func(order, state_ctx)
55
61
  else:
56
62
  await h_func(order)
@@ -1,4 +1,4 @@
1
-
1
+ from fpx.utils import errors as fpx_err
2
2
 
3
3
 
4
4
  class Router:
@@ -12,18 +12,33 @@ class Router:
12
12
  'review': [],
13
13
  'lot_category': [],
14
14
  'chip_category': [],
15
+ 'commands': [],
16
+ 'error': [],
15
17
 
16
18
  # системные
17
19
  'startup': [],
18
20
  'flood': []
19
21
  }
22
+
23
+ def on_error(self):
24
+ '''Декоратор для отлова ошибок'''
25
+ def decorator(func):
26
+ self._handlers['error'] = func
27
+ return func
28
+ return decorator
20
29
 
21
- def on_message(self, text: str | None = None, mapping: dict[str, str] | None = None, state: str | None = None):
30
+ def on_message(
31
+ self,
32
+ text: str | None = None,
33
+ mapping: dict[str, str] | None = None,
34
+ state: str | None = None,
35
+ command: dict | None = None
36
+ ):
22
37
  '''Декоратор отслеживает новые сообщения.
23
38
 
24
39
  Args:
25
- - text (str): Текст на который начинается сообщение, по которому фильтруется отображение новых сообщений.
26
- - mapping (dict): Словарь 'ключ': 'значение' для упрощённых ответов, вводи 'Привет' и 'Привет, работаю' и теперь скрипт будет всегда отвечать за тебя Привет, работаю когда тебе пишут привет. Вводи сколько угодно маппинга
40
+ - text (str | None): Текст на который начинается сообщение, по которому фильтруется отображение новых сообщений.
41
+ - mapping (dict | None): Словарь 'ключ': 'значение' для упрощённых ответов, вводи 'Привет' и 'Привет, работаю' и теперь скрипт будет всегда отвечать за тебя Привет, работаю когда тебе пишут привет. Вводи сколько угодно маппинга
27
42
 
28
43
  Returns:
29
44
  Message: Объект, содержащий:
@@ -33,13 +48,24 @@ class Router:
33
48
  - is_system (bool): Системное ли сообщение
34
49
  - anwer (method): При указании текста в аргументах, отвечает на сообщение
35
50
  '''
51
+ if command and (text is not None or mapping is not None):
52
+ raise fpx_err.FpxAttributeError('В декоратор on_message неправильно переданы аттрибуты, если вы передаёте command, остальные аргументы нельзя передавать.')
36
53
  def decorator(func):
37
- self._handlers['message'].append({
38
- 'function': func,
39
- 'filter_text': text,
40
- 'mapping': mapping,
41
- 'state': state
42
- })
54
+ if command:
55
+ self._handlers['commands'].append({
56
+ 'function': func,
57
+ 'filter_text': text,
58
+ 'mapping': mapping,
59
+ 'command': command,
60
+ 'state': state
61
+ })
62
+ else:
63
+ self._handlers['message'].append({
64
+ 'function': func,
65
+ 'filter_text': text,
66
+ 'mapping': mapping,
67
+ 'state': state
68
+ })
43
69
  return func
44
70
  return decorator
45
71
 
@@ -89,7 +115,11 @@ class Router:
89
115
  return func
90
116
  return decorator
91
117
 
92
- def on_new_order(self, mapping: list | None = None):
118
+ def on_new_order(
119
+ self,
120
+ mapping: list | None = None,
121
+ trigger_with_command: dict | None = None
122
+ ):
93
123
  '''
94
124
  Декоратор, который отслеживает только новые заказы.
95
125
 
@@ -64,6 +64,7 @@ class Order:
64
64
  description: Optional[str] = None
65
65
  client_name: Optional[str] = None
66
66
  price: Optional[float] = None
67
+ finded_mapping: Optional[str] = None
67
68
  status: Optional[str] = None
68
69
  name: Optional[str] = None
69
70
  category: Optional[str] = None
@@ -22,6 +22,11 @@ class FpxRunnerError(FpxError):
22
22
  def __init__(self, message='Ошибка раннера'):
23
23
  super().__init__(message)
24
24
 
25
+ class FpxHandlerError(FpxError):
26
+ """Ошибки хендлера."""
27
+ def __init__(self, message='Ошибка раннера'):
28
+ super().__init__(message)
29
+
25
30
  # Третий уровень, конкретные ошибки
26
31
 
27
32
 
@@ -73,4 +78,18 @@ class FpxNullDataError(FpxParseError):
73
78
  class FpxCriticalRunnerError(FpxRunnerError):
74
79
  """Критический сбой раннера, требующий остановки или жесткого перезапуска."""
75
80
  def __init__(self, message='Критический сбой раннера, требующий остановки или жесткого перезапуска'):
76
- super().__init__(message)
81
+ super().__init__(message)
82
+
83
+ # --- Ошибки хендлера ---
84
+
85
+ class FpxAttributeError(FpxHandlerError):
86
+ """Неправильно переданы аттрибуты."""
87
+ def __init__(self, message='Неправильно переданы аттрибуты'):
88
+ super().__init__(message)
89
+
90
+ class FpxCommandArgsError(FpxHandlerError):
91
+ """Вызывается, когда функция команды ожидает аргументы, но в сообщении их передали меньше, чем нужно."""
92
+ def __init__(self, function_name, missing_arg):
93
+ self.function_name = function_name
94
+ self.missing_arg = missing_arg
95
+ super().__init__(f"Команда '{function_name}' ожидает аргумент '{missing_arg}', но он не был передан в чате.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpx-engine
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Автоматизация работы с FunPay
5
5
  Author-email: Be My Code <gettofarmila@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/bymyforge/fpx
@@ -10,7 +10,7 @@ Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.8
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: httpx>=0.24.0
13
+ Requires-Dist: httpx[http2]>=0.24.0
14
14
  Requires-Dist: beautifulsoup4>=4.12.0
15
15
  Dynamic: license-file
16
16
 
@@ -29,6 +29,9 @@ Dynamic: license-file
29
29
  <a href="https://github.com/bymyforge/fpx" target="_blank">
30
30
  <img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white" alt="GitHub">
31
31
  </a>
32
+ <a href="https://fpx.readthedocs.io/ru/latest/" target="_blank">
33
+ <img src="https://img.shields.io/badge/Документация-00b0ff?style=for-the-badge&logo=read-the-docs&logoColor=white" alt="Read the Docs">
34
+ </a>
32
35
  <a href="https://t.me/fpx_engine" target="_blank">
33
36
  <img src="https://img.shields.io/badge/Телеграм_Чат-26A5E4?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram">
34
37
  </a>
@@ -1,2 +1,2 @@
1
- httpx>=0.24.0
1
+ httpx[http2]>=0.24.0
2
2
  beautifulsoup4>=4.12.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fpx-engine"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  authors = [
9
9
  { name="Be My Code", email="gettofarmila@gmail.com" },
10
10
  ]
@@ -17,7 +17,7 @@ classifiers = [
17
17
  "Operating System :: OS Independent",
18
18
  ]
19
19
  dependencies = [
20
- "httpx>=0.24.0",
20
+ "httpx[http2]>=0.24.0",
21
21
  "beautifulsoup4>=4.12.0",
22
22
  ]
23
23
 
@@ -1,90 +0,0 @@
1
- import inspect
2
-
3
- from fpx.models.chat import Message
4
- from fpx.fsm import FSMContext
5
-
6
- class ChatRunner:
7
- def __init__(self, runner):
8
- self.runner = runner
9
-
10
- async def _compare_chat_cache(self):
11
- '''
12
- Сравнивает старый кеш сообщений с новым, если находит отличия, выносит сообщение в список, после чего возвращает полный список
13
- '''
14
- result = []
15
- if self.runner._cache['msgs'] != self.runner._cache['old_msgs']:
16
- for message in self.runner._cache['msgs']:
17
- if message not in self.runner._cache['old_msgs']:
18
- stop_words = ('оплатил заказ', 'можете перейти в discord', 'написал отзыв', 'изменил отзыв', 'вернул деньги', 'подтвердил успешное выполнение')
19
- msg_lower = message['last_msg'].lower()
20
- if not any(word in msg_lower for word in stop_words):
21
- result.append(Message(sender=message['sender'], chat_id=message['chat_id'], text=message['last_msg'], is_system=False))
22
- return result
23
-
24
- async def _update_chat_cache(self):
25
- '''
26
- Обновляет кеш последних чатов
27
- '''
28
- chats = await self.runner._account.chat.get_chats()
29
- result = []
30
- counter = 0
31
- for chat in chats:
32
- if counter > 30:
33
- break
34
- chat = {'sender': chat.username, 'chat_id': chat.id, 'last_msg': chat.last_msg}
35
- result.append(chat)
36
- counter += 1
37
- self.runner._cache['old_msgs'] = self.runner._cache['msgs']
38
- self.runner._cache['msgs'] = result
39
-
40
- async def _trigger_message_handlers(self, message):
41
- if self.runner._account.username == message.sender:
42
- return
43
- msg_text = message.text.lower()
44
- current_state = await self.runner.storage.get_state(message.chat_id)
45
- state_ctx = FSMContext(storage=self.runner.storage, chat_id=message.chat_id)
46
- for handler in self.runner.handler._handlers['message']:
47
- if handler['state'] != current_state:
48
- continue
49
- async def call_handler(h_func):
50
- sig = inspect.signature(h_func)
51
- if len(sig.parameters) >= 2:
52
- await h_func(message, state_ctx)
53
- else:
54
- await h_func(message)
55
- if handler['state'] is not None:
56
- await call_handler(handler['function'])
57
- break
58
- if handler['mapping'] is not None:
59
- matched = False
60
- for trigger, reply in handler['mapping'].items():
61
- if msg_text.startswith(trigger.lower()):
62
- formatted_reply = reply.format(
63
- sender=message.sender,
64
- chat_id=message.chat_id,
65
- text=message.text
66
- )
67
- await message.answer(formatted_reply)
68
- matched = True
69
- break
70
- if matched:
71
- await call_handler(handler['function'])
72
- break
73
- filter_text = handler['filter_text']
74
- if filter_text is None and handler['mapping'] is None:
75
- await call_handler(handler['function'])
76
- break
77
- if isinstance(filter_text, str) and msg_text.startswith(filter_text.lower()):
78
- await call_handler(handler['function'])
79
- break
80
-
81
- async def _check_chats(self):
82
- await self.runner._chat._update_chat_cache()
83
- chats = await self.runner._chat._compare_chat_cache()
84
- if chats:
85
- for chat in chats:
86
- msg_obj = await self.runner._account.chat.get_chat_data(chat.chat_id)
87
- message = msg_obj.last_message
88
- chat = Message(sender=message['sender'], chat_id=chat.chat_id, text=chat.text, is_system=message['is_system'])
89
- chat._client = self.runner
90
- await self._trigger_message_handlers(chat)
File without changes
File without changes
File without changes
File without changes
File without changes