xync-bot 0.3.24.dev9__tar.gz → 0.3.24.dev12__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.

Files changed (31) hide show
  1. {xync_bot-0.3.24.dev9/xync_bot.egg-info → xync_bot-0.3.24.dev12}/PKG-INFO +1 -1
  2. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/__init__.py +1 -1
  3. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/pay/cd.py +1 -0
  4. xync_bot-0.3.24.dev12/xync_bot/routers/pay/dep.py +290 -0
  5. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/pay/handler.py +21 -47
  6. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/pay/window.py +89 -52
  7. xync_bot-0.3.24.dev12/xync_bot/typs.py +0 -0
  8. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12/xync_bot.egg-info}/PKG-INFO +1 -1
  9. xync_bot-0.3.24.dev9/xync_bot/routers/__init__.py +0 -98
  10. xync_bot-0.3.24.dev9/xync_bot/routers/pay/dep.py +0 -122
  11. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/.env.dist +0 -0
  12. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/.gitignore +0 -0
  13. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/.pre-commit-config.yaml +0 -0
  14. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/makefile +0 -0
  15. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/pyproject.toml +0 -0
  16. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/setup.cfg +0 -0
  17. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/test_main.http +0 -0
  18. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/loader.py +0 -0
  19. /xync_bot-0.3.24.dev9/xync_bot/typs.py → /xync_bot-0.3.24.dev12/xync_bot/routers/__init__.py +0 -0
  20. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/cond/__init__.py +0 -0
  21. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/cond/func.py +0 -0
  22. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/main.py +0 -0
  23. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/order.py +0 -0
  24. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/photo.py +0 -0
  25. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/vpn.py +0 -0
  26. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/routers/xicon.png +0 -0
  27. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot/shared.py +0 -0
  28. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot.egg-info/SOURCES.txt +0 -0
  29. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot.egg-info/dependency_links.txt +0 -0
  30. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot.egg-info/requires.txt +0 -0
  31. {xync_bot-0.3.24.dev9 → xync_bot-0.3.24.dev12}/xync_bot.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-bot
3
- Version: 0.3.24.dev9
3
+ Version: 0.3.24.dev12
4
4
  Summary: Telegram bot with web app for xync net
5
5
  Author-email: Artemiev <mixartemev@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -5,8 +5,8 @@ from PGram import Bot
5
5
  from aiogram.client.default import DefaultBotProperties
6
6
  from x_model import init_db
7
7
 
8
- from xync_bot.routers import Store
9
8
  from xync_bot.routers.cond import cr as cr
9
+ from xync_bot.routers.pay.dep import Store
10
10
 
11
11
  # from xync_bot.routers.main import mr
12
12
  from xync_bot.routers.pay.handler import pay as pay
@@ -34,6 +34,7 @@ class Pm(CallbackData, prefix="pm"):
34
34
 
35
35
  class Ppo(CallbackData, prefix="ppo"):
36
36
  num: int
37
+ is_target: int # bool
37
38
 
38
39
 
39
40
  class PayNav(CallbackData, prefix="pay_nav"):
