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.
- {fpx_engine-0.3.0/fpx_engine.egg-info → fpx_engine-0.3.2}/PKG-INFO +5 -2
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/README.md +3 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/__init__.py +1 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/runner.py +20 -14
- fpx_engine-0.3.2/fpx/classes/runner/subclasses/chat.py +160 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/order.py +7 -1
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/router.py +41 -11
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/models/account.py +1 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/utils/errors.py +20 -1
- {fpx_engine-0.3.0 → fpx_engine-0.3.2/fpx_engine.egg-info}/PKG-INFO +5 -2
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/requires.txt +1 -1
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/pyproject.toml +2 -2
- fpx_engine-0.3.0/fpx/classes/runner/subclasses/chat.py +0 -90
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/LICENSE +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/api/client.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/api/parsers.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/account.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/addons.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/category.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/chat.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/editor.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/lot.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/order.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/profile.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/account/subclasses/review.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/category.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/handler.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/classes/runner/subclasses/review.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/fsm.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/main.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/middlewares/request_engine.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/models/chat.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx/models/lots.py +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/SOURCES.txt +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/dependency_links.txt +0 -0
- {fpx_engine-0.3.0 → fpx_engine-0.3.2}/fpx_engine.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
96
|
+
tasks.append(self._category._check_lot_categories(watch_lots))
|
|
94
97
|
if watch_chips:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
38
|
-
'
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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(
|
|
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
|
|
|
@@ -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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|