xync-bot 0.0.9__tar.gz → 0.2.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xync-bot
3
- Version: 0.0.9
3
+ Version: 0.2.9
4
4
  Summary: Telegram bot with web app for xync net
5
5
  Author-email: Artemiev <mixartemev@gmail.com>
6
6
  License: MIT
@@ -10,7 +10,6 @@ Keywords: aiogram,xync-net
10
10
  Requires-Python: >=3.11
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: aiogram
13
- Requires-Dist: antifragility-schema
14
13
  Requires-Dist: python-dotenv
15
- Requires-Dist: tortoise-api-model
16
- Requires-Dist: python-wireguard
14
+ Requires-Dist: tortoise-api
15
+ Requires-Dist: xync-schema
@@ -10,12 +10,11 @@ readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  dependencies = [
12
12
  "aiogram",
13
- "antifragility-schema",
14
13
  "python-dotenv",
15
- "tortoise-api-model",
16
- "python-wireguard"
14
+ "tortoise-api",
15
+ "xync-schema",
17
16
  ]
18
- version = "0.0.9"
17
+ version = "0.2.9"
19
18
 
20
19
  [project.urls]
21
20
  Homepage = "https://gitlab.com/xync/back/tg-bot"
@@ -1,4 +1,4 @@
1
- __version__ = "0.0.5"
1
+ from aiogram.client.default import DefaultBotProperties
2
2
  from aiogram.types import MenuButtonWebApp, WebAppInfo
3
3
  import logging
4
4
  from os import getenv as env
@@ -10,12 +10,10 @@ from xync_bot.handlers import main, vpn
10
10
 
11
11
  load_dotenv()
12
12
 
13
- TOKEN = env('TOKEN')
14
13
  logging.basicConfig(filemode='a', level=logging.DEBUG)
15
- bot = Bot(token=TOKEN, parse_mode='Markdown')
14
+
15
+ bot = Bot(token=env('TOKEN'), default=DefaultBotProperties(parse_mode='Markdown'))
16
16
  dp = Dispatcher(bot=bot)
17
- dp.include_routers(vpn)
18
- dp.include_routers(main)
19
17
 
20
18
 
21
19
  async def on_startup(wh_url: str, twa_url: str, cn: AsyncpgDBClient, mbt: str = 'Go!'):