@@ -0,0 +1,290 @@
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
+
15
+ class Report(StatesGroup):
16
+ text = State()
17
+
18
+
19
+ class CredState(StatesGroup):
20
+ detail = State()
21
+ name = State()
22
+
23
+
24
+ class PaymentState(StatesGroup):
25
+ amount = State()
26
+ timer = State()
27
+ timer_active = State()
28
+
29
+
30
+ class ActionType(IntEnum):
31
+ """Цель (назначение) платежа (target)"""
32
+
33
+ sent = 1 # Отправил
34
+ received = 2 # Получил
35
+ not_received = 3 # Не получил
36
+
37
+
38
+ class PayStep(IntEnum):
39
+ """Цель (назначение) платежа (target)"""
40
+
41
+ t_type = 1 # Выбор типа
42
+ t_cur = 2 # Выбор валюты
43
+ t_coin = 3 # Выбор монеты
44
+ t_pm = 4 # Выбор платежки
45
+ t_ex = 5 # Выбор биржи
46
+ t_cred_dtl = 6 # Ввод номера карты
47
+ t_cred_name = 7 # Ввод имени
48
+ # t_addr = 8 # todo: позже добавим: Выбор/ввод крипто кошелька
49
+ t_amount = 9 # Ввод суммы
50
+ """ Источник платежа (source) """
51
+ s_type = 10 # Выбор типа
52
+ s_cur = 11 # Выбор типа
53
+ s_pm = 12 # Выбор типа
54
+ s_coin = 13 # Выбор типа
55
+ s_ex = 14 # Выбор типа
56
+ ppo = 15 # Выбор возможности разбивки платежа
57
+ urgency = 16 # Выбор срочности получения платежа
58
+ pending_send = 17 # Ожидание отправки (если мы платим фиатом)
59
+ pending_confirm = 18 # Ожидание пока на той стороне подтвердят получение нашего фиата (если мы платим фиатом)
60
+ pending_receive = 19 # Ожидание поступления (если мы получаем фиат)
61
+
62
+
63
+ flags = {
64
+ "RUB": "🇷🇺",
65
+ "THB": "🇹🇭",
66
+ "IDR": "🇮🇩",
67
+ "TRY": "🇹🇷",
68
+ "GEL": "🇬🇪",
69
+ "VND": "🇻🇳",
70
+ "AED": "🇦🇪",
71
+ "AMD": "🇦🇲",
72
+ "AZN": "🇦🇿",
73
+ "CNY": "🇨🇳",
74
+ "EUR": "🇪🇺",
75
+ "HKD": "🇭🇰",
76
+ "INR": "🇮🇳",
77
+ "PHP": "🇵🇭",
78
+ "USD": "🇺🇸",
79
+ }
80
+
81
+
82
+ class SingleStore(type):
83
+ _store = None
84
+
85
+ async def __call__(cls):
86
+ if not cls._store:
87
+ cls._store = super(SingleStore, cls).__call__()
88
+ cls._store.coins = {k: v for k, v in await models.Coin.all().order_by("ticker").values_list("id", "ticker")}
89
+ curs = {c.id: c for c in await models.Cur.filter(ticker__in=flags.keys()).order_by("ticker")}
90
+ cls._store.curs = curs
91
+ cls._store.exs = {k: v for k, v in await models.Ex.all().values_list("id", "name")}
92
+ cls._store.pmcurs = {
93
+ k: v
94
+ for k, v in await models.Pmex.filter(pm__pmcurs__cur_id__in=cls._store.curs.keys())
95
+ .annotate(sname=Min("name"))
96
+ .group_by("pm__pmcurs__id")
97
+ .values_list("pm__pmcurs__id", "sname")
98
+ }
99
+ cls._store.coinexs = {
100
+ c.id: [ex.ex_id for ex in c.coinexs] for c in await models.Coin.all().prefetch_related("coinexs")
101
+ }
102
+ cls._store.curpms = {
103
+ cur_id: ids
104
+ for cur_id, ids in await models.Pmcur.filter(cur_id__in=curs.keys())
105
+ .annotate(ids=ArrayAgg("id"))
106
+ .group_by("cur_id")
107
+ .values_list("cur_id", "ids")
108
+ }
109
+ cls._store.curpms = {
110
+ cur_id: ids
111
+ for cur_id, ids in await models.Pmcur.filter(cur_id__in=curs.keys())
112
+ .annotate(ids=ArrayAgg("id"))
113
+ .group_by("cur_id")
114
+ .values_list("cur_id", "ids")
115
+ }
116
+
117
+ return cls._store
118
+
119
+
120
+ class Store:
121
+ class Global(metaclass=SingleStore):
122
+ coins: dict[int, str] # id:ticker
123
+ curs: dict[int, models.Cur] # id:Cur
124
+ exs: dict[int, str] # id:name
125
+ coinexs: dict[int, list[int]] # id:[ex_ids]
126
+ pmcurs: dict[int, str] # pmcur_id:name
127
+ curpms: dict[int, list[int]] # id:[pmcur_ids]
128
+
129
+ class Permanent:
130
+ msg_id: int = None
131
+ user: models.User = None
132
+ actors: dict[int, int] = None # key=ex_id
133
+ creds: dict[int, models.Cred] = None # key=cred_id
134
+ cur_creds: dict[int, list[int]] = None # pmcur_id:[cred_ids]
135
+
136
+ class Current:
137
+ is_target: bool = True
138
+ is_fiat: bool = None
139
+ msg_to_del: Message = None
140
+
141
+ class Payment:
142
+ t_cur_id: int = None
143
+ s_cur_id: int = None
144
+ t_coin_id: int = None
145
+ s_coin_id: int = None
146
+ t_pmcur_id: int = None
147
+ s_pmcur_id: int = None
148
+ t_ex_id: int = None
149
+ s_ex_id: int = None
150
+ amount: int | float = None
151
+ ppo: int = 1
152
+ addr_id: int = None
153
+ cred_dtl: str = None
154
+ cred_id: int = None
155
+ urg: int = 5
156
+ pr_id: int = None
157
+
158
+ glob: Global
159
+ perm: Permanent = Permanent()
160
+ pay: Payment = Payment()
161
+ curr: Current = Current()
162
+
163
+
164
+ async def fill_creds(person_id: int) -> tuple[dict[int, models.Cred], dict[int, list[int]]]:
165
+ cq = models.Cred.filter(person_id=person_id)
166
+ creds = {c.id: c for c in await cq}
167
+ cur_creds = {
168
+ pci: ids
169
+ for pci, ids in await cq.annotate(ids=ArrayAgg("id")).group_by("pmcur_id").values_list("pmcur_id", "ids")
170
+ }
171
+ return creds, cur_creds
172
+
173
+
174
+ async def fill_actors(person_id: int) -> dict[int, int]:
175
+ ex_actors = {
176
+ # todo: check len(ids) == 1
177
+ exi: ids[0]
178
+ for exi, ids in await models.Actor.filter(person_id=person_id)
179
+ .annotate(ids=ArrayAgg("id"))
180
+ .group_by("ex_id")
181
+ .values_list("ex_id", "ids")
182
+ }
183
+ return ex_actors
184
+
185
+
186
+ async def edit(msg: Message, txt: str, rm: InlineKeyboardMarkup):
187
+ await gather(msg.edit_text(txt), msg.edit_reply_markup(reply_markup=rm))
188
+
189
+
190
+ async def ans(cbq: CallbackQuery, txt: str = None):
191
+ await cbq.answer(txt, cache_time=0)
192
+
193
+
194
+ async def dlt(msg: Message):
195
+ await msg.delete()
196
+
197
+
198
+ async def edt(msg: Message, txt: str, rm: InlineKeyboardMarkup):
199
+ if msg.message_id == msg.bot.store.perm.msg_id:
200
+ await msg.edit_text(txt, reply_markup=rm)
201
+ else: # окно вызвано в ответ на текст, а не кнопку
202
+ try:
203
+ await msg.bot.edit_message_text(
204
+ txt, chat_id=msg.chat.id, message_id=msg.bot.store.perm.msg_id, reply_markup=rm
205
+ )
206
+ except TelegramBadRequest as e:
207
+ print(msg.bot.store.perm.msg_id, e)
208
+
209
+
210
+ async def xync_have_coin_amount(store: Store) -> bool:
211
+ assets = await models.Asset.filter(
212
+ addr__coin_id=store.pay.t_coin_id, addr__ex_id=store.pay.t_ex_id, addr__actor__user__role__in=Role.ADMIN
213
+ )
214
+ return store.pay.amount <= sum(a.free for a in assets)
215
+
216
+
217
+ async def client_have_coin_amount(store: Store) -> bool:
218
+ assets = await models.Asset.filter(addr__coin_id=store.pay.t_coin_id, addr__actor_id__in=store.perm.actors.values())
219
+ return store.pay.amount <= sum(a.free for a in assets)
220
+
221
+
222
+ async def need_ppo(store: Store):
223
+ cur_id = getattr(store.pay, ("t" if store.curr.is_target else "s") + "_cur_id")
224
+ usd_amount = store.pay.amount * store.glob.curs[cur_id].rate
225
+ if usd_amount < 50:
226
+ return 0
227
+ elif usd_amount > 100:
228
+ return 2
229
+ else:
230
+ return 1
231
+
232
+
233
+ async def client_target_repr(store: Store) -> tuple[models.Addr | models.Cred, str]:
234
+ if store.pay.t_ex_id:
235
+ addr_to = (
236
+ await models.Addr.filter(
237
+ actor__ex_id=store.pay.t_ex_id, coin_id=store.pay.t_coin_id, actor__user=store.perm.user
238
+ )
239
+ .prefetch_related("actor")
240
+ .first()
241
+ )
242
+ ex, coin = store.glob.exs[store.pay.s_ex_id], store.glob.coins[store.pay.s_coin_id]
243
+ if not addr_to:
244
+ logging.error(f"No {coin} addr in {ex} for user: {store.perm.user.username_id}")
245
+ return addr_to, f"{coin} на {ex} по id: `{addr_to.actor.exid}`"
246
+ # иначе: реквизиты для фиата
247
+ cur, pm = store.glob.curs[store.pay.t_cur_id], store.glob.pmcurs[store.pay.t_pmcur_id]
248
+ cred = store.perm.creds[store.pay.cred_id]
249
+ return cred, f"{cur.ticker} на {pm} по номеру: {cred.repr()}"
250
+
251
+
252
+ async def get_merch_target(store: Store) -> tuple[models.Addr | models.Cred, str]:
253
+ if store.pay.s_ex_id:
254
+ addr_in = (
255
+ await models.Addr.filter(
256
+ actor__ex_id=store.pay.s_ex_id, coin_id=store.pay.s_coin_id, actor__user__role__gte=Role.ADMIN
257
+ )
258
+ .prefetch_related("actor")
259
+ .first()
260
+ )
261
+ ex, coin = store.glob.exs[store.pay.s_ex_id], store.glob.coins[store.pay.s_coin_id]
262
+ if not addr_in:
263
+ logging.error(f"No {coin} addr in {ex}")
264
+ return addr_in, f"{coin} на {ex} по id: `{addr_in.actor.exid}`"
265
+ # иначе: реквизиты для фиатной оплаты
266
+ s_pmcur = await models.Pmcur.get(id=store.pay.s_pmcur_id).prefetch_related("pm__grp")
267
+ cred = await models.Cred.filter(
268
+ **({"pmcur__pm__grp": s_pmcur.pm.grp} if s_pmcur.pm.grp else {"pmcur_id": store.pay.s_pmcur_id}),
269
+ person__user__role__gte=Role.ADMIN,
270
+ ).first() # todo: order by fiat.target-fiat.amount
271
+ cur, pm = store.glob.curs[store.pay.s_cur_id], store.glob.pmcurs[store.pay.s_pmcur_id]
272
+ if not cred:
273
+ logging.error(f"No {cur.ticker} cred for {pm}")
274
+ return cred, f"{cur.ticker} на {pm} по номеру: {cred.repr()}"
275
+
276
+
277
+ def fmt_sec(sec: int):
278
+ days = sec // (24 * 3600)
279
+ sec %= 24 * 3600
280
+ hours = sec // 3600
281
+ sec %= 3600
282
+ minutes = sec // 60
283
+ sec %= 60
284
+
285
+ if days > 0:
286
+ return f"{days}д {hours:02d}:{minutes:02d}:{sec:02d}"
287
+ elif hours > 0:
288
+ return f"{hours:02d}:{minutes:02d}:{sec:02d}"
289
+ else:
290
+ return f"{minutes:02d}:{sec:02d}"
@@ -1,15 +1,14 @@
1
- from asyncio import create_task, gather
2
- from datetime import timedelta, datetime
1
+ from asyncio import gather
2
+ from datetime import datetime
3
3
 
