xync-bot 0.3.7.dev4__tar.gz → 0.3.24__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.7.dev4/xync_bot.egg-info → xync_bot-0.3.24}/PKG-INFO +1 -1
- xync_bot-0.3.24/xync_bot/__main__.py +36 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/loader.py +2 -0
- xync_bot-0.3.24/xync_bot/routers/__init__.py +41 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/routers/cond/__init__.py +1 -7
- xync_bot-0.3.7.dev4/xync_bot/routers/main.py → xync_bot-0.3.24/xync_bot/routers/main/handler.py +25 -57
- xync_bot-0.3.24/xync_bot/routers/pay/cd.py +49 -0
- xync_bot-0.3.24/xync_bot/routers/pay/dep.py +270 -0
- xync_bot-0.3.24/xync_bot/routers/pay/handler.py +247 -0
- xync_bot-0.3.24/xync_bot/routers/pay/window.py +244 -0
- xync_bot-0.3.24/xync_bot/shared.py +24 -0
- xync_bot-0.3.24/xync_bot/store.py +150 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24/xync_bot.egg-info}/PKG-INFO +1 -1
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot.egg-info/SOURCES.txt +8 -3
- xync_bot-0.3.7.dev4/xync_bot/__init__.py +0 -23
- xync_bot-0.3.7.dev4/xync_bot/routers/pay/main.py +0 -133
- xync_bot-0.3.7.dev4/xync_bot/shared.py +0 -5
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/.env.dist +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/.gitignore +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/.pre-commit-config.yaml +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/makefile +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/pyproject.toml +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/setup.cfg +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/test_main.http +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/routers/cond/func.py +0 -0
- {xync_bot-0.3.7.dev4/xync_bot/routers → xync_bot-0.3.24/xync_bot/routers/main}/__init__.py +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/routers/order.py +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/routers/photo.py +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/routers/vpn.py +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/routers/xicon.png +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot/typs.py +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot.egg-info/dependency_links.txt +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot.egg-info/requires.txt +0 -0
- {xync_bot-0.3.7.dev4 → xync_bot-0.3.24}/xync_bot.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
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.routers.main.handler import mr
|
|
10
|
+
from xync_bot.routers.cond import cr
|
|
11
|
+
from xync_bot.routers.pay.handler import pr
|
|
12
|
+
from xync_bot.routers import last
|
|
13
|
+
from xync_bot.routers.pay.dep import Store
|
|
14
|
+
|
|
15
|
+
au = [
|
|
16
|
+
UpdateType.MESSAGE,
|
|
17
|
+
UpdateType.CALLBACK_QUERY,
|
|
18
|
+
UpdateType.CHAT_MEMBER,
|
|
19
|
+
UpdateType.MY_CHAT_MEMBER,
|
|
20
|
+
] # , UpdateType.CHAT_JOIN_REQUEST
|
|
21
|
+
bot = Bot([cr, pr, mr, last], Store(), au, default=DefaultBotProperties(parse_mode="HTML"))
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
from xync_bot.loader import TOKEN, TORM
|
|
25
|
+
|
|
26
|
+
logging.basicConfig(level=logging.INFO)
|
|
27
|
+
|
|
28
|
+
async def main() -> None:
|
|
29
|
+
cn = await init_db(TORM)
|
|
30
|
+
bot.dp.workflow_data["store"].glob = await Store.Global() # todo: refact store loading
|
|
31
|
+
await bot.start(
|
|
32
|
+
TOKEN,
|
|
33
|
+
cn,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
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()
|
xync_bot-0.3.7.dev4/xync_bot/routers/main.py → xync_bot-0.3.24/xync_bot/routers/main/handler.py
RENAMED
|
@@ -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
|
]
|
|
@@ -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")):
|
|
@@ -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,49 @@
|
|
|
1
|
+
from aiogram.filters.callback_data import CallbackData
|
|
2
|
+
|
|
3
|
+
from xync_bot.routers.pay.dep import PayStep, ActionType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MoneyType(CallbackData, prefix="target"):
|
|
7
|
+
is_fiat: int # bool
|
|
8
|
+
is_target: int # bool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Cur(CallbackData, prefix="cur"):
|
|
12
|
+
id: int
|
|
13
|
+
is_target: int # bool
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Coin(CallbackData, prefix="coin"):
|
|
17
|
+
id: int
|
|
18
|
+
is_target: int # bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Cred(CallbackData, prefix="cred"):
|
|
22
|
+
id: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Ex(CallbackData, prefix="ex"):
|
|
26
|
+
id: int
|
|
27
|
+
is_target: int # bool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Pm(CallbackData, prefix="pm"):
|
|
31
|
+
pmcur_id: int
|
|
32
|
+
is_target: bool
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Ppo(CallbackData, prefix="ppo"):
|
|
36
|
+
num: int
|
|
37
|
+
is_target: int # bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PayNav(CallbackData, prefix="pay_nav"):
|
|
41
|
+
to: PayStep
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Time(CallbackData, prefix="time"):
|
|
45
|
+
minutes: int # время в минутах
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Action(CallbackData, prefix="action"):
|
|
49
|
+
act: ActionType # "received" или "not_received"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from asyncio import gather
|
|
3
|
+
from enum import IntEnum
|
|
4
|
+
|
|
5
|
+
from aiogram.exceptions import TelegramBadRequest
|
|
6
|
+
from aiogram.fsm.state import StatesGroup, State
|
|
7
|
+
from aiogram.types import Message, InlineKeyboardMarkup
|
|
8
|
+
from pyrogram.types import CallbackQuery
|
|
9
|
+
from tortoise.functions import Min
|
|
10
|
+
from x_auth.enums import Role
|
|
11
|
+
from x_model.func import ArrayAgg
|
|
12
|
+
from xync_schema import models
|
|
13
|
+
|
|
14
|
+
from xync_bot.shared import flags
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Report(StatesGroup):
|
|
18
|
+
text = State()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CredState(StatesGroup):
|
|
22
|
+
detail = State()
|
|
23
|
+
name = State()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PaymentState(StatesGroup):
|
|
27
|
+
amount = State()
|
|
28
|
+
timer = State()
|
|
29
|
+
timer_active = State()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ActionType(IntEnum):
|
|
33
|
+
"""Цель (назначение) платежа (target)"""
|
|
34
|
+
|
|
35
|
+
sent = 1 # Отправил
|
|
36
|
+
received = 2 # Получил
|
|
37
|
+
not_received = 3 # Не получил
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PayStep(IntEnum):
|
|
41
|
+
"""Цель (назначение) платежа (target)"""
|
|
42
|
+
|
|
43
|
+
t_type = 1 # Выбор типа
|
|
44
|
+
t_cur = 2 # Выбор валюты
|
|
45
|
+
t_coin = 3 # Выбор монеты
|
|
46
|
+
t_pm = 4 # Выбор платежки
|
|
47
|
+
t_ex = 5 # Выбор биржи
|
|
48
|
+
t_cred_dtl = 6 # Ввод номера карты
|
|
49
|
+
t_cred_name = 7 # Ввод имени
|
|
50
|
+
# t_addr = 8 # todo: позже добавим: Выбор/ввод крипто кошелька
|
|
51
|
+
t_amount = 9 # Ввод суммы
|
|
52
|
+
""" Источник платежа (source) """
|
|
53
|
+
s_type = 10 # Выбор типа
|
|
54
|
+
s_cur = 11 # Выбор типа
|
|
55
|
+
s_pm = 12 # Выбор типа
|
|
56
|
+
s_coin = 13 # Выбор типа
|
|
57
|
+
s_ex = 14 # Выбор типа
|
|
58
|
+
ppo = 15 # Выбор возможности разбивки платежа
|
|
59
|
+
urgency = 16 # Выбор срочности получения платежа
|
|
60
|
+
pending_send = 17 # Ожидание отправки (если мы платим фиатом)
|
|
61
|
+
pending_confirm = 18 # Ожидание пока на той стороне подтвердят получение нашего фиата (если мы платим фиатом)
|
|
62
|
+
pending_receive = 19 # Ожидание поступления (если мы получаем фиат)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SingleStore(type):
|
|
66
|
+
_store = None
|
|
67
|
+
|
|
68
|
+
async def __call__(cls):
|
|
69
|
+
if not cls._store:
|
|
70
|
+
cls._store = super(SingleStore, cls).__call__()
|
|
71
|
+
cls._store.coins = {k: v for k, v in await models.Coin.all().order_by("ticker").values_list("id", "ticker")}
|
|
72
|
+
curs = {c.id: c for c in await models.Cur.filter(ticker__in=flags.keys()).order_by("ticker")}
|
|
73
|
+
cls._store.curs = curs
|
|
74
|
+
cls._store.exs = {k: v for k, v in await models.Ex.all().values_list("id", "name")}
|
|
75
|
+
cls._store.pmcurs = {
|
|
76
|
+
k: v
|
|
77
|
+
for k, v in await models.Pmex.filter(pm__pmcurs__cur_id__in=cls._store.curs.keys())
|
|
78
|
+
.annotate(sname=Min("name"))
|
|
79
|
+
.group_by("pm__pmcurs__id")
|
|
80
|
+
.values_list("pm__pmcurs__id", "sname")
|
|
81
|
+
}
|
|
82
|
+
cls._store.coinexs = {
|
|
83
|
+
c.id: [ex.ex_id for ex in c.coinexs] for c in await models.Coin.all().prefetch_related("coinexs")
|
|
84
|
+
}
|
|
85
|
+
cls._store.curpms = {
|
|
86
|
+
cur_id: ids
|
|
87
|
+
for cur_id, ids in await models.Pmcur.filter(cur_id__in=curs.keys())
|
|
88
|
+
.annotate(ids=ArrayAgg("id"))
|
|
89
|
+
.group_by("cur_id")
|
|
90
|
+
.values_list("cur_id", "ids")
|
|
91
|
+
}
|
|
92
|
+
cls._store.curpms = {
|
|
93
|
+
cur_id: ids
|
|
94
|
+
for cur_id, ids in await models.Pmcur.filter(cur_id__in=curs.keys())
|
|
95
|
+
.annotate(ids=ArrayAgg("id"))
|
|
96
|
+
.group_by("cur_id")
|
|
97
|
+
.values_list("cur_id", "ids")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return cls._store
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Store:
|
|
104
|
+
class Global(metaclass=SingleStore):
|
|
105
|
+
coins: dict[int, str] # id:ticker
|
|
106
|
+
curs: dict[int, models.Cur] # id:Cur
|
|
107
|
+
exs: dict[int, str] # id:name
|
|
108
|
+
coinexs: dict[int, list[int]] # id:[ex_ids]
|
|
109
|
+
pmcurs: dict[int, str] # pmcur_id:name
|
|
110
|
+
curpms: dict[int, list[int]] # id:[pmcur_ids]
|
|
111
|
+
|
|
112
|
+
class Permanent:
|
|
113
|
+
msg_id: int = None
|
|
114
|
+
user: models.User = None
|
|
115
|
+
actors: dict[int, int] = None # key=ex_id
|
|
116
|
+
creds: dict[int, models.Cred] = None # key=cred_id
|
|
117
|
+
cur_creds: dict[int, list[int]] = None # pmcur_id:[cred_ids]
|
|
118
|
+
|
|
119
|
+
class Current:
|
|
120
|
+
is_target: bool = True
|
|
121
|
+
is_fiat: bool = None
|
|
122
|
+
msg_to_del: Message = None
|
|
123
|
+
|
|
124
|
+
class Payment:
|
|
125
|
+
t_cur_id: int = None
|
|
126
|
+
s_cur_id: int = None
|
|
127
|
+
t_coin_id: int = None
|
|
128
|
+
s_coin_id: int = None
|
|
129
|
+
t_pmcur_id: int = None
|
|
130
|
+
s_pmcur_id: int = None
|
|
131
|
+
t_ex_id: int = None
|
|
132
|
+
s_ex_id: int = None
|
|
133
|
+
amount: int | float = None
|
|
134
|
+
ppo: int = 1
|
|
135
|
+
addr_id: int = None
|
|
136
|
+
cred_dtl: str = None
|
|
137
|
+
cred_id: int = None
|
|
138
|
+
urg: int = 5
|
|
139
|
+
pr_id: int = None
|
|
140
|
+
|
|
141
|
+
glob: Global
|
|
142
|
+
perm: Permanent = Permanent()
|
|
143
|
+
pay: Payment = Payment()
|
|
144
|
+
curr: Current = Current()
|
|
145
|
+
|
|
146
|
+
async def xync_have_coin_amount(self) -> bool:
|
|
147
|
+
assets = await models.Asset.filter(
|
|
148
|
+
addr__coin_id=self.pay.t_coin_id, addr__ex_id=self.pay.t_ex_id, addr__actor__user__role__in=Role.ADMIN
|
|
149
|
+
)
|
|
150
|
+
return self.pay.amount <= sum(a.free for a in assets)
|
|
151
|
+
|
|
152
|
+
async def client_have_coin_amount(self) -> bool:
|
|
153
|
+
assets = await models.Asset.filter(
|
|
154
|
+
addr__coin_id=self.pay.t_coin_id, addr__actor_id__in=self.perm.actors.values()
|
|
155
|
+
)
|
|
156
|
+
return self.pay.amount <= sum(a.free for a in assets)
|
|
157
|
+
|
|
158
|
+
async def need_ppo(self):
|
|
159
|
+
cur_id = getattr(self.pay, ("t" if self.curr.is_target else "s") + "_cur_id")
|
|
160
|
+
usd_amount = self.pay.amount * self.glob.curs[cur_id].rate
|
|
161
|
+
if usd_amount < 50:
|
|
162
|
+
return 0
|
|
163
|
+
elif usd_amount > 100:
|
|
164
|
+
return 2
|
|
165
|
+
else:
|
|
166
|
+
return 1
|
|
167
|
+
|
|
168
|
+
async def client_target_repr(self) -> tuple[models.Addr | models.Cred, str]:
|
|
169
|
+
if self.pay.t_ex_id:
|
|
170
|
+
addr_to = (
|
|
171
|
+
await models.Addr.filter(
|
|
172
|
+
actor__ex_id=self.pay.t_ex_id, coin_id=self.pay.t_coin_id, actor__user=self.perm.user
|
|
173
|
+
)
|
|
174
|
+
.prefetch_related("actor")
|
|
175
|
+
.first()
|
|
176
|
+
)
|
|
177
|
+
ex, coin = self.glob.exs[self.pay.s_ex_id], self.glob.coins[self.pay.s_coin_id]
|
|
178
|
+
if not addr_to:
|
|
179
|
+
logging.error(f"No {coin} addr in {ex} for user: {self.perm.user.username_id}")
|
|
180
|
+
return addr_to, f"{coin} на {ex} по id: `{addr_to.actor.exid}`"
|
|
181
|
+
# иначе: реквизиты для фиата
|
|
182
|
+
cur, pm = self.glob.curs[self.pay.t_cur_id], self.glob.pmcurs[self.pay.t_pmcur_id]
|
|
183
|
+
cred = self.perm.creds[self.pay.cred_id]
|
|
184
|
+
return cred, f"{cur.ticker} на {pm} по номеру: {cred.repr()}"
|
|
185
|
+
|
|
186
|
+
async def get_merch_target(self) -> tuple[models.Addr | models.Cred, str]:
|
|
187
|
+
if self.pay.s_ex_id:
|
|
188
|
+
addr_in = (
|
|
189
|
+
await models.Addr.filter(
|
|
190
|
+
actor__ex_id=self.pay.s_ex_id, coin_id=self.pay.s_coin_id, actor__user__role__gte=Role.ADMIN
|
|
191
|
+
)
|
|
192
|
+
.prefetch_related("actor")
|
|
193
|
+
.first()
|
|
194
|
+
)
|
|
195
|
+
ex, coin = self.glob.exs[self.pay.s_ex_id], self.glob.coins[self.pay.s_coin_id]
|
|
196
|
+
if not addr_in:
|
|
197
|
+
logging.error(f"No {coin} addr in {ex}")
|
|
198
|
+
return addr_in, f"{coin} на {ex} по id: `{addr_in.actor.exid}`"
|
|
199
|
+
# иначе: реквизиты для фиатной оплаты
|
|
200
|
+
s_pmcur = await models.Pmcur.get(id=self.pay.s_pmcur_id).prefetch_related("pm__grp")
|
|
201
|
+
cred = await models.Cred.filter(
|
|
202
|
+
**({"pmcur__pm__grp": s_pmcur.pm.grp} if s_pmcur.pm.grp else {"pmcur_id": self.pay.s_pmcur_id}),
|
|
203
|
+
person__user__role__gte=Role.ADMIN,
|
|
204
|
+
).first() # todo: order by fiat.target-fiat.amount
|
|
205
|
+
cur, pm = self.glob.curs[self.pay.s_cur_id], self.glob.pmcurs[self.pay.s_pmcur_id]
|
|
206
|
+
if not cred:
|
|
207
|
+
logging.error(f"No {cur.ticker} cred for {pm}")
|
|
208
|
+
return cred, f"{cur.ticker} на {pm} по номеру: {cred.repr()}"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def fill_creds(person_id: int) -> tuple[dict[int, models.Cred], dict[int, list[int]]]:
|
|
212
|
+
cq = models.Cred.filter(person_id=person_id)
|
|
213
|
+
creds = {c.id: c for c in await cq}
|
|
214
|
+
cur_creds = {
|
|
215
|
+
pci: ids
|
|
216
|
+
for pci, ids in await cq.annotate(ids=ArrayAgg("id")).group_by("pmcur_id").values_list("pmcur_id", "ids")
|
|
217
|
+
}
|
|
218
|
+
return creds, cur_creds
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def fill_actors(person_id: int) -> dict[int, int]:
|
|
222
|
+
ex_actors = {
|
|
223
|
+
# todo: check len(ids) == 1
|
|
224
|
+
exi: ids[0]
|
|
225
|
+
for exi, ids in await models.Actor.filter(person_id=person_id)
|
|
226
|
+
.annotate(ids=ArrayAgg("id"))
|
|
227
|
+
.group_by("ex_id")
|
|
228
|
+
.values_list("ex_id", "ids")
|
|
229
|
+
}
|
|
230
|
+
return ex_actors
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def edit(msg: Message, txt: str, rm: InlineKeyboardMarkup):
|
|
234
|
+
await gather(msg.edit_text(txt), msg.edit_reply_markup(reply_markup=rm))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
async def ans(cbq: CallbackQuery, txt: str = None):
|
|
238
|
+
await cbq.answer(txt, cache_time=0)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
async def dlt(msg: Message):
|
|
242
|
+
await msg.delete()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def edt(msg: Message, txt: str, rm: InlineKeyboardMarkup):
|
|
246
|
+
if msg.message_id == msg.bot.store.perm.msg_id:
|
|
247
|
+
await msg.edit_text(txt, reply_markup=rm)
|
|
248
|
+
else: # окно вызвано в ответ на текст, а не кнопку
|
|
249
|
+
try:
|
|
250
|
+
await msg.bot.edit_message_text(
|
|
251
|
+
txt, chat_id=msg.chat.id, message_id=msg.bot.store.perm.msg_id, reply_markup=rm
|
|
252
|
+
)
|
|
253
|
+
except TelegramBadRequest as e:
|
|
254
|
+
print(msg.bot.store.perm.msg_id, e)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def fmt_sec(sec: int):
|
|
258
|
+
days = sec // (24 * 3600)
|
|
259
|
+
sec %= 24 * 3600
|
|
260
|
+
hours = sec // 3600
|
|
261
|
+
sec %= 3600
|
|
262
|
+
minutes = sec // 60
|
|
263
|
+
sec %= 60
|
|
264
|
+
|
|
265
|
+
if days > 0:
|
|
266
|
+
return f"{days}д {hours:02d}:{minutes:02d}:{sec:02d}"
|
|
267
|
+
elif hours > 0:
|
|
268
|
+
return f"{hours:02d}:{minutes:02d}:{sec:02d}"
|
|
269
|
+
else:
|
|
270
|
+
return f"{minutes:02d}:{sec:02d}"
|