@@ -24,16 +22,18 @@ async def on_startup(wh_url: str, twa_url: str, cn: AsyncpgDBClient, mbt: str =
24
22
  """ WEBHOOK SETUP """
25
23
  webhook_info = await bot.get_webhook_info()
26
24
  if webhook_info.url != wh_url:
27
- await bot.set_webhook(url=wh_url)
25
+ await bot.set_webhook(url=wh_url, drop_pending_updates=True)
28
26
  """ WEBAPP URL SETUP IN MENU """
29
27
  await bot.set_chat_menu_button(menu_button=MenuButtonWebApp(text=mbt, web_app=WebAppInfo(url=twa_url)))
30
28
 
31
29
 
32
30
  async def on_shutdown():
33
31
  """ CLOSE BOT SESSION """
34
- await bot.delete_webhook()
32
+ await bot.delete_webhook(drop_pending_updates=True)
35
33
  await bot.session.close()
36
34
 
37
35
 
38
36
  dp.startup.register(on_startup)
39
37
  dp.shutdown.register(on_shutdown)
38
+
39
+ dp.include_routers(vpn, main)
@@ -0,0 +1,117 @@
1
+ import logging
2
+ from aiogram import Router, F
3
+ from aiogram.filters import CommandStart, CommandObject
4
+ from aiogram.filters.callback_data import CallbackData
5
+ from aiogram.types import User as TgUser, ChatMemberUpdated, Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
6
+ from aiogram.utils.deep_linking import create_start_link
7
+ from aiogram.utils.web_app import WebAppUser
8
+ from xync_schema.models import User, UserStatus, Lang
9
+
10
+ from xync_bot.shared import NavCallbackData
11
+
12
+ main = Router()
13
+
14
+
15
+ class RrCallbackData(CallbackData, prefix="reg_res"): # registration response
16
+ to: int
17
+ res: bool
18
+
19
+
20
+ home_btns = InlineKeyboardMarkup(
21
+ inline_keyboard=[[
22
+ InlineKeyboardButton(text='Invite', callback_data=NavCallbackData(to='ref_link').pack()),
23
+ InlineKeyboardButton(text='Get VPN', callback_data=NavCallbackData(to='get_vpn').pack()),
24
+ ]])
25
+
26
+
27
+ @main.message(CommandStart(deep_link=True, deep_link_encoded=True))
28
+ async def start_handler(msg: Message, command: CommandObject):
29
+ me: TgUser = msg.from_user
30
+ ref_id: int = command.args.isnumeric() and int(command.args)
31
+ user = await User.get_or_none(id=me.id, status__gte=UserStatus.RESTRICTED)
32
+ rm = None
33
+ if user:
34
+ rs, rm = f'{me.full_name}, you have registered already😉', home_btns
35
+ elif not (ref := await User.get_or_none(id=ref_id)):
36
+ rs = f'No registered user #{ref_id}😬'
37
+ else: # new user created
38
+ user, cr = await user_upsert(me)
39
+ await user.update_from_dict({'ref': ref}).save()
40
+ approve_btns = InlineKeyboardMarkup(
41
+ inline_keyboard=[[
42
+ InlineKeyboardButton(text='Отклонить', callback_data=RrCallbackData(to=user.id, res=False).pack()),
43
+ InlineKeyboardButton(text='Одобрить', callback_data=RrCallbackData(to=user.id, res=True).pack())
44
+ ]])
45
+ await msg.bot.send_message(ref.id, f'{me.full_name} просит что б Вы взяли за него/ее ответственность', reply_markup=approve_btns)
46
+ return await msg.answer(f'Please wait for @{ref.username} approving...')
47
+ return await msg.answer(rs, reply_markup=rm)
48
+
49
+
50
+ @main.callback_query(RrCallbackData.filter())
51
+ async def phrases_input_request(cb: CallbackQuery, callback_data: RrCallbackData) -> None:
52
+ protege = await User[callback_data.to]
53
+ if callback_data.res:
54
+ # protege.status = UserStatus.RESTRICTED
55
+ await protege.save()
56
+ rs = f'{cb.from_user.full_name}, теперь Вы несете ответветвенность за {protege.username}'
57
+ else:
58
+ rs = f'Вы отклонили запрос юзера "{protege.username}" на Вашу протекцию'
59
+ res = {True: 'одобрил', False: 'отклонил'}
60
+ txt = f'{cb.from_user.full_name} {res[callback_data.res]} вашу регистрацию'
61
+ txt, rm = (f'Поздравляем! {txt}💥', home_btns) if callback_data.res else (f'К сожалению {txt}😢', None)
62
+ await cb.bot.send_message(protege.id, txt, reply_markup=rm)
63
+ await cb.answer('👌🏼')
64
+ await cb.message.edit_text(rs)
65
+
66
+
67
+ @main.message(CommandStart(deep_link=True)) # attempt to reg by fake link
68
+ async def fraud_handler(msg: Message):
69
+ logging.info(f'Start: {msg.from_user.id}. Msg: {msg}')
70
+ # todo: alert to admins! Fraud attempt!
71
+ await msg.answer('🤔')
72
+
73
+
74
+ @main.message(CommandStart())
75
+ async def start_no_ref_handler(msg: Message):
76
+ me = msg.from_user
77
+ user, cr = await user_upsert(me)
78
+ rr = 'сначала вы должны найти поручителя, и перейти по его реферальной ссылке.\nhttps://telegra.ph/XyncNet-02-13'
79
+ if cr: # has ref and created now
80
+ await msg.answer(f'Здравствуйте {me.full_name}, что бы использовать возможности нашей сети, {rr}')
81
+ elif not user.ref_id:
82
+ await msg.answer(rr.capitalize())
83
+ else:
84
+ await msg.answer(f'{me.full_name}, не балуйтесь, вы и так уже активный участник👌🏼', reply_markup=home_btns)
85
+
86
+
87
+ @main.callback_query(NavCallbackData.filter(F.to == 'ref_link'))
88
+ async def ref_link_handler(cbq: CallbackQuery):
89
+ me = cbq.from_user
90
+ if not (u := await User.get_or_none(id=me.id, status__gt=UserStatus.RESTRICTED).prefetch_related('ref')):
91
+ return await cbq.answer(f"{me.full_name}, сначала сами получите одобрение поручителя😉")
92
+ link = await create_start_link(cbq.bot, str(u.id), encode=True)
93
+ logging.info(f'Start: {me.id}. Msg: {cbq}')
94
+ await cbq.message.answer(f"Your referrer is {u.ref_id and u.ref.username}"
95
+ f"\nThis is your invite link: {link}"
96
+ f"\nGive it to your protege, and approve his request")
97
+ await cbq.answer('Wait for your protege request..')
98
+
99
+
100
+ async def user_upsert(u: TgUser | WebAppUser, status: UserStatus = None) -> (User, bool):
101
+ pic = (gpp := await u.get_profile_photos(0, 1)).photos and gpp.photos[0][-1].file_unique_id if type(u) is TgUser else (u.photo_url[0] if u.photo_url else None)
102
+ udf = {'username': u.username, 'first_name': u.first_name, 'last_name': u.last_name, 'status': UserStatus.MEMBER, 'lang': u.language_code and Lang[u.language_code], 'pic': pic}
103
+ if status:
104
+ udf.update({'status': status})
105
+ return await User.update_or_create(udf, id=u.id)
106
+
107
+
108
+ @main.my_chat_member()
109
+ async def user_set_status(my_chat_member: ChatMemberUpdated):
110
+ u: TgUser = my_chat_member.from_user
111
+ new_status = my_chat_member.new_chat_member.status
112
+ await user_upsert(u, status=new_status)
113
+
114
+
115
+ @main.message()
116
+ async def del_cbq(msg: Message):
117
+ await msg.delete()
@@ -0,0 +1,48 @@
1
+ from aiogram import Router, F
2
+ from aiogram.types import BufferedInputFile, CallbackQuery
3
+ from xync_schema.models import User, Vpn, UserStatus
4
+ from os import getenv as env
5
+ from dotenv import load_dotenv
6
+
7
+ from xync_bot.shared import NavCallbackData
8
+
9
+ load_dotenv()
10
+
11
+ vpn = Router()
12
+
13
+
14
+ @vpn.callback_query(NavCallbackData.filter(F.to == 'get_vpn'))
15
+ async def get_vpn(cbq: CallbackQuery):
16
+ me = cbq.from_user
17
+ if not (u := await User.get_or_none(id=me.id, status__gt=UserStatus.RESTRICTED).prefetch_related('vpn')):
18
+ return await cbq.answer('Этот сервис только для наших доверенных участников. Что бы зарегистрироваться, '
19
+ 'вы должны перейти в бот по пригласительный ссылке от одного из действующих участников сети,'
20
+ 'и дождаться пока он одобрит вашу регистрацию.')
21
+ if not u.vpn:
22
+ try:
23
+ from python_wireguard import Key
24
+ private, public = Key.key_pair()
25
+ except OSError:
26
+ private, public = 'local_test_private_key_'+me.username, 'local_test_public_key_'+me.username
27
+ v = await Vpn.create(priv=private, pub=public, user=u)
28
+ else:
29
+ v = u.vpn
30
+ private = v.priv
31
+ txt = f'''[Interface]
32
+ PrivateKey={private}
33
+ Address=10.0.0.{v.id}/24
34
+ DNS=8.8.8.8
35
+ [Peer]
36
+ PublicKey={env('SWGPUB')}
37
+ AllowedIPs=0.0.0.0/0
38
+ Endpoint=vpn.xync.net:51820
39
+ PersistentKeepalive=60
40
+ '''
41
+ file = BufferedInputFile(txt.encode(), f'XyncVPN_{u.username}.conf')
42
+ caption = 'Import this config file to Wireguard:\n[iOS](https://apps.apple.com/us/app/wireguard/id1441195209)\n' \
43
+ '[Android](https://play.google.com/store/apps/details?id=com.wireguard.android)\n' \
44
+ '[MacOS](https://itunes.apple.com/us/app/wireguard/id1451685025)\n' \
45
+ '[Windows](https://download.wireguard.com/windows-client/wireguard-installer.exe)\n\n' \
46
+ '[Instructions](https://graph.org/VPN-02-19)'
47
+ await cbq.message.answer_document(file, caption=caption)
48
+ await cbq.answer('Your VPN is ready')
@@ -0,0 +1,5 @@
1
+ from aiogram.filters.callback_data import CallbackData
2
+
3
+
4
+ class NavCallbackData(CallbackData, prefix="nav"): # navigate menu
5
+ to: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xync-bot
3
- Version: 0.0.9
3
+ Version: 0.2.9
4
4
  Summary: Telegram bot with web app for xync net
5
5
  Author-email: Artemiev <mixartemev@gmail.com>
6
6
  License: MIT
@@ -10,7 +10,6 @@ Keywords: aiogram,xync-net
10
10
  Requires-Python: >=3.11
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: aiogram
13
- Requires-Dist: antifragility-schema
14
13
  Requires-Dist: python-dotenv
15
- Requires-Dist: tortoise-api-model
16
- Requires-Dist: python-wireguard
14
+ Requires-Dist: tortoise-api
15
+ Requires-Dist: xync-schema
@@ -1,5 +1,6 @@
1
1
  pyproject.toml
2
2
  xync_bot/__init__.py
3
+ xync_bot/shared.py
3
4
  xync_bot.egg-info/PKG-INFO
4
5
  xync_bot.egg-info/SOURCES.txt
5
6
  xync_bot.egg-info/dependency_links.txt
@@ -0,0 +1,4 @@
1
+ aiogram
2
+ python-dotenv
3
+ tortoise-api
4
+ xync-schema
@@ -1,73 +0,0 @@
1
- import logging
2
- from aiogram import Router
3
- from aiogram.filters import CommandStart, CommandObject, Command
4
- from aiogram.types import User as TgUser, ChatMemberUpdated, Message
5
- from aiogram.utils.deep_linking import create_start_link
6
- from antifragility_schema.models import Client
7
- from tortoise_api_model import User
8
- from tortoise_api_model.enum import UserStatus
9
-
10
- main = Router()
11
-
12
-
13
- @main.message(CommandStart(deep_link=True, deep_link_encoded=True))
14
- async def start_handler(msg: Message, command: CommandObject):
15
- me: TgUser = msg.from_user
16
- ref_id: int = command.args.isnumeric() and int(command.args)
17
- client: Client
18
- client, cr = await user_upsert(me)
19
- if cr:
20
- rs = 'You have registered already😉'
21
- elif client.id == ref_id:
22
- rs = 'You can not to ref yourself😒'
23
- elif not (ref := await Client.get_or_none(id=ref_id)):
24
- rs = f'No registered user #{ref_id}😬'
25
- else:
26
- rs = f'Please wait for {ref.name} approving...'
27
- await client.update_from_dict({'ref': ref}).save()
28
- return await msg.answer(rs)
29
-
30
-
31
- @main.message(CommandStart(deep_link=True))
32
- async def fraud_handler(msg: Message):
33
- logging.info(f'Start: {msg.from_user.id}. Msg: {msg}')
34
- # todo: alert to admins! Fraud attempt!
35
- await msg.answer('🤔')
36
-
37
-
38
- @main.message(CommandStart())
39
- async def start_no_ref_handler(msg: Message):
40
- me = msg.from_user
41
- # client, cr = await user_upsert(me) # todo: baskdoor for first user
42
- logging.info(f'Start: {me.id}. Msg: {msg}')
43
- await msg.answer(f'Sorry {me.full_name}, we do not accept only persons who has a guarantor.\n'
44
- 'https://telegra.ph/XyncNet-02-13')
45
-
46
-
47
- @main.message(Command('ref_link'))
48
- async def ref_link_handler(msg: Message):
49
- my_id = msg.from_user.id
50
- cl = await Client.get(user_id=my_id)
51
- link = await create_start_link(msg.bot, str(cl.id), encode=True)
52
- logging.info(f'Start: {my_id}. Msg: {msg}')
53
- await msg.answer(f"Give it to your protege: {link}")
54
-
55
-
56
- async def user_upsert(u: TgUser, status: UserStatus = None) -> (Client, bool):
57
- udf = {'username': u.username, 'password': ''}
58
- if status:
59
- udf.update({'status': status})
60
- user, is_created = await User.update_or_create(udf, id=u.id)
61
- return await Client.update_or_create({'name': u.full_name}, user=user)
62
-
63
-
64
- @main.my_chat_member()
65
- async def user_set_status(my_chat_member: ChatMemberUpdated):
66
- u: TgUser = my_chat_member.from_user
67
- new_status = UserStatus[my_chat_member.new_chat_member.status]
68
- await user_upsert(u, status=new_status)
69
-
70
-
71
- @main.message()
72
- async def del_msg(msg: Message):
73
- await msg.delete()
@@ -1,38 +0,0 @@
1
- from aiogram.types import MenuButtonWebApp, WebAppInfo
2
- import logging
3
- from os import getenv as env
4
- from dotenv import load_dotenv
5
- from aiogram import Bot, Dispatcher
6
- from tortoise.backends.asyncpg import AsyncpgDBClient
7
-
8
- from xync_bot.handlers import main, vpn
9
-
10
- load_dotenv()
11
-
12
- TOKEN = env('TOKEN')
13
- logging.basicConfig(filemode='a', level=logging.DEBUG)
14
- bot = Bot(token=TOKEN, parse_mode='Markdown')
15
- dp = Dispatcher(bot=bot)
16
- dp.include_routers(vpn)
17
- dp.include_routers(main)
18
-
19
-
20
- async def on_startup(wh_url: str, twa_url: str, cn: AsyncpgDBClient, mbt: str = 'Go!'):
21
- """ SET DEISPATCHER GLOBAL WORKFLOW DATA FOR DB Connection """
22
- dp['dbc'] = cn
23
- """ WEBHOOK SETUP """
24
- webhook_info = await bot.get_webhook_info()
25
- if webhook_info.url != wh_url:
26
- await bot.set_webhook(url=wh_url)
27
- """ WEBAPP URL SETUP IN MENU """
28
- await bot.set_chat_menu_button(menu_button=MenuButtonWebApp(text=mbt, web_app=WebAppInfo(url=twa_url)))
29
-
30
-
31
- async def on_shutdown():
32
- """ CLOSE BOT SESSION """
33
- await bot.delete_webhook()
34
- await bot.session.close()
35
-
36
-
37
- dp.startup.register(on_startup)
38
- dp.shutdown.register(on_shutdown)
@@ -1,5 +0,0 @@
1
- aiogram
2
- antifragility-schema
3
- python-dotenv
4
- tortoise-api-model
5
- python-wireguard
File without changes