4
4
  import PGram
5
5
  from aiogram import Router, F
6
6
  from aiogram.filters import Command
7
7
  from aiogram.types import Message, CallbackQuery
8
8
  from aiogram.fsm.context import FSMContext
9
- from xync_bot.routers.pay.dep import fill_creds, fill_actors, dlt, ans
9
+ from xync_bot.routers.pay.dep import fill_creds, fill_actors, dlt, ans, Store
10
10
  from xync_schema import models
11
11
 
12
- from xync_bot import Store
13
12
  from xync_bot.routers.pay import cd, dep, window
14
13
 
15
14
  pay = Router()
@@ -23,6 +22,7 @@ async def h_start(msg: Message):
23
22
  await gather(window.type_select(msg), dlt(msg))
24
23
  store.perm.user = await models.User.get(username_id=msg.from_user.id)
25
24
  store.perm.creds, store.perm.cur_creds = await fill_creds(store.perm.user.person_id)
25
+ store.perm.actors = await fill_actors(store.perm.user.person_id)
26
26
 
27
27
 
28
28
  @pay.callback_query(cd.MoneyType.filter(F.is_fiat))
@@ -36,9 +36,7 @@ async def h_got_fiat_type(query: CallbackQuery, bot: PGram):
36
36
  async def h_got_crypto_type(query: CallbackQuery, bot: PGram):
