xync-bot 0.3.24.dev12__tar.gz → 0.3.26.dev0__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.
Potentially problematic release.
This version of xync-bot might be problematic. Click here for more details.
- {xync_bot-0.3.24.dev12/xync_bot.egg-info → xync_bot-0.3.26.dev0}/PKG-INFO +1 -1
- xync_bot-0.3.26.dev0/pager.py +274 -0
- xync_bot-0.3.26.dev0/xync_bot/__main__.py +37 -0
- xync_bot-0.3.26.dev0/xync_bot/routers/__init__.py +41 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/cond/__init__.py +1 -7
- xync_bot-0.3.24.dev12/xync_bot/routers/main.py → xync_bot-0.3.26.dev0/xync_bot/routers/main/handler.py +28 -60
- xync_bot-0.3.26.dev0/xync_bot/routers/pay/dep.py +119 -0
- xync_bot-0.3.26.dev0/xync_bot/routers/pay/handler.py +267 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/pay/window.py +38 -52
- xync_bot-0.3.26.dev0/xync_bot/routers/send/__init__.py +117 -0
- xync_bot-0.3.26.dev0/xync_bot/shared.py +24 -0
- xync_bot-0.3.26.dev0/xync_bot/store.py +151 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0/xync_bot.egg-info}/PKG-INFO +1 -1
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot.egg-info/SOURCES.txt +7 -3
- xync_bot-0.3.24.dev12/xync_bot/__init__.py +0 -26
- xync_bot-0.3.24.dev12/xync_bot/routers/pay/dep.py +0 -290
- xync_bot-0.3.24.dev12/xync_bot/routers/pay/handler.py +0 -253
- xync_bot-0.3.24.dev12/xync_bot/shared.py +0 -5
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/.env.dist +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/.gitignore +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/.pre-commit-config.yaml +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/makefile +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/pyproject.toml +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/setup.cfg +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/test_main.http +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/loader.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/cond/func.py +0 -0
- {xync_bot-0.3.24.dev12/xync_bot/routers → xync_bot-0.3.26.dev0/xync_bot/routers/main}/__init__.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/order.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/pay/cd.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/photo.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/vpn.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/routers/xicon.png +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot/typs.py +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot.egg-info/dependency_links.txt +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot.egg-info/requires.txt +0 -0
- {xync_bot-0.3.24.dev12 → xync_bot-0.3.26.dev0}/xync_bot.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from aiogram import Bot, Dispatcher, types
|
|
2
|
+
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
|
3
|
+
from aiogram.filters import Command
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from xync_bot.loader import TOKEN
|
|
8
|
+
|
|
9
|
+
# Пример списка из 200 вариантов (замените на ваши данные)
|
|
10
|
+
OPTIONS = (
|
|
11
|
+
[
|
|
12
|
+
f"Apple {i}"
|
|
13
|
+
for i in range(50) # 50 вариантов на A
|
|
14
|
+
]
|
|
15
|
+
+ [
|
|
16
|
+
"Banana" # 1 вариант на B
|
|
17
|
+
]
|
|
18
|
+
+ [
|
|
19
|
+
f"Dog {i}"
|
|
20
|
+
for i in range(30) # 30 вариантов на D
|
|
21
|
+
]
|
|
22
|
+
+ [
|
|
23
|
+
f"Elephant {i}"
|
|
24
|
+
for i in range(20) # 20 вариантов на E
|
|
25
|
+
]
|
|
26
|
+
+ [
|
|
27
|
+
f"Fish {i}"
|
|
28
|
+
for i in range(40) # 40 вариантов на F
|
|
29
|
+
]
|
|
30
|
+
+ [
|
|
31
|
+
f"Zebra {i}"
|
|
32
|
+
for i in range(59) # 59 вариантов на Z
|
|
33
|
+
]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SmartAlphaPager:
|
|
38
|
+
def __init__(self, options, items_per_page=15):
|
|
39
|
+
self.options = sorted(options) # Сортируем по алфавиту
|
|
40
|
+
self.items_per_page = items_per_page
|
|
41
|
+
self.pages = self._create_balanced_pages()
|
|
42
|
+
|
|
43
|
+
def _create_balanced_pages(self):
|
|
44
|
+
"""Создаем страницы с примерно равным количеством элементов"""
|
|
45
|
+
pages = []
|
|
46
|
+
current_page = []
|
|
47
|
+
|
|
48
|
+
for option in self.options:
|
|
49
|
+
current_page.append(option)
|
|
50
|
+
|
|
51
|
+
# Если страница заполнена, создаем новую
|
|
52
|
+
if len(current_page) >= self.items_per_page:
|
|
53
|
+
pages.append(current_page)
|
|
54
|
+
current_page = []
|
|
55
|
+
|
|
56
|
+
# Добавляем последнюю страницу, если есть остатки
|
|
57
|
+
if current_page:
|
|
58
|
+
pages.append(current_page)
|
|
59
|
+
|
|
60
|
+
return pages
|
|
61
|
+
|
|
62
|
+
def _get_page_title(self, page_items):
|
|
63
|
+
"""Создает заголовок страницы на основе диапазона букв"""
|
|
64
|
+
if not page_items:
|
|
65
|
+
return "Пустая страница"
|
|
66
|
+
|
|
67
|
+
first_letter = page_items[0][0].upper()
|
|
68
|
+
last_letter = page_items[-1][0].upper()
|
|
69
|
+
|
|
70
|
+
if first_letter == last_letter:
|
|
71
|
+
return f"📋 Буква '{first_letter}'"
|
|
72
|
+
else:
|
|
73
|
+
return f"📋 Буквы '{first_letter}' - '{last_letter}'"
|
|
74
|
+
|
|
75
|
+
def get_overview_menu(self):
|
|
76
|
+
"""Возвращает обзорное меню со всеми страницами"""
|
|
77
|
+
if not self.pages:
|
|
78
|
+
return InlineKeyboardMarkup(inline_keyboard=[])
|
|
79
|
+
|
|
80
|
+
keyboard = InlineKeyboardMarkup(inline_keyboard=[])
|
|
81
|
+
|
|
82
|
+
# Создаем кнопки для каждой страницы
|
|
83
|
+
for i, page_items in enumerate(self.pages):
|
|
84
|
+
first_letter = page_items[0][0].upper()
|
|
85
|
+
last_letter = page_items[-1][0].upper()
|
|
86
|
+
count = len(page_items)
|
|
87
|
+
|
|
88
|
+
if first_letter == last_letter:
|
|
89
|
+
button_text = f"{first_letter} ({count})"
|
|
90
|
+
else:
|
|
91
|
+
button_text = f"{first_letter}-{last_letter} ({count})"
|
|
92
|
+
|
|
93
|
+
# Группируем по 3 кнопки в ряд
|
|
94
|
+
if i % 3 == 0:
|
|
95
|
+
keyboard.inline_keyboard.append([])
|
|
96
|
+
|
|
97
|
+
keyboard.inline_keyboard[-1].append(InlineKeyboardButton(text=button_text, callback_data=f"page_{i}"))
|
|
98
|
+
|
|
99
|
+
return keyboard
|
|
100
|
+
|
|
101
|
+
def get_page_keyboard(self, page_num):
|
|
102
|
+
"""Возвращает клавиатуру для конкретной страницы"""
|
|
103
|
+
if page_num < 0 or page_num >= len(self.pages):
|
|
104
|
+
return None, None
|
|
105
|
+
|
|
106
|
+
page_items = self.pages[page_num]
|
|
107
|
+
title = self._get_page_title(page_items)
|
|
108
|
+
|
|
109
|
+
keyboard = InlineKeyboardMarkup(inline_keyboard=[])
|
|
110
|
+
|
|
111
|
+
# Добавляем кнопки с вариантами
|
|
112
|
+
for option in page_items:
|
|
113
|
+
keyboard.inline_keyboard.append([InlineKeyboardButton(text=option, callback_data=f"select_{option}")])
|
|
114
|
+
|
|
115
|
+
# Навигационные кнопки
|
|
116
|
+
nav_row = []
|
|
117
|
+
|
|
118
|
+
# Кнопка "К обзору"
|
|
119
|
+
nav_row.append(InlineKeyboardButton(text="📖 Обзор", callback_data="back_to_overview"))
|
|
120
|
+
|
|
121
|
+
# Кнопки навигации между страницами
|
|
122
|
+
if len(self.pages) > 1:
|
|
123
|
+
if page_num > 0:
|
|
124
|
+
nav_row.append(InlineKeyboardButton(text="⬅️ Пред", callback_data=f"nav_page_{page_num - 1}"))
|
|
125
|
+
|
|
126
|
+
nav_row.append(
|
|
127
|
+
InlineKeyboardButton(text=f"{page_num + 1}/{len(self.pages)}", callback_data="current_page_info")
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if page_num < len(self.pages) - 1:
|
|
131
|
+
nav_row.append(InlineKeyboardButton(text="След ➡️", callback_data=f"nav_page_{page_num + 1}"))
|
|
132
|
+
|
|
133
|
+
keyboard.inline_keyboard.append(nav_row)
|
|
134
|
+
|
|
135
|
+
# Дополнительная информация в заголовке
|
|
136
|
+
items_count = len(page_items)
|
|
137
|
+
total_items = sum(len(page) for page in self.pages)
|
|
138
|
+
full_title = f"{title}\nСтраница {page_num + 1}/{len(self.pages)} • {items_count} из {total_items} вариантов"
|
|
139
|
+
|
|
140
|
+
return keyboard, full_title
|
|
141
|
+
|
|
142
|
+
def get_stats(self):
|
|
143
|
+
"""Возвращает статистику распределения"""
|
|
144
|
+
stats = {}
|
|
145
|
+
for page_num, page_items in enumerate(self.pages):
|
|
146
|
+
first_letter = page_items[0][0].upper()
|
|
147
|
+
last_letter = page_items[-1][0].upper()
|
|
148
|
+
|
|
149
|
+
if first_letter == last_letter:
|
|
150
|
+
range_text = f"'{first_letter}'"
|
|
151
|
+
else:
|
|
152
|
+
range_text = f"'{first_letter}'-'{last_letter}'"
|
|
153
|
+
|
|
154
|
+
stats[f"Страница {page_num + 1}"] = {"range": range_text, "count": len(page_items)}
|
|
155
|
+
|
|
156
|
+
return stats
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Инициализация бота
|
|
160
|
+
bot = Bot(token=TOKEN)
|
|
161
|
+
dp = Dispatcher()
|
|
162
|
+
|
|
163
|
+
pager = SmartAlphaPager(OPTIONS)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dp.message(Command("start"))
|
|
167
|
+
async def start_handler(message: types.Message):
|
|
168
|
+
"""Стартовая команда - показываем обзор страниц"""
|
|
169
|
+
keyboard = pager.get_overview_menu()
|
|
170
|
+
total_options = len(pager.options)
|
|
171
|
+
total_pages = len(pager.pages)
|
|
172
|
+
|
|
173
|
+
text = (
|
|
174
|
+
f"🔍 Выберите диапазон для просмотра:\n\n"
|
|
175
|
+
f"📊 Всего вариантов: {total_options}\n"
|
|
176
|
+
f"📄 Всего страниц: {total_pages}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
await message.answer(text, reply_markup=keyboard)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dp.message(Command("stats"))
|
|
183
|
+
async def stats_handler(message: types.Message):
|
|
184
|
+
"""Показать статистику распределения"""
|
|
185
|
+
stats = pager.get_stats()
|
|
186
|
+
|
|
187
|
+
text = "📊 Статистика распределения по страницам:\n\n"
|
|
188
|
+
for page_name, info in stats.items():
|
|
189
|
+
text += f"{page_name}: {info['range']} — {info['count']} вариантов\n"
|
|
190
|
+
|
|
191
|
+
await message.answer(text)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dp.callback_query(lambda c: c.data == "back_to_overview")
|
|
195
|
+
async def back_to_overview(callback: types.CallbackQuery):
|
|
196
|
+
"""Возврат к обзору страниц"""
|
|
197
|
+
keyboard = pager.get_overview_menu()
|
|
198
|
+
total_options = len(pager.options)
|
|
199
|
+
total_pages = len(pager.pages)
|
|
200
|
+
|
|
201
|
+
text = (
|
|
202
|
+
f"🔍 Выберите диапазон для просмотра:\n\n"
|
|
203
|
+
f"📊 Всего вариантов: {total_options}\n"
|
|
204
|
+
f"📄 Всего страниц: {total_pages}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
await callback.message.edit_text(text, reply_markup=keyboard)
|
|
208
|
+
await callback.answer()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dp.callback_query(lambda c: c.data.startswith("page_"))
|
|
212
|
+
async def show_page(callback: types.CallbackQuery):
|
|
213
|
+
"""Показываем конкретную страницу"""
|
|
214
|
+
page_num = int(callback.data.split("_")[1])
|
|
215
|
+
keyboard, text = pager.get_page_keyboard(page_num)
|
|
216
|
+
|
|
217
|
+
if keyboard:
|
|
218
|
+
await callback.message.edit_text(text, reply_markup=keyboard)
|
|
219
|
+
else:
|
|
220
|
+
await callback.answer("Ошибка: страница не найдена")
|
|
221
|
+
|
|
222
|
+
await callback.answer()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dp.callback_query(lambda c: c.data.startswith("nav_page_"))
|
|
226
|
+
async def navigate_pages(callback: types.CallbackQuery):
|
|
227
|
+
"""Навигация между страницами"""
|
|
228
|
+
page_num = int(callback.data.split("_")[2])
|
|
229
|
+
keyboard, text = pager.get_page_keyboard(page_num)
|
|
230
|
+
|
|
231
|
+
if keyboard:
|
|
232
|
+
await callback.message.edit_text(text, reply_markup=keyboard)
|
|
233
|
+
else:
|
|
234
|
+
await callback.answer("Ошибка навигации")
|
|
235
|
+
|
|
236
|
+
await callback.answer()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dp.callback_query(lambda c: c.data.startswith("select_"))
|
|
240
|
+
async def option_selected(callback: types.CallbackQuery):
|
|
241
|
+
"""Обработка выбора варианта"""
|
|
242
|
+
selected = callback.data.replace("select_", "")
|
|
243
|
+
|
|
244
|
+
await callback.message.edit_text(
|
|
245
|
+
f"✅ Вы выбрали: {selected}\n\n"
|
|
246
|
+
f"Используйте /start для нового выбора\n"
|
|
247
|
+
f"Используйте /stats для просмотра статистики"
|
|
248
|
+
)
|
|
249
|
+
await callback.answer(f"Выбран: {selected}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@dp.callback_query(lambda c: c.data == "current_page_info")
|
|
253
|
+
async def current_page_info(callback: types.CallbackQuery):
|
|
254
|
+
"""Информация о текущей странице"""
|
|
255
|
+
await callback.answer("Информация о текущей странице")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
async def main():
|
|
259
|
+
"""Запуск бота"""
|
|
260
|
+
logging.basicConfig(level=logging.INFO)
|
|
261
|
+
print("Бот запущен...")
|
|
262
|
+
|
|
263
|
+
# Выводим статистику при запуске
|
|
264
|
+
stats = pager.get_stats()
|
|
265
|
+
print("\n📊 Статистика распределения:")
|
|
266
|
+
for page_name, info in stats.items():
|
|
267
|
+
print(f"{page_name}: {info['range']} — {info['count']} вариантов")
|
|
268
|
+
print()
|
|
269
|
+
|
|
270
|
+
await dp.start_polling(bot)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from asyncio import run
|
|
3
|
+
|
|
4
|
+
from PGram import Bot
|
|
5
|
+
from aiogram.client.default import DefaultBotProperties
|
|
6
|
+
from aiogram.enums import UpdateType
|
|
7
|
+
from x_model import init_db
|
|
8
|
+
|
|
9
|
+
from xync_bot.store import Store
|
|
10
|
+
from xync_bot.routers.main.handler import mr
|
|
11
|
+
from xync_bot.routers.cond import cr
|
|
12
|
+
from xync_bot.routers.pay.handler import pr
|
|
13
|
+
from xync_bot.routers import last
|
|
14
|
+
from xync_bot.routers.send import sd
|
|
15
|
+
|
|
16
|
+
au = [
|
|
17
|
+
UpdateType.MESSAGE,
|
|
18
|
+
UpdateType.CALLBACK_QUERY,
|
|
19
|
+
UpdateType.CHAT_MEMBER,
|
|
20
|
+
UpdateType.MY_CHAT_MEMBER,
|
|
21
|
+
] # , UpdateType.CHAT_JOIN_REQUEST
|
|
22
|
+
bot = Bot([sd, cr, pr, mr, last], Store(), au, default=DefaultBotProperties(parse_mode="HTML"))
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
from xync_bot.loader import TOKEN, TORM
|
|
26
|
+
|
|
27
|
+
logging.basicConfig(level=logging.INFO)
|
|
28
|
+
|
|
29
|
+
async def main() -> None:
|
|
30
|
+
cn = await init_db(TORM)
|
|
31
|
+
bot.dp.workflow_data["store"].glob = await Store.Global() # todo: refact store loading
|
|
32
|
+
await bot.start(
|
|
33
|
+
TOKEN,
|
|
34
|
+
cn,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
run(main())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from aiogram import Router, F
|
|
4
|
+
from aiogram.enums import ContentType
|
|
5
|
+
from aiogram.exceptions import TelegramBadRequest
|
|
6
|
+
from aiogram.types import Message
|
|
7
|
+
|
|
8
|
+
last = Router(name="last")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@last.message(
|
|
12
|
+
F.content_type.not_in(
|
|
13
|
+
{
|
|
14
|
+
ContentType.NEW_CHAT_MEMBERS,
|
|
15
|
+
# ContentType.LEFT_CHAT_MEMBER,
|
|
16
|
+
# ContentType.SUPERGROUP_CHAT_CREATED,
|
|
17
|
+
# ContentType.NEW_CHAT_PHOTO,
|
|
18
|
+
# ContentType.FORUM_TOPIC_CREATED,
|
|
19
|
+
# ContentType.FORUM_TOPIC_EDITED,
|
|
20
|
+
ContentType.FORUM_TOPIC_CLOSED,
|
|
21
|
+
# ContentType.GENERAL_FORUM_TOPIC_HIDDEN, # deletable
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
async def del_cbq(msg: Message):
|
|
26
|
+
try:
|
|
27
|
+
await msg.delete()
|
|
28
|
+
logging.info({"DELETED": msg.model_dump(exclude_none=True)})
|
|
29
|
+
except TelegramBadRequest:
|
|
30
|
+
logging.error({"NOT_DELETED": msg.model_dump(exclude_none=True)})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@last.message()
|
|
34
|
+
async def all_rest(msg: Message):
|
|
35
|
+
logging.warning(
|
|
36
|
+
{
|
|
37
|
+
"NO_HANDLED": msg.model_dump(
|
|
38
|
+
exclude_none=True,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
)
|
|
@@ -8,7 +8,7 @@ from xync_schema.enums import SynonymType
|
|
|
8
8
|
|
|
9
9
|
from xync_bot.routers.cond.func import wrap_cond, get_val, btns, rkm, ikm, SynTypeCd, CondCd
|
|
10
10
|
|
|
11
|
-
cr = Router()
|
|
11
|
+
cr = Router(name="cond")
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@cr.message(Command("cond"))
|
|
@@ -89,9 +89,3 @@ async def got_action(cbq: CallbackQuery, callback_data: CondCd, state: FSMContex
|
|
|
89
89
|
await (await state.get_value("cmsg")).delete()
|
|
90
90
|
await show_cond(cbq.message, cond)
|
|
91
91
|
return await cbq.answer(callback_data.act)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@cr.message()
|
|
95
|
-
async def unknown(msg: Message):
|
|
96
|
-
# user = await User.get(username_id=msg.from_user.id)
|
|
97
|
-
await msg.delete()
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from aiogram import Router, F
|
|
4
|
-
from aiogram.enums import ContentType
|
|
5
|
-
from aiogram.exceptions import TelegramBadRequest
|
|
6
4
|
from aiogram.filters import CommandStart, CommandObject, ChatMemberUpdatedFilter, JOIN_TRANSITION, LEAVE_TRANSITION
|
|
7
5
|
from aiogram.filters.callback_data import CallbackData
|
|
8
6
|
from aiogram.types import (
|
|
@@ -18,8 +16,9 @@ from aiogram.utils.deep_linking import create_start_link
|
|
|
18
16
|
from xync_schema import models
|
|
19
17
|
|
|
20
18
|
from xync_bot.shared import NavCallbackData
|
|
19
|
+
from xync_bot.store import Store
|
|
21
20
|
|
|
22
|
-
mr = Router()
|
|
21
|
+
mr = Router(name="main")
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class RrCallbackData(CallbackData, prefix="reg_res"): # registration response
|
|
@@ -30,6 +29,7 @@ class RrCallbackData(CallbackData, prefix="reg_res"): # registration response
|
|
|
30
29
|
home_btns = InlineKeyboardMarkup(
|
|
31
30
|
inline_keyboard=[
|
|
32
31
|
[
|
|
32
|
+
InlineKeyboardButton(text="Transfer", callback_data=NavCallbackData(to="transfer").pack()),
|
|
33
33
|
InlineKeyboardButton(text="Invite", callback_data=NavCallbackData(to="ref_link").pack()),
|
|
34
34
|
InlineKeyboardButton(text="Get VPN", callback_data=NavCallbackData(to="get_vpn").pack()),
|
|
35
35
|
]
|
|
@@ -41,7 +41,7 @@ home_btns = InlineKeyboardMarkup(
|
|
|
41
41
|
async def start_handler(msg: Message, command: CommandObject):
|
|
42
42
|
me: User = msg.from_user
|
|
43
43
|
ref_id: int = command.args.isnumeric() and int(command.args)
|
|
44
|
-
user = await models.User.
|
|
44
|
+
user = await models.User.get(username_id=me.id, blocked=False)
|
|
45
45
|
rm = None
|
|
46
46
|
logging.info(msg, {"src": "start"})
|
|
47
47
|
if user:
|
|
@@ -66,6 +66,27 @@ async def start_handler(msg: Message, command: CommandObject):
|
|
|
66
66
|
return await msg.answer(rs, reply_markup=rm)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
@mr.message(CommandStart(deep_link=True)) # attempt to reg by fake link
|
|
70
|
+
async def fraud_handler(msg: Message):
|
|
71
|
+
logging.warning(f"Start: {msg.from_user.id}. Msg: {msg}")
|
|
72
|
+
# todo: alert to admins! Fraud attempt!
|
|
73
|
+
await msg.answer("🤔")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@mr.message(CommandStart()) # обычный /start
|
|
77
|
+
async def home(msg: Message, store: Store):
|
|
78
|
+
me = msg.from_user
|
|
79
|
+
user, is_new = await models.User.tg_upsert(me, False)
|
|
80
|
+
|
|
81
|
+
rr = "сначала вы должны найти поручителя, и перейти по его реферальной ссылке.\nhttps://telegra.ph/XyncNet-02-13"
|
|
82
|
+
if is_new: # has ref and created now
|
|
83
|
+
await msg.answer(f"Здравствуйте {me.full_name}, что бы использовать возможности нашей сети, {rr}")
|
|
84
|
+
elif not user.ref_id:
|
|
85
|
+
await msg.answer(rr.capitalize())
|
|
86
|
+
else:
|
|
87
|
+
await msg.answer(f"{me.full_name}, не балуйтесь, вы и так уже активный участник👌🏼", reply_markup=home_btns)
|
|
88
|
+
|
|
89
|
+
|
|
69
90
|
@mr.callback_query(RrCallbackData.filter())
|
|
70
91
|
async def phrases_input_request(cb: CallbackQuery, callback_data: RrCallbackData) -> None:
|
|
71
92
|
protege = await models.User[callback_data.to]
|
|
@@ -83,27 +104,7 @@ async def phrases_input_request(cb: CallbackQuery, callback_data: RrCallbackData
|
|
|
83
104
|
await cb.message.edit_text(rs)
|
|
84
105
|
|
|
85
106
|
|
|
86
|
-
@mr.
|
|
87
|
-
async def fraud_handler(msg: Message):
|
|
88
|
-
logging.warning(f"Start: {msg.from_user.id}. Msg: {msg}")
|
|
89
|
-
# todo: alert to admins! Fraud attempt!
|
|
90
|
-
await msg.answer("🤔")
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@mr.message(CommandStart())
|
|
94
|
-
async def start_no_ref_handler(msg: Message):
|
|
95
|
-
me = msg.from_user
|
|
96
|
-
user, cr = await models.User.tg2in(me, False)
|
|
97
|
-
rr = "сначала вы должны найти поручителя, и перейти по его реферальной ссылке.\nhttps://telegra.ph/XyncNet-02-13"
|
|
98
|
-
if cr: # has ref and created now
|
|
99
|
-
await msg.answer(f"Здравствуйте {me.full_name}, что бы использовать возможности нашей сети, {rr}")
|
|
100
|
-
elif not user.ref_id:
|
|
101
|
-
await msg.answer(rr.capitalize())
|
|
102
|
-
else:
|
|
103
|
-
await msg.answer(f"{me.full_name}, не балуйтесь, вы и так уже активный участник👌🏼", reply_markup=home_btns)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@mr.callback_query(NavCallbackData.filter(F.to == "ref_link"))
|
|
107
|
+
@mr.callback_query(NavCallbackData.filter(F.to.__eq__("ref_link")))
|
|
107
108
|
async def ref_link_handler(cbq: CallbackQuery):
|
|
108
109
|
me = cbq.from_user
|
|
109
110
|
if not (u := await models.User.get_or_none(id=me.id, blocked=False).prefetch_related("ref")):
|
|
@@ -118,7 +119,7 @@ async def ref_link_handler(cbq: CallbackQuery):
|
|
|
118
119
|
await cbq.answer("Wait for your protege request..")
|
|
119
120
|
|
|
120
121
|
|
|
121
|
-
@mr.my_chat_member(F.chat.type == "private") # my_chat_member is fired on
|
|
122
|
+
@mr.my_chat_member(F.chat.type == "private") # my_chat_member is fired on adding bot to any chat. filter for preventing
|
|
122
123
|
async def my_user_set_status(my_chat_member: ChatMemberUpdated):
|
|
123
124
|
logging.info({"my_chat_member": my_chat_member.model_dump(exclude_none=True)})
|
|
124
125
|
u: User = my_chat_member.from_user
|
|
@@ -128,7 +129,7 @@ async def my_user_set_status(my_chat_member: ChatMemberUpdated):
|
|
|
128
129
|
|
|
129
130
|
@mr.my_chat_member()
|
|
130
131
|
async def user_set_status(my_chat_member: ChatMemberUpdated):
|
|
131
|
-
if my_chat_member.new_chat_member.user.username == "XyncNetBot": # удалена
|
|
132
|
+
if my_chat_member.new_chat_member.user.username == "XyncNetBot": # удалена группа, где бот был добавлен админом
|
|
132
133
|
if forum := await models.Forum.get_or_none(id=my_chat_member.chat.id):
|
|
133
134
|
await forum.delete()
|
|
134
135
|
res = f"I {my_chat_member.new_chat_member.status} from {my_chat_member.chat.id}:{my_chat_member.chat.title}"
|
|
@@ -186,36 +187,3 @@ async def order_msg(msg: Message):
|
|
|
186
187
|
await models.Msg.create(tgid=msg.message_id, txt=msg.text, order_id=order.id, receiver=receiver)
|
|
187
188
|
logging.info(msg, {"src": "order_msg"})
|
|
188
189
|
return await msg.send_copy(receiver.forum, message_thread_id=rcv_topic)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
@mr.message(
|
|
192
|
-
F.content_type.not_in(
|
|
193
|
-
{
|
|
194
|
-
ContentType.NEW_CHAT_MEMBERS,
|
|
195
|
-
# ContentType.LEFT_CHAT_MEMBER,
|
|
196
|
-
# ContentType.SUPERGROUP_CHAT_CREATED,
|
|
197
|
-
# ContentType.NEW_CHAT_PHOTO,
|
|
198
|
-
# ContentType.FORUM_TOPIC_CREATED,
|
|
199
|
-
# ContentType.FORUM_TOPIC_EDITED,
|
|
200
|
-
ContentType.FORUM_TOPIC_CLOSED,
|
|
201
|
-
# ContentType.GENERAL_FORUM_TOPIC_HIDDEN, # deletable
|
|
202
|
-
}
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
async def del_cbq(msg: Message):
|
|
206
|
-
try:
|
|
207
|
-
await msg.delete()
|
|
208
|
-
logging.info({"DELETED": msg.model_dump(exclude_none=True)})
|
|
209
|
-
except TelegramBadRequest:
|
|
210
|
-
logging.error({"NOT_DELETED": msg.model_dump(exclude_none=True)})
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
@mr.message()
|
|
214
|
-
async def all_rest(msg: Message):
|
|
215
|
-
logging.warning(
|
|
216
|
-
{
|
|
217
|
-
"NO_HANDLED": msg.model_dump(
|
|
218
|
-
exclude_none=True,
|
|
219
|
-
)
|
|
220
|
-
}
|
|
221
|
-
)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from asyncio import gather
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
|
|
4
|
+
from aiogram.exceptions import TelegramBadRequest
|
|
5
|
+
from aiogram.fsm.state import StatesGroup, State
|
|
6
|
+
from aiogram.types import Message, InlineKeyboardMarkup
|
|
7
|
+
from pyrogram.types import CallbackQuery
|
|
8
|
+
from x_model.func import ArrayAgg
|
|
9
|
+
from xync_schema import models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Report(StatesGroup):
|
|
13
|
+
text = State()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CredState(StatesGroup):
|
|
17
|
+
detail = State()
|
|
18
|
+
name = State()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PaymentState(StatesGroup):
|
|
22
|
+
amount = State()
|
|
23
|
+
timer = State()
|
|
24
|
+
timer_active = State()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ActionType(IntEnum):
|
|
28
|
+
"""Цель (назначение) платежа (target)"""
|
|
29
|
+
|
|
30
|
+
sent = 1 # Отправил
|
|
31
|
+
received = 2 # Получил
|
|
32
|
+
not_received = 3 # Не получил
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PayStep(IntEnum):
|
|
36
|
+
"""Цель (назначение) платежа (target)"""
|
|
37
|
+
|
|
38
|
+
t_type = 1 # Выбор типа
|
|
39
|
+
t_cur = 2 # Выбор валюты
|
|
40
|
+
t_coin = 3 # Выбор монеты
|
|
41
|
+
t_pm = 4 # Выбор платежки
|
|
42
|
+
t_ex = 5 # Выбор биржи
|
|
43
|
+
t_cred_dtl = 6 # Ввод номера карты
|
|
44
|
+
t_cred_name = 7 # Ввод имени
|
|
45
|
+
# t_addr = 8 # todo: позже добавим: Выбор/ввод крипто кошелька
|
|
46
|
+
t_amount = 9 # Ввод суммы
|
|
47
|
+
""" Источник платежа (source) """
|
|
48
|
+
s_type = 10 # Выбор типа
|
|
49
|
+
s_cur = 11 # Выбор типа
|
|
50
|
+
s_pm = 12 # Выбор типа
|
|
51
|
+
s_coin = 13 # Выбор типа
|
|
52
|
+
s_ex = 14 # Выбор типа
|
|
53
|
+
ppo = 15 # Выбор возможности разбивки платежа
|
|
54
|
+
urgency = 16 # Выбор срочности получения платежа
|
|
55
|
+
pending_send = 17 # Ожидание отправки (если мы платим фиатом)
|
|
56
|
+
pending_confirm = 18 # Ожидание пока на той стороне подтвердят получение нашего фиата (если мы платим фиатом)
|
|
57
|
+
pending_receive = 19 # Ожидание поступления (если мы получаем фиат)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def fill_creds(person_id: int) -> tuple[dict[int, models.Cred], dict[int, list[int]]]:
|
|
61
|
+
cq = models.Cred.filter(person_id=person_id)
|
|
62
|
+
creds = {c.id: c for c in await cq}
|
|
63
|
+
cur_creds = {
|
|
64
|
+
pci: ids
|
|
65
|
+
for pci, ids in await cq.annotate(ids=ArrayAgg("id")).group_by("pmcur_id").values_list("pmcur_id", "ids")
|
|
66
|
+
}
|
|
67
|
+
return creds, cur_creds
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def fill_actors(person_id: int) -> dict[int, int]:
|
|
71
|
+
ex_actors = {
|
|
72
|
+
# todo: check len(ids) == 1
|
|
73
|
+
exi: ids[0]
|
|
74
|
+
for exi, ids in await models.Actor.filter(person_id=person_id)
|
|
75
|
+
.annotate(ids=ArrayAgg("id"))
|
|
76
|
+
.group_by("ex_id")
|
|
77
|
+
.values_list("ex_id", "ids")
|
|
78
|
+
}
|
|
79
|
+
return ex_actors
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def edit(msg: Message, txt: str, rm: InlineKeyboardMarkup):
|
|
83
|
+
await gather(msg.edit_text(txt), msg.edit_reply_markup(reply_markup=rm))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def ans(cbq: CallbackQuery, txt: str = None):
|
|
87
|
+
await cbq.answer(txt, cache_time=0)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def dlt(msg: Message):
|
|
91
|
+
await msg.delete()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def edt(msg: Message, txt: str, rm: InlineKeyboardMarkup):
|
|
95
|
+
if msg.message_id == msg.bot.store.perm.msg_id:
|
|
96
|
+
await msg.edit_text(txt, reply_markup=rm)
|
|
97
|
+
else: # окно вызвано в ответ на текст, а не кнопку
|
|
98
|
+
try:
|
|
99
|
+
await msg.bot.edit_message_text(
|
|
100
|
+
txt, chat_id=msg.chat.id, message_id=msg.bot.store.perm.msg_id, reply_markup=rm
|
|
101
|
+
)
|
|
102
|
+
except TelegramBadRequest as e:
|
|
103
|
+
print(msg.bot.store.perm.msg_id, e)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def fmt_sec(sec: int):
|
|
107
|
+
days = sec // (24 * 3600)
|
|
108
|
+
sec %= 24 * 3600
|
|
109
|
+
hours = sec // 3600
|
|
110
|
+
sec %= 3600
|
|
111
|
+
minutes = sec // 60
|
|
112
|
+
sec %= 60
|
|
113
|
+
|
|
114
|
+
if days > 0:
|
|
115
|
+
return f"{days}д {hours:02d}:{minutes:02d}:{sec:02d}"
|
|
116
|
+
elif hours > 0:
|
|
117
|
+
return f"{hours:02d}:{minutes:02d}:{sec:02d}"
|
|
118
|
+
else:
|
|
119
|
+
return f"{minutes:02d}:{sec:02d}"
|