37
37
  """Step 2c: Select coin"""
38
38
  bot.store.curr.is_fiat = False
39
- (bot.store.perm.actors, bot.store.perm.ex_actors), *_ = await gather(
40
- fill_actors(bot.store.perm.user.person_id), window.coin_select(query.message), ans(query, "Понял, крипта")
41
- )
39
+ await gather(window.coin_select(query.message), ans(query, "Понял, крипта"))
42
40
 
43
41
 
44
42
  @pay.callback_query(cd.Coin.filter())
@@ -91,7 +89,8 @@ async def h_got_cred_name(msg: Message, state: FSMContext):
91
89
  person_id=store.perm.user.person_id,
92
90
  pmcur_id=store.pay.t_pmcur_id,
93
91
  )
94
- msg.bot.store.pay.cred_id = cred.id
92
+ store.pay.cred_id = cred.id
93
+ store.perm.creds[cred.id] = cred
95
94
  await gather(window.amount(msg), dlt(msg), state.set_state(dep.PaymentState.amount))
96
95
 
97
96
 
@@ -101,11 +100,15 @@ async def h_got_ex(query: CallbackQuery, callback_data: cd.Ex, state: FSMContext
101
100
  store: Store = query.message.bot.store
102
101
  ist = store.curr.is_target
103
102
  setattr(store.pay, ("t" if ist else "s") + "_ex_id", callback_data.id)
104
- await gather(
105
- (window.amount if ist else window.set_ppo)(query.message),
106
- ans(query, f"Биржа {store.glob.exs[callback_data.id]} выбрана"),
107
- state.set_state(dep.PaymentState.amount),
108
- )
103
+ if ist:
104
+ await window.amount(query.message)
105
+ actor_id = store.perm.actors[store.pay.t_ex_id]
106
+ addr = await models.Addr.get(coin_id=store.pay.t_coin_id, actor_id=actor_id)
107
+ store.pay.addr_id = addr.id
108
+ else:
109
+ await window.set_ppo(query.message)
110
+ await ans(query, f"Биржа {store.glob.exs[callback_data.id]} выбрана")
111
+ await state.set_state(dep.PaymentState.amount)
109
112
 
110
113
 
111
114
  @pay.message(dep.PaymentState.amount)
@@ -129,7 +132,7 @@ async def h_got_source_pm(query: CallbackQuery, callback_data: cd.Pm):
129
132
  store.pay.s_pmcur_id = callback_data.pmcur_id
130
133
  await gather(
131
134
  window.set_ppo(query.message),
132
- ans(query, store.glob.pms[callback_data.pmcur_id]),
135
+ ans(query, store.glob.pmcurs[callback_data.pmcur_id]),
133
136
  )
134
137
 
135
138
 
@@ -140,39 +143,10 @@ async def h_got_ppo(query: CallbackQuery, callback_data: cd.Ppo):
140
143
 
141
144
 
142
145
  @pay.callback_query(cd.Time.filter())
143
- async def process_time_selection(query: CallbackQuery, callback_data: cd.Time, state: FSMContext):
144
- store: Store = query.message.bot.store
145
-
146
- pay_until = datetime.now() + timedelta(minutes=callback_data.minutes)
147
-
148
- if ex_id := (store.pay.t_ex_id or store.pay.s_ex_id):
149
- actor_id = store.perm.ex_actors[ex_id]
150
- if not (addr_id := store.pay.addr_id):
151
- coin_id = store.pay.t_coin_id or store.pay.s_coin_id
152
- addr_id = await models.Addr.get(coin_id=coin_id, actor_id=actor_id).values_list("id", flat=True)
153
- store.pay.addr_id = addr_id
154
- else:
155
- addr_id = None
156
- pr_data = dict(
157
- pay_until=pay_until,
158
- amount=store.pay.amount,
159
- parts=store.pay.ppo,
160
- payed_at=None,
161
- addr_id=addr_id,
162
- cred_id=store.pay.cred_id,
163
- user=store.perm.user,
164
- )
165
- (pay_req, _), *__ = await gather(
166
- models.PayReq.update_or_create(**pr_data), ans(query, None), state.set_state(dep.PaymentState.timer)
167
- )
168
-
169
- await state.update_data(
170
- timer=callback_data.minutes,
171
- timer_active=True,
172
- pay_until=pay_until,
173
- pay_req_id=pay_req.id,
174
- )
175
- create_task(window.run_timer(query.message, state))
146
+ async def h_got_urgency(query: CallbackQuery, callback_data: cd.Time):
147
+ query.message.bot.store.pay.urg = callback_data.minutes
148
+ await window.create_payreq(query.message)
149
+ await ans(query, f"Ok {callback_data.minutes} min.")
176
150
 
177
151
 
178
152
  # ACTIONS
@@ -1,11 +1,21 @@
1
1
  from asyncio import sleep
2
+ from datetime import datetime, timedelta
2
3
 
3
4
  from aiogram.fsm.context import FSMContext
4
5
  from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
5
6
  from aiogram.utils.keyboard import InlineKeyboardBuilder
6
- from xync_bot.routers.pay.dep import edt
7
+ from xync_schema import models
8
+
9
+ from xync_bot.routers.pay.dep import (
10
+ edt,
11
+ need_ppo,
12
+ Store,
13
+ get_merch_target,
14
+ fmt_sec,
15
+ client_have_coin_amount,
16
+ client_target_repr,
17
+ )
7
18
 
8
- from xync_bot import Store
9
19
  from xync_bot.routers.pay import cd, dep
10
20
 
11
21
 
@@ -25,7 +35,16 @@ async def type_select(msg: Message):
25
35
  ]
26
36
  ]
27
37
  )
28
- txt = "Что нужно?" if store.curr.is_target else "Чем платишь?"
38
+ if store.curr.is_target:
39
+ txt = "Что нужно?"
40
+ else:
41
+ if store.pay.t_coin_id:
42
+ inf = f"{store.glob.coins[store.pay.t_coin_id]} на {store.glob.exs[store.pay.t_ex_id]}:{store.pay.addr_id}"
43
+ else:
44
+ cur = store.glob.curs[store.pay.t_cur_id].ticker
45
+ cred: models.Cred = store.perm.creds[store.pay.cred_id]
46
+ inf = f"{cur} на {store.glob.pmcurs[store.pay.t_pmcur_id]}: {cred.repr()}"
47
+ txt = f"Нужен платеж: {store.pay.amount} {inf}\nЧем будете платить?"
29
48
  if store.perm.msg_id:
30
49
  await edt(msg, txt, rm)
31
50
  else:
@@ -37,8 +56,8 @@ async def cur_select(msg: Message):
37
56
  """Common using cur func"""
38
57
  builder = InlineKeyboardBuilder()
39
58
  ist: bool = msg.bot.store.curr.is_target
40
- for cur_id, ticker in msg.bot.store.glob.curs.items():
41
- builder.button(text=ticker + dep.flags[ticker], callback_data=cd.Cur(id=cur_id, is_target=ist))
59
+ for cur_id, cur in msg.bot.store.glob.curs.items():
60
+ builder.button(text=cur.ticker + dep.flags[cur.ticker], callback_data=cd.Cur(id=cur_id, is_target=ist))
42
61
  builder.button(text="Назад к выбору типа", callback_data=cd.PayNav(to=cd.PayStep.t_type))
43
62
  builder.adjust(3, 3, 3, 3, 3, 1)
44
63
  sfx = "ую нужно" if ist else "ой платишь"
@@ -82,7 +101,7 @@ async def pm(msg: Message):
82
101
  cur_id = getattr(store.pay, ("t" if ist else "s") + "_cur_id")
83
102
  builder = InlineKeyboardBuilder()
84
103
  for pmcur_id in store.glob.curpms[cur_id]:
85
- builder.button(text=store.glob.pms[pmcur_id], callback_data=cd.Pm(pmcur_id=pmcur_id, is_target=ist))
104
+ builder.button(text=store.glob.pmcurs[pmcur_id], callback_data=cd.Pm(pmcur_id=pmcur_id, is_target=ist))
86
105
  builder.button(
87
106
  text="Назад к выбору валюты", callback_data=cd.PayNav(to=cd.PayStep.t_cur if ist else cd.PayStep.s_cur)
88
107
  )
@@ -99,10 +118,7 @@ async def fill_cred_dtl(msg: Message):
99
118
  if cred_ids := store.perm.cur_creds.get(store.pay.t_pmcur_id):
100
119
  for cred_id in cred_ids:
101
120
  cred = store.perm.creds[cred_id]
102
- txt = f"{cred.detail}\n{cred.name}"
103
- if cred.extra:
104
- txt += f" ({cred.extra})"
105
- builder.button(text=txt, callback_data=cd.Cred(id=cred_id))
121
+ builder.button(text=cred.repr(), callback_data=cd.Cred(id=cred_id))
106
122
  txt = "Выберите реквизиты куда нужно получить деньги, если в списке нет нужных, то\nв"
107
123
 
108
124
  builder.button(text="Назад к выбору платежной системы", callback_data=cd.PayNav(to=cd.PayStep.t_pm))
@@ -110,7 +126,7 @@ async def fill_cred_dtl(msg: Message):
110
126
  builder.adjust(2)
111
127
 
112
128
  await msg.edit_text(
113
- f"{txt}ведите номер для {store.glob.pms[store.pay.t_pmcur_id]}:", reply_markup=builder.as_markup()
129
+ f"{txt}ведите номер для {store.glob.pmcurs[store.pay.t_pmcur_id]}:", reply_markup=builder.as_markup()
114
130
  )
115
131
 
116
132
 
@@ -121,9 +137,9 @@ async def fill_cred_name(msg: Message):
121
137
  builder.adjust(2)
122
138
  store: Store = msg.bot.store
123
139
  cur = store.glob.curs[store.pay.t_cur_id]
124
- payment = store.glob.pms[store.pay.t_pmcur_id]
140
+ payment = store.glob.pmcurs[store.pay.t_pmcur_id]
125
141
  detail = store.pay.cred_dtl
126
- await edt(msg, f"{cur}:{payment}:{detail}: Введите имя получателя", builder.as_markup())
142
+ await edt(msg, f"{cur.ticker}:{payment}:{detail}: Введите имя получателя", builder.as_markup())
127
143
 
128
144
 
129
145
  async def amount(msg: Message):
@@ -131,9 +147,9 @@ async def amount(msg: Message):
131
147
  builder = InlineKeyboardBuilder()
132
148
  store: Store = msg.bot.store
133
149
  if store.curr.is_fiat:
134
- cur_coin = store.glob.curs[store.pay.t_cur_id]
150
+ cur_coin = store.glob.curs[store.pay.t_cur_id].ticker
135
151
  builder.button(text="Назад к вводу имени", callback_data=cd.PayNav(to=cd.PayStep.t_cred_name))
136
- t_name = store.glob.pms[store.pay.t_pmcur_id]
152
+ t_name = store.glob.pmcurs[store.pay.t_pmcur_id]
137
153
  else:
138
154
  cur_coin = store.glob.coins[store.pay.t_coin_id]
139
155
  builder.button(text="Назад к выбору биржи", callback_data=cd.PayNav(to=cd.PayStep.t_ex))
@@ -146,55 +162,76 @@ async def amount(msg: Message):
146
162
 
147
163
 
148
164
  async def set_ppo(msg: Message):
165
+ store: Store = msg.bot.store
166
+ ist = store.curr.is_target
167
+ if nppo := await need_ppo(store):
168
+ builder = InlineKeyboardBuilder()
169
+ builder.button(text="Нет", callback_data=cd.Ppo(num=1, is_target=ist)).button(
170
+ text="Да", callback_data=cd.Ppo(num=2, is_target=ist)
171
+ )
172
+ if nppo > 1:
173
+ builder.button(text="Да хоть 3мя", callback_data=cd.Ppo(num=3, is_target=ist))
174
+ builder.adjust(2)
175
+ await edt(msg, f"2мя платежами сможете {'принять' if ist else 'отравить'}?", builder.as_markup())
176
+ elif ist:
177
+ store.curr.is_target = False
178
+ await type_select(msg)
179
+ else:
180
+ await set_urgency(msg)
181
+
182
+
183
+ async def set_urgency(msg: Message):
184
+ store: Store = msg.bot.store
185
+ if not store.curr.is_fiat or await client_have_coin_amount(store):
186
+ return await create_payreq(msg) # next
187
+ builder = InlineKeyboardBuilder()
188
+ (
189
+ builder.button(text="1 мин", callback_data=cd.Time(minutes=1))
190
+ .button(text="5 мин", callback_data=cd.Time(minutes=5))
191
+ .button(text="30 мин", callback_data=cd.Time(minutes=30))
192
+ .button(text="3 часа", callback_data=cd.Time(minutes=180))
193
+ .button(text="сутки", callback_data=cd.Time(minutes=60 * 24))
194
+ .button(text="Назад к выбору платежной\nсистемы для отправки", callback_data=cd.PayNav(to=cd.PayStep.s_pm))
195
+ .button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
196
+ .adjust(2, 2, 1, 1, 1)
197
+ )
198
+ return await edt(msg, "Сколько можешь ждать?", builder.as_markup())
199
+
200
+
201
+ async def create_payreq(msg: Message):
202
+ store: Store = msg.bot.store
203
+ pay_req, _ = await models.PayReq.update_or_create(
204
+ {"pay_until": datetime.now() + timedelta(minutes=store.pay.urg)},
205
+ amount=store.pay.amount,
206
+ parts=store.pay.ppo,
207
+ addr_id=store.pay.addr_id,
208
+ cred_id=store.pay.cred_id,
209
+ user=store.perm.user,
210
+ )
211
+ store.pay.pr_id = pay_req.id
212
+ inp, txt = await get_merch_target(store)
213
+ ccred, ctxt = await client_target_repr(store)
214
+ txt += f"\nИ получите {store.pay.amount} {ctxt} в течение {fmt_sec(store.pay.urg * 60)}"
215
+ if store.pay.ppo > 1:
216
+ txt += f" максимум {store.pay.ppo} платежами"
149
217
  rm = InlineKeyboardMarkup(
150
218
  inline_keyboard=[
151
- [
152
- InlineKeyboardButton(text="Нет", callback_data="ppo:1"),
153
- InlineKeyboardButton(text="Да", callback_data="ppo:2"),
154
- ],
155
- [InlineKeyboardButton(text="Да хоть на 3", callback_data="ppo:3")],
219
+ [InlineKeyboardButton(text="Отправил", callback_data=cd.Action(act=cd.ActionType.sent).pack())],
156
220
  ]
157
221
  )
158
- await msg.edit_text("На 2 платежа сможем разбить?", reply_markup=rm)
222
+ await edt(msg, f"Отправьте {100500} " + txt, rm) # todo: get rate
159
223
 
160
-
161
- async def set_urgency(msg: Message):
162
- builder = InlineKeyboardBuilder()
163
- builder.button(text="1 мин", callback_data=cd.Time(minutes=1))
164
- builder.button(text="5 мин", callback_data=cd.Time(minutes=5))
165
- builder.button(text="30 мин", callback_data=cd.Time(minutes=30))
166
- builder.button(text="3 часа", callback_data=cd.Time(minutes=180))
167
- builder.button(text="сутки", callback_data=cd.Time(minutes=60 * 24))
168
- builder.button(text="Назад к вводу платежей", callback_data=cd.PayNav(to=cd.PayStep.t_pm))
169
- builder.button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
170
- builder.adjust(2, 2, 1, 1, 1)
171
- await msg.edit_text("Сколько можешь ждать?", reply_markup=builder.as_markup())
224
+ # create_task(window.run_timer(msg))В
172
225
 
173
226
 
174
227
  async def run_timer(message, state: FSMContext):
175
228
  builder = InlineKeyboardBuilder()
176
229
  builder.button(text="Платеж получен", callback_data=cd.Action(act=cd.ActionType.received))
177
230
 
178
- data = await state.get_value("timer")
179
- seconds = data * 60
180
-
181
- def format(sec):
182
- days = sec // (24 * 3600)
183
- sec %= 24 * 3600
184
- hours = sec // 3600
185
- sec %= 3600
186
- minutes = sec // 60
187
- sec %= 60
188
-
189
- if days > 0:
190
- return f"{days}д {hours:02d}:{minutes:02d}:{sec:02d}"
191
- elif hours > 0:
192
- return f"{hours:02d}:{minutes:02d}:{sec:02d}"
193
- else:
194
- return f"{minutes:02d}:{sec:02d}"
231
+ seconds = await state.get_value("timer") * 60
195
232
 
196
233
  try:
197
- await message.edit_text(f"⏳ Осталось {format(seconds)}", reply_markup=builder.as_markup())
234
+ await message.edit_text(f"⏳ Осталось {fmt_sec(seconds)}", reply_markup=builder.as_markup())
198
235
  except Exception:
199
236
  return
200
237
 
@@ -202,7 +239,7 @@ async def run_timer(message, state: FSMContext):
202
239
  await sleep(1)
203
240
  seconds -= 1
204
241
  try:
205
- await message.edit_text(f"⏳ Осталось {format(seconds)}", reply_markup=builder.as_markup())
242
+ await message.edit_text(f"⏳ Осталось {fmt_sec(seconds)}", reply_markup=builder.as_markup())
206
243
  await state.update_data(timer=seconds)
207
244
  except Exception:
208
245
  break
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-bot
3
- Version: 0.3.24.dev9
3
+ Version: 0.3.24.dev12
4
4
  Summary: Telegram bot with web app for xync net
5
5
  Author-email: Artemiev <mixartemev@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -1,98 +0,0 @@
1
- from aiogram.types import Message
2
- from tortoise.functions import Min
3
- from x_model.func import ArrayAgg
4
- from xync_schema import models
5
-
6
- from xync_bot.routers.pay.dep import flags
7
-
8
-
9
- class SingleStore(type):
10
- _store = None
11
-
12
- async def __call__(cls):
13
- if not cls._store:
14
- cls._store = super(SingleStore, cls).__call__()
15
- cls._store.coins = {k: v for k, v in await models.Coin.all().order_by("ticker").values_list("id", "ticker")}
16
- curs = {
17
- k: v
18
- for k, v in await models.Cur.filter(ticker__in=flags.keys())
19
- .order_by("ticker")
20
- .values_list("id", "ticker")
21
- }
22
- cls._store.curs = curs
23
- cls._store.exs = {k: v for k, v in await models.Ex.all().values_list("id", "name")}
24
- cls._store.pms = {
25
- k: v
26
- for k, v in await models.Pmex.filter(pm__pmcurs__cur_id__in=cls._store.curs.keys())
27
- .annotate(sname=Min("name"))
28
- .group_by("pm__pmcurs__id")
29
- .values_list("pm__pmcurs__id", "sname")
30
- }
31
- cls._store.coinexs = {
32
- c.id: [ex.ex_id for ex in c.coinexs] for c in await models.Coin.all().prefetch_related("coinexs")
33
- }
34
- cls._store.curpms = {
35
- cur_id: ids
36
- for cur_id, ids in await models.Pmcur.filter(cur_id__in=curs.keys())
37
- .annotate(ids=ArrayAgg("id"))
38
- .group_by("cur_id")
39
- .values_list("cur_id", "ids")
40
- }
41
- cls._store.curpms = {
42
- cur_id: ids
43
- for cur_id, ids in await models.Pmcur.filter(cur_id__in=curs.keys())
44
- .annotate(ids=ArrayAgg("id"))
45
- .group_by("cur_id")
46
- .values_list("cur_id", "ids")
47
- }
48
-
49
- return cls._store
50
-
51
-
52
- class Store:
53
- class Global(metaclass=SingleStore):
54
- coins: dict[int, str] # id:ticker
55
- curs: dict[int, str] # id:ticker
56
- exs: dict[int, str] # id:name
57
- coinexs: dict[int, list[int]] # id:[ex_ids]
58
- pms: dict[int, str] # pmcur_id:name
59
- curpms: dict[int, list[int]] # id:[pmcur_ids]
60
-
61
- class Permanent:
62
- msg_id: int = None
63
- user: models.User = None
64
- actors: dict[int, models.Actor] = None # key=actor_id
65
- ex_actors: dict[int, list[int]] = None # key=ex_id
66
- creds: dict[int, models.Cred] = None # key=cred_id
67
- cur_creds: dict[int, list[int]] = None # pmcur_id:[cred_ids]
68
-
69
- class Current:
70
- is_target: bool = True
71
- is_fiat: bool = None
72
- msg_to_del: Message = None
73
-
74
- class Payment:
75
- t_cur_id: int = None
76
- s_cur_id: int = None
77
- t_coin_id: int = None
78
- s_coin_id: int = None
79
- t_pmcur_id: int = None
80
- s_pmcur_id: int = None
81
- t_ex_id: int = None
82
- s_ex_id: int = None
83
- amount: int | float = None
84
- ppo: int = None
85
- addr_id: int = None
86
- cred_dtl: str = None
87
- cred_id: int = None
88
-
89
- class Payreq:
90
- id: int = None
91
- created_at: int = None
92
- payed_at: int = None
93
-
94
- glob: Global
95
- perm: Permanent = Permanent()
96
- pay: Payment = Payment()
97
- pr: Payreq = Payreq()
98
- curr: Current = Current()
@@ -1,122 +0,0 @@
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
- flags = {
61
- "RUB": "🇷🇺",
62
- "THB": "🇹🇭",
63
- "IDR": "🇮🇩",
64
- "TRY": "🇹🇷",
65
- "GEL": "🇬🇪",
66
- "VND": "🇻🇳",
67
- "AED": "🇦🇪",
68
- "AMD": "🇦🇲",
69
- "AZN": "🇦🇿",
70
- "CNY": "🇨🇳",
71
- "EUR": "🇪🇺",
72
- "HKD": "🇭🇰",
73
- "INR": "🇮🇳",
74
- "PHP": "🇵🇭",
75
- "USD": "🇺🇸",
76
- }
77
-
78
-
79
- async def fill_creds(person_id: int) -> tuple[dict[int, models.Cred], dict[int, list[int]]]:
80
- cq = models.Cred.filter(person_id=person_id)
81
- creds = {c.id: c for c in await cq}
82
- cur_creds = {
83
- pci: ids
84
- for pci, ids in await cq.annotate(ids=ArrayAgg("id")).group_by("pmcur_id").values_list("pmcur_id", "ids")
85
- }
86
- return creds, cur_creds
87
-
88
-
89
- async def fill_actors(person_id: int) -> tuple[dict[int, models.Actor], dict[int, list[int]]]:
90
- aq = models.Actor.filter(person_id=person_id)
91
- actors = {a.id: a for a in await aq}
92
- ex_act_id = {
93
- exi: ids[0]
94
- for exi, ids in await aq.annotate(ids=ArrayAgg("id")) # todo: check len(ids) == 1
95
- .group_by("ex_id")
96
- .values_list("ex_id", "ids")
97
- }
98
- return actors, ex_act_id
99
-
100
-
101
- async def edit(msg: Message, txt: str, rm: InlineKeyboardMarkup):
102
- await gather(msg.edit_text(txt), msg.edit_reply_markup(reply_markup=rm))
103
-
104
-
105
- async def ans(cbq: CallbackQuery, txt: str = None):
106
- await cbq.answer(txt, cache_time=0)
107
-
108
-
109
- async def dlt(msg: Message):
110
- await msg.delete()
111
-
112
-
113
- async def edt(msg: Message, txt: str, rm: InlineKeyboardMarkup):
114
- if msg.message_id == msg.bot.store.perm.msg_id:
115
- await msg.edit_text(txt, reply_markup=rm)
116
- else: # окно вызвано в ответ на текст, а не кнопку
117
- try:
118
- await msg.bot.edit_message_text(
119
- txt, chat_id=msg.chat.id, message_id=msg.bot.store.perm.msg_id, reply_markup=rm
120
- )
121
- except TelegramBadRequest as e:
122
- print(msg.bot.store.perm.msg_id, e)
File without changes