Initial local state
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import json
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiohttp import ClientConnectorCertificateError
|
||||
|
||||
from tgbot.database import Paymentsx
|
||||
from tgbot.utils.const_functions import ded, to_number
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
from tgbot.utils.misc_functions import send_admins
|
||||
|
||||
# Список поддерживаемых валют
|
||||
ALLOW_CURRENCIES = ['BTC', 'ETH', 'LTC', 'USDT', 'USDC', 'TRX', 'TON', 'BNB', 'SOL', 'DOGE']
|
||||
|
||||
|
||||
# АПИ для работы с CryptoBot
|
||||
class CryptobotAPI:
|
||||
# Настройка клиента CryptoBot
|
||||
def __init__(
|
||||
self,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: str = "None",
|
||||
adding: bool = False,
|
||||
skipping_error: bool = False,
|
||||
):
|
||||
self.bot = bot
|
||||
self.arSession = arSession
|
||||
self.update = update
|
||||
self.token = token
|
||||
self.adding = adding
|
||||
self.skipping_error = skipping_error
|
||||
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(
|
||||
cls,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: Optional[str] = None,
|
||||
skipping_error: bool = False,
|
||||
) -> "CryptobotAPI":
|
||||
adding = token is not None
|
||||
|
||||
if token is None:
|
||||
payments = await Paymentsx().get()
|
||||
token = payments.cryptobot_token
|
||||
adding = False
|
||||
|
||||
return cls(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=update,
|
||||
token=token,
|
||||
adding=adding,
|
||||
skipping_error=skipping_error,
|
||||
)
|
||||
|
||||
# Уведомления о неработоспособности кассы/кошелька
|
||||
async def error_notification(self, error_code: str = "Unknown"):
|
||||
bot_logger.warning("CryptoBot недоступен: %s", error_code)
|
||||
|
||||
if not self.skipping_error:
|
||||
if self.adding and self.update is not None:
|
||||
await self.update.edit_text(
|
||||
f"<b>🔷 Не удалось добавить CryptoBot кассу ❌</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
)
|
||||
else:
|
||||
await send_admins(
|
||||
self.bot,
|
||||
f"<b>🔷 CryptoBot недоступен. Как можно быстрее его замените</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
)
|
||||
|
||||
# Проверка кассы/кошелька
|
||||
async def check(self) -> Tuple[bool, str]:
|
||||
status, response = await self._request("getMe")
|
||||
|
||||
if status and response['ok']:
|
||||
return True, ded(f"""
|
||||
<b>🔷 CryptoBot кошелёк полностью функционирует ✅</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Токен: <code>{self.token}</code>
|
||||
▪️ Айди: <code>{response['result']['app_id']}</code>
|
||||
▪️ Имя: <code>{response['result']['name']}</code>
|
||||
""")
|
||||
|
||||
return False, "<b>🔷 Не удалось проверить CryptoBot кошелёк ❌</b>"
|
||||
|
||||
# Получение баланса
|
||||
async def balance(self) -> str:
|
||||
status, response = await self._request("getBalance")
|
||||
|
||||
if status and response['ok']:
|
||||
save_currencies = []
|
||||
|
||||
response_balances = sorted(
|
||||
response['result'],
|
||||
reverse=True,
|
||||
key=lambda balance: to_number(balance['available']),
|
||||
)
|
||||
|
||||
for currency in response_balances:
|
||||
if currency['currency_code'] in ALLOW_CURRENCIES:
|
||||
save_currencies.append(
|
||||
f"▪️ {currency['currency_code']}: <code>{currency['available']}</code>"
|
||||
)
|
||||
|
||||
save_currencies = "\n".join(save_currencies)
|
||||
|
||||
return ded(f"""
|
||||
<b>🔷 Баланс CryptoBot кошелька составляет</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{save_currencies}
|
||||
""")
|
||||
|
||||
return "<b>🔷 Не удалось получить баланс CryptoBot кошелька ❌</b>"
|
||||
|
||||
# Создание счета на оплату
|
||||
async def bill(self, pay_amount: Union[float, int]) -> Tuple[Union[str, bool], str, str]:
|
||||
assets_currencies = ",".join(ALLOW_CURRENCIES)
|
||||
|
||||
payload = {
|
||||
'currency_type': 'fiat',
|
||||
'fiat': 'RUB',
|
||||
'amount': str(pay_amount),
|
||||
'expires_in': 10800,
|
||||
'accepted_assets': assets_currencies
|
||||
}
|
||||
|
||||
status, response = await self._request("createInvoice", payload)
|
||||
|
||||
if status and response['ok']:
|
||||
bill_message = ded(f"""
|
||||
<b>💰 Пополнение баланса</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
||||
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
|
||||
▪️ У вас имеется 3 часа на оплату счета
|
||||
▪️ Сумма пополнения: <code>{pay_amount}₽</code>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
❗️ После оплаты, нажмите на <code>Проверить оплату</code>
|
||||
""")
|
||||
|
||||
return bill_message, response['result']['mini_app_invoice_url'], response['result']['invoice_id']
|
||||
|
||||
return False, "", ""
|
||||
|
||||
# Проверка счета на оплату
|
||||
async def bill_check(self, bill_receipt: Optional[Union[str, int]] = None, records: int = 1) -> Tuple[int, float]:
|
||||
payload = {
|
||||
'invoice_ids': f'{bill_receipt}',
|
||||
'fiat': 'RUB',
|
||||
}
|
||||
|
||||
status, response = await self._request("getInvoices", payload)
|
||||
|
||||
pay_status, pay_amount = 1, 0
|
||||
|
||||
if status and response['ok']:
|
||||
get_invoice = response['result']['items'][0]
|
||||
|
||||
if get_invoice['status'] == "active":
|
||||
pay_status = 2
|
||||
elif get_invoice['status'] == "expired":
|
||||
pay_status = 3
|
||||
else:
|
||||
pay_status = 0
|
||||
pay_amount = to_number(get_invoice['amount'])
|
||||
|
||||
return pay_status, pay_amount
|
||||
|
||||
# Генерация запроса
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[bool, Any]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
base_url = 'https://pay.crypt.bot/api/'
|
||||
headers = {
|
||||
'Crypto-Pay-API-Token': self.token,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
url = base_url + method
|
||||
|
||||
try:
|
||||
response = await session.post(
|
||||
url=url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if response.status == 200:
|
||||
return True, response_data
|
||||
else:
|
||||
await self.error_notification(f"{response.status} - {str(response_data)}")
|
||||
|
||||
return False, response_data
|
||||
except ClientConnectorCertificateError:
|
||||
bot_logger.warning("Ошибка SSL при запросе CryptoBot", exc_info=True)
|
||||
await self.error_notification("CERTIFICATE_VERIFY_FAILED")
|
||||
|
||||
return False, "CERTIFICATE_VERIFY_FAILED"
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Ошибка запроса CryptoBot", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return False, str(ex)
|
||||
@@ -0,0 +1,239 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import asyncio
|
||||
import json
|
||||
from io import BytesIO
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
|
||||
import ujson
|
||||
from aiogram import Bot
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiohttp import ClientConnectorCertificateError, FormData
|
||||
|
||||
from tgbot.database import Settingsx
|
||||
from tgbot.utils.const_functions import gen_id, send_errors
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
from tgbot.utils.misc_functions import send_admins
|
||||
|
||||
|
||||
class DiscordAPI:
|
||||
# Настройка клиента Discord webhook
|
||||
def __init__(
|
||||
self,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
webhook_url: str = "None",
|
||||
adding: bool = False,
|
||||
skipping_error: bool = False,
|
||||
):
|
||||
self.bot = bot
|
||||
self.arSession = arSession
|
||||
self.update = update
|
||||
self.adding = adding
|
||||
self.skipping_error = skipping_error
|
||||
|
||||
self.webhook_username = "Djimbo Shop | Free Bot"
|
||||
self.base_url = "https://djimbo.dev/dsapi"
|
||||
|
||||
webhook_url = self._normalize_webhook_url(webhook_url)
|
||||
|
||||
if "/" in webhook_url:
|
||||
self.webhook_id = webhook_url.split("/")[0]
|
||||
self.webhook_token = webhook_url.split("/")[1]
|
||||
else:
|
||||
self.webhook_id = ""
|
||||
self.webhook_token = ""
|
||||
|
||||
# Нормализация Discord webhook URL
|
||||
@staticmethod
|
||||
def _normalize_webhook_url(webhook_url: str) -> str:
|
||||
if webhook_url is None:
|
||||
return "None"
|
||||
|
||||
if webhook_url.startswith("https://discord.com/api/"):
|
||||
webhook_url = webhook_url[33:]
|
||||
if webhook_url.startswith("discord.com/api/webhooks/"):
|
||||
webhook_url = webhook_url[25:]
|
||||
|
||||
return webhook_url
|
||||
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(
|
||||
cls,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
webhook_url: Optional[str] = None,
|
||||
skipping_error: bool = False,
|
||||
) -> "DiscordAPI":
|
||||
adding = webhook_url is not None
|
||||
|
||||
if webhook_url is None:
|
||||
settings = await Settingsx().get()
|
||||
webhook_url = settings.misc_discord_webhook_url
|
||||
adding = False
|
||||
|
||||
return cls(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=update,
|
||||
webhook_url=webhook_url,
|
||||
adding=adding,
|
||||
skipping_error=skipping_error,
|
||||
)
|
||||
|
||||
# Рассылка админам о нерабочем вебхуке
|
||||
async def error_account_admin(self, error_code: str = "Unknown"):
|
||||
bot_logger.warning("Discord webhook недоступен: %s", error_code)
|
||||
|
||||
if not self.skipping_error:
|
||||
await send_admins(
|
||||
self.bot,
|
||||
f"<b>🧿 Дискорд вебхук недоступен. Как можно быстрее его замените</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
)
|
||||
|
||||
# Проверка вебхука
|
||||
async def check(self) -> Tuple[bool, str]:
|
||||
request_url = f"{self.base_url}/webhooks/{self.webhook_id}/{self.webhook_token}"
|
||||
|
||||
status, response = await self._request(
|
||||
request_url=request_url,
|
||||
request_method="GET",
|
||||
)
|
||||
|
||||
if status and "channel_id" in response:
|
||||
discord_channel_id = response['channel_id']
|
||||
discord_hook_name = response['name']
|
||||
|
||||
return True, discord_hook_name
|
||||
|
||||
return False, ""
|
||||
|
||||
# Загрузка фотографий
|
||||
async def upload_photo(self, photo_data: Union[BytesIO, bytes], photo_name: Optional[str] = None) -> Tuple[
|
||||
bool, str]:
|
||||
request_url = f"{self.base_url}/webhooks/{self.webhook_id}/{self.webhook_token}"
|
||||
|
||||
if photo_name is None:
|
||||
photo_name = str(gen_id(24))
|
||||
|
||||
if not photo_name.endswith(".png") and not photo_name.endswith(".jpg"):
|
||||
photo_name = f"{photo_name}.png"
|
||||
|
||||
send_json = {
|
||||
'username': self.webhook_username,
|
||||
'content': '',
|
||||
}
|
||||
|
||||
data = FormData()
|
||||
data.add_field('file', photo_data, filename=photo_name)
|
||||
data.add_field('payload_json', ujson.dumps(send_json))
|
||||
|
||||
await asyncio.sleep(1)
|
||||
status, response = await self._request(
|
||||
request_url=request_url,
|
||||
request_method="POST",
|
||||
request_data=data,
|
||||
)
|
||||
|
||||
if "id" in response:
|
||||
channel_id = response['channel_id']
|
||||
message_id = response['id']
|
||||
|
||||
get_discord_forevercdn = await (
|
||||
DiscordDJ(
|
||||
arSession=self.arSession,
|
||||
bot=self.bot,
|
||||
)
|
||||
).export_forevercdn()
|
||||
|
||||
photo_url = f"{get_discord_forevercdn}/attachments/{channel_id}/{message_id}"
|
||||
|
||||
return True, photo_url
|
||||
|
||||
return False, "None"
|
||||
|
||||
# Запрос
|
||||
async def _request(
|
||||
self,
|
||||
request_url: str,
|
||||
request_method: str,
|
||||
request_data: Optional[Union[Dict[str, Any], FormData]] = None,
|
||||
) -> Tuple[bool, Any]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
try:
|
||||
response = await session.request(
|
||||
method=request_method,
|
||||
url=request_url,
|
||||
data=request_data,
|
||||
headers={"Accept-Encoding": "gzip, deflate"},
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if response.status == 200:
|
||||
return True, response_data
|
||||
else:
|
||||
await self.error_account_admin(f"{response.status} - {str(response_data)}")
|
||||
|
||||
return False, response_data
|
||||
except ClientConnectorCertificateError:
|
||||
bot_logger.warning("Ошибка SSL при запросе Discord webhook", exc_info=True)
|
||||
await self.error_account_admin("CERTIFICATE_VERIFY_FAILED")
|
||||
|
||||
return False, "CERTIFICATE_VERIFY_FAILED"
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Ошибка запроса Discord webhook", exc_info=True)
|
||||
await self.error_account_admin(str(ex))
|
||||
|
||||
return False, str(ex)
|
||||
|
||||
|
||||
# Извлечение ссылок для работы с локальным АПИ дискорда
|
||||
class DiscordDJ:
|
||||
# Настройка клиента сервисных ссылок Discord
|
||||
def __init__(self, arSession: ARS, bot: Bot):
|
||||
self.arSession = arSession
|
||||
self.bot = bot
|
||||
|
||||
self.const_url = "https://djimbo.dev/autoshop_discord.json"
|
||||
|
||||
# Экспорт публичного дискорд вебхука
|
||||
async def export_webhook(self) -> str:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
try:
|
||||
response = await session.get(
|
||||
self.const_url,
|
||||
headers={"Accept-Encoding": "gzip, deflate"},
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
except Exception as ex:
|
||||
await send_errors(self.bot, 7729051, f"Error getting Discord Webhook - {ex}")
|
||||
return "None"
|
||||
else:
|
||||
return response_data['webhook']
|
||||
|
||||
# Экспорт ссылки на постоянный CDN
|
||||
async def export_forevercdn(self) -> str:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
try:
|
||||
response = await session.get(
|
||||
self.const_url,
|
||||
headers={"Accept-Encoding": "gzip, deflate"},
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
except Exception as ex:
|
||||
await send_errors(self.bot, 7729051, f"Error getting Discord ForeverCDN - {ex}")
|
||||
return "None"
|
||||
else:
|
||||
return response_data['forevercdn']
|
||||
@@ -0,0 +1,157 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
from aiogram import Bot
|
||||
|
||||
from tgbot.database import Settingsx
|
||||
from tgbot.services.api_telegraph import TelegraphAPI
|
||||
from tgbot.utils.const_functions import send_errors
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
|
||||
|
||||
# Model Hosting Text
|
||||
@dataclass
|
||||
class ModelHostingText:
|
||||
telegraph: str = "telegraph"
|
||||
pastie: str = "pastie"
|
||||
friendpaste: str = "friendpaste"
|
||||
snippet: str = "snippet"
|
||||
|
||||
|
||||
# API для работы с текстовыми хостингами
|
||||
class HostingAPI:
|
||||
# Настройка клиента текстового хостинга
|
||||
def __init__(
|
||||
self,
|
||||
arSession: ARS,
|
||||
bot: Bot,
|
||||
text_hosting: str,
|
||||
):
|
||||
self.arSession = arSession
|
||||
self.bot = bot
|
||||
self.text_hosting = text_hosting
|
||||
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(cls, arSession: ARS, bot: Bot):
|
||||
settings = await Settingsx().get()
|
||||
|
||||
return cls(
|
||||
arSession=arSession,
|
||||
bot=bot,
|
||||
text_hosting=settings.misc_hosting_text, # telegraph, pastie, friendpaste, snippet
|
||||
)
|
||||
|
||||
# Загрузка текста на хостинг
|
||||
async def upload_text(self, text: str) -> str:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
max_attempts = 10
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
status_success = True
|
||||
return_link = "None"
|
||||
|
||||
############################################################
|
||||
######################## TELEGRAPH #########################
|
||||
if self.text_hosting == ModelHostingText.telegraph:
|
||||
telegraph_api = await TelegraphAPI.connect(arSession=self.arSession)
|
||||
status, response = await telegraph_api.upload_text(text)
|
||||
|
||||
if status:
|
||||
return_link = response
|
||||
else:
|
||||
await send_errors(self.bot, 4044321, str(response))
|
||||
status_success = False
|
||||
|
||||
############################################################
|
||||
########################## PASTIE ##########################
|
||||
elif self.text_hosting == ModelHostingText.pastie:
|
||||
try:
|
||||
response = await session.request(
|
||||
method="post",
|
||||
url="http://pastie.org/pastes/create/",
|
||||
data={
|
||||
'language': 'plaintext',
|
||||
'content': text,
|
||||
},
|
||||
)
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Ошибка загрузки текста в Pastie", exc_info=True)
|
||||
await send_errors(self.bot, 4719012, str(ex))
|
||||
status_success = False
|
||||
else:
|
||||
cache_link = response.url
|
||||
|
||||
if "create" in str(cache_link) or str(cache_link) == "http://pastie.org/":
|
||||
status_success = False
|
||||
else:
|
||||
return_link = cache_link
|
||||
|
||||
############################################################
|
||||
######################## FRIENDPASTE #######################
|
||||
elif self.text_hosting == ModelHostingText.friendpaste:
|
||||
try:
|
||||
response = await session.request(
|
||||
method="post",
|
||||
url="https://www.friendpaste.com/",
|
||||
json={
|
||||
'language': 'text',
|
||||
'title': '',
|
||||
'snippet': text,
|
||||
},
|
||||
)
|
||||
cache_link = json.loads((await response.read()).decode())['url']
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Ошибка загрузки текста в FriendPaste", exc_info=True)
|
||||
await send_errors(self.bot, 8434711, str(ex))
|
||||
status_success = False
|
||||
else:
|
||||
return_link = cache_link
|
||||
|
||||
############################################################
|
||||
######################### SNIPPET ##########################
|
||||
elif self.text_hosting == ModelHostingText.snippet:
|
||||
try:
|
||||
response = await session.request(
|
||||
method="post",
|
||||
url="https://snippet.host/",
|
||||
data={
|
||||
'title': '',
|
||||
'content': text,
|
||||
'visibility': '1',
|
||||
'expires': 'never',
|
||||
'language': 'plain text'
|
||||
},
|
||||
)
|
||||
cache_link = response.url
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Ошибка загрузки текста в Snippet", exc_info=True)
|
||||
await send_errors(self.bot, 8900012, str(ex))
|
||||
status_success = False
|
||||
else:
|
||||
return_link = cache_link
|
||||
|
||||
if status_success:
|
||||
return str(return_link)
|
||||
|
||||
# pastie -> friendpaste
|
||||
if self.text_hosting == ModelHostingText.pastie:
|
||||
self.text_hosting = ModelHostingText.friendpaste
|
||||
# friendpaste -> snippet
|
||||
elif self.text_hosting == ModelHostingText.friendpaste:
|
||||
self.text_hosting = ModelHostingText.snippet
|
||||
# snippet -> telegraph
|
||||
elif self.text_hosting == ModelHostingText.snippet:
|
||||
self.text_hosting = ModelHostingText.telegraph
|
||||
# telegraph -> pastie
|
||||
elif self.text_hosting == ModelHostingText.telegraph:
|
||||
self.text_hosting = ModelHostingText.pastie
|
||||
|
||||
if attempt < max_attempts - 1:
|
||||
await asyncio.sleep(3)
|
||||
|
||||
return "None"
|
||||
@@ -0,0 +1,31 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import ssl
|
||||
from typing import Optional
|
||||
|
||||
import aiohttp
|
||||
import certifi
|
||||
|
||||
|
||||
# Асинхронная сессия для запросов
|
||||
class AsyncRequestSession:
|
||||
# Создание ленивой aiohttp-сессии
|
||||
def __init__(self) -> None:
|
||||
self._session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
# Получение объекта сессии
|
||||
async def get_session(self) -> aiohttp.ClientSession:
|
||||
if self._session is None:
|
||||
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||
timeout = aiohttp.ClientTimeout(total=15, connect=5, sock_read=10)
|
||||
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
||||
|
||||
self._session = aiohttp.ClientSession(timeout=timeout, connector=connector)
|
||||
|
||||
return self._session
|
||||
|
||||
# Закрытие сессии
|
||||
async def close(self) -> None:
|
||||
if self._session is None:
|
||||
return None
|
||||
|
||||
await self._session.close()
|
||||
@@ -0,0 +1,297 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import math
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import CallbackQuery, LabeledPrice, Message, PreCheckoutQuery, SuccessfulPayment
|
||||
|
||||
from tgbot.database import Paymentsx
|
||||
from tgbot.utils.const_functions import ded, gen_id, to_number
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
from tgbot.utils.misc_functions import send_admins
|
||||
|
||||
STARS_CURRENCY = "XTR"
|
||||
STARS_PAYLOAD_PREFIX = "stars_refill"
|
||||
|
||||
|
||||
# АПИ для работы с Telegram Stars
|
||||
class StarsAPI:
|
||||
# Настройка клиента Telegram Stars
|
||||
def __init__(
|
||||
self,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery, PreCheckoutQuery]] = None,
|
||||
stars_course: float = 1.5,
|
||||
skipping_error: bool = False,
|
||||
):
|
||||
self.bot = bot
|
||||
self.arSession = arSession
|
||||
self.update = update
|
||||
self.stars_course = float(stars_course or 1.5)
|
||||
self.skipping_error = skipping_error
|
||||
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(
|
||||
cls,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery, PreCheckoutQuery]] = None,
|
||||
skipping_error: bool = False,
|
||||
) -> "StarsAPI":
|
||||
payments = await Paymentsx().get()
|
||||
|
||||
return cls(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=update,
|
||||
stars_course=payments.stars_course,
|
||||
skipping_error=skipping_error,
|
||||
)
|
||||
|
||||
# Уведомление админов о проблеме со Stars
|
||||
async def error_notification(self, error_code: str = "Unknown") -> None:
|
||||
bot_logger.warning("Telegram Stars недоступны: %s", error_code)
|
||||
|
||||
if self.skipping_error:
|
||||
return
|
||||
|
||||
await send_admins(
|
||||
self.bot,
|
||||
f"<b>⭐️ Telegram Stars недоступны</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
)
|
||||
|
||||
# Проверка доступности Stars для бота
|
||||
async def check(self) -> Tuple[bool, str]:
|
||||
try:
|
||||
bot_info = await self.bot.get_me()
|
||||
balance = await self.bot.get_my_star_balance()
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Не удалось проверить Telegram Stars", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return False, "<b>⭐️ Не удалось проверить Telegram Stars ❌</b>"
|
||||
|
||||
rub_balance = self.stars_to_rub(balance.amount)
|
||||
|
||||
return True, ded(f"""
|
||||
<b>⭐️ Telegram Stars полностью функционируют ✅</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Бот: <code>@{bot_info.username}</code>
|
||||
▪️ Баланс: <code>{balance.amount} ⭐️</code> ≈ <code>{rub_balance}₽</code>
|
||||
▪️ Курс: <code>1 ⭐️ = {self.stars_course}₽</code>
|
||||
""")
|
||||
|
||||
# Получение баланса Stars у бота
|
||||
async def balance(self) -> str:
|
||||
try:
|
||||
balance = await self.bot.get_my_star_balance()
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Не удалось получить баланс Telegram Stars", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return "<b>⭐️ Не удалось получить баланс Telegram Stars ❌</b>"
|
||||
|
||||
rub_balance = self.stars_to_rub(balance.amount)
|
||||
nanostars = balance.nanostar_amount or 0
|
||||
|
||||
return ded(f"""
|
||||
<b>⭐️ Баланс Telegram Stars</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Звёзды: <code>{balance.amount} ⭐️</code>
|
||||
▪️ Нанозвёзды: <code>{nanostars}</code>
|
||||
▪️ В рублях по курсу: <code>{rub_balance}₽</code>
|
||||
▪️ Курс: <code>1 ⭐️ = {self.stars_course}₽</code>
|
||||
""")
|
||||
|
||||
# Создание ссылки на счёт Telegram Stars
|
||||
async def bill(self, pay_amount: Union[float, int]) -> Tuple[Union[str, bool], str, int]:
|
||||
rub_amount = round(float(pay_amount), 2)
|
||||
stars_amount = self.rub_to_stars(rub_amount)
|
||||
bill_chat_id, bill_message_id = self.get_bill_message()
|
||||
receipt = gen_id(12)
|
||||
|
||||
payload = self.make_payload(
|
||||
receipt,
|
||||
rub_amount,
|
||||
stars_amount,
|
||||
bill_chat_id,
|
||||
bill_message_id,
|
||||
)
|
||||
|
||||
try:
|
||||
bill_link = await self.bot.create_invoice_link(
|
||||
title="Пополнение баланса",
|
||||
description=f"Пополнение баланса на {rub_amount}₽ через Telegram Stars",
|
||||
payload=payload,
|
||||
provider_token="",
|
||||
currency=STARS_CURRENCY,
|
||||
prices=[
|
||||
LabeledPrice(
|
||||
label=f"Пополнение на {rub_amount}₽",
|
||||
amount=stars_amount,
|
||||
)
|
||||
],
|
||||
)
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Не удалось создать счёт Telegram Stars", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return False, "", 0
|
||||
|
||||
bill_message = ded(f"""
|
||||
<b>💰 Пополнение баланса через Telegram Stars</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Сумма пополнения: <code>{rub_amount}₽</code>
|
||||
▪️ К оплате: <code>{stars_amount} ⭐️</code>
|
||||
▪️ Курс: <code>1 ⭐️ = {self.stars_course}₽</code>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
❗️ После оплаты средства автоматически зачислятся на ваш баланс
|
||||
Если сообщение не обновилось, нажмите <code>Проверить оплату</code>
|
||||
""")
|
||||
|
||||
return bill_message, bill_link, receipt
|
||||
|
||||
# Проверка оплаты по истории Stars
|
||||
async def bill_check(self, bill_receipt: Optional[Union[str, int]] = None, records: int = 100) -> Tuple[int, float]:
|
||||
if bill_receipt is None:
|
||||
return 1, 0
|
||||
|
||||
try:
|
||||
transactions = await self.bot.get_star_transactions(limit=records)
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Не удалось проверить оплату Telegram Stars", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return 1, 0
|
||||
|
||||
receipt = str(bill_receipt)
|
||||
|
||||
for transaction in transactions.transactions:
|
||||
source = transaction.source
|
||||
payload = getattr(source, "invoice_payload", None)
|
||||
|
||||
if not payload:
|
||||
continue
|
||||
|
||||
payload_data = self.parse_payload(payload)
|
||||
|
||||
if payload_data is None or str(payload_data[0]) != receipt:
|
||||
continue
|
||||
|
||||
_receipt, rub_amount, stars_amount, _bill_chat_id, _bill_message_id = payload_data
|
||||
|
||||
if transaction.amount != stars_amount:
|
||||
bot_logger.warning(
|
||||
"Сумма Telegram Stars не совпала с payload: receipt=%s expected=%s actual=%s",
|
||||
receipt,
|
||||
stars_amount,
|
||||
transaction.amount,
|
||||
)
|
||||
return 1, 0
|
||||
|
||||
return 0, rub_amount
|
||||
|
||||
return 2, 0
|
||||
|
||||
# Подтверждение автоматической оплаты
|
||||
async def answer_pre_checkout(self, query: PreCheckoutQuery) -> None:
|
||||
payload_data = self.parse_payload(query.invoice_payload)
|
||||
|
||||
if query.currency != STARS_CURRENCY or payload_data is None or query.total_amount != payload_data[2]:
|
||||
await self.bot.answer_pre_checkout_query(
|
||||
pre_checkout_query_id=query.id,
|
||||
ok=False,
|
||||
error_message="Некорректный счёт Telegram Stars",
|
||||
)
|
||||
return
|
||||
|
||||
await self.bot.answer_pre_checkout_query(
|
||||
pre_checkout_query_id=query.id,
|
||||
ok=True,
|
||||
)
|
||||
|
||||
# Получение данных успешной оплаты
|
||||
def parse_successful_payment(
|
||||
self,
|
||||
payment: SuccessfulPayment,
|
||||
) -> Tuple[int, float, str, Optional[int], Optional[int]]:
|
||||
payload_data = self.parse_payload(payment.invoice_payload)
|
||||
|
||||
if payment.currency != STARS_CURRENCY or payload_data is None or payment.total_amount != payload_data[2]:
|
||||
raise ValueError("Некорректная успешная оплата Telegram Stars")
|
||||
|
||||
receipt, rub_amount, _stars_amount, bill_chat_id, bill_message_id = payload_data
|
||||
|
||||
return receipt, rub_amount, payment.telegram_payment_charge_id, bill_chat_id, bill_message_id
|
||||
|
||||
# Возврат оплаты пользователю
|
||||
async def refund(self, user_id: int, telegram_payment_charge_id: str) -> bool:
|
||||
try:
|
||||
return await self.bot.refund_star_payment(
|
||||
user_id=user_id,
|
||||
telegram_payment_charge_id=telegram_payment_charge_id,
|
||||
)
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Не удалось вернуть оплату Telegram Stars", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return False
|
||||
|
||||
# Конвертация рублей в звёзды
|
||||
def rub_to_stars(self, rub_amount: Union[float, int]) -> int:
|
||||
if self.stars_course <= 0:
|
||||
raise ValueError("Курс Telegram Stars должен быть больше нуля")
|
||||
|
||||
return max(1, math.ceil(float(rub_amount) / self.stars_course))
|
||||
|
||||
# Конвертация звёзд в рубли
|
||||
def stars_to_rub(self, stars_amount: Union[float, int]) -> float:
|
||||
return round(float(stars_amount) * self.stars_course, 2)
|
||||
|
||||
# Получение данных сообщения со счётом
|
||||
def get_bill_message(self) -> Tuple[Optional[int], Optional[int]]:
|
||||
if isinstance(self.update, Message):
|
||||
return self.update.chat.id, self.update.message_id
|
||||
|
||||
return None, None
|
||||
|
||||
# Сбор payload для телеграм инвойса
|
||||
@staticmethod
|
||||
def make_payload(
|
||||
receipt: int,
|
||||
rub_amount: float,
|
||||
stars_amount: int,
|
||||
bill_chat_id: Optional[int] = None,
|
||||
bill_message_id: Optional[int] = None,
|
||||
) -> str:
|
||||
payload = f"{STARS_PAYLOAD_PREFIX}:{receipt}:{rub_amount}:{stars_amount}"
|
||||
|
||||
if bill_chat_id is not None and bill_message_id is not None:
|
||||
payload += f":{bill_chat_id}:{bill_message_id}"
|
||||
|
||||
return payload
|
||||
|
||||
# Разбор payload телеграм инвойса
|
||||
@staticmethod
|
||||
def parse_payload(
|
||||
payload: str,
|
||||
) -> Optional[Tuple[int, float, int, Optional[int], Optional[int]]]:
|
||||
payload_parts = str(payload).split(":")
|
||||
|
||||
if len(payload_parts) not in (4, 6) or payload_parts[0] != STARS_PAYLOAD_PREFIX:
|
||||
return None
|
||||
|
||||
try:
|
||||
receipt = int(payload_parts[1])
|
||||
rub_amount = float(to_number(payload_parts[2]))
|
||||
stars_amount = int(payload_parts[3])
|
||||
bill_chat_id = int(payload_parts[4]) if len(payload_parts) == 6 else None
|
||||
bill_message_id = int(payload_parts[5]) if len(payload_parts) == 6 else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
return receipt, rub_amount, stars_amount, bill_chat_id, bill_message_id
|
||||
@@ -0,0 +1,87 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import json
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from tgbot.database import Settingsx
|
||||
from tgbot.utils.const_functions import gen_id
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
|
||||
|
||||
# API для работы с телеграфом
|
||||
class TelegraphAPI:
|
||||
# Настройка клиента Telegraph
|
||||
def __init__(self, arSession: ARS, token: Optional[str] = None):
|
||||
self.arSession = arSession
|
||||
self.token = token
|
||||
|
||||
self.domain = "https://api.telegra.ph"
|
||||
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(cls, arSession: ARS, token: Optional[str] = None):
|
||||
if token is None:
|
||||
settings = await Settingsx().get()
|
||||
token = settings.misc_token_telegraph
|
||||
|
||||
return cls(arSession=arSession, token=token)
|
||||
|
||||
# Загрузка текста в телеграф
|
||||
async def upload_text(self, text: str) -> Tuple[bool, str]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
if self.token == "None" or self.token is None:
|
||||
status, response = await self.generate_token()
|
||||
|
||||
if status:
|
||||
self.token = response
|
||||
else:
|
||||
return False, "Error generate token"
|
||||
|
||||
try:
|
||||
response = await session.request(
|
||||
method="POST",
|
||||
url=f"{self.domain}/createPage",
|
||||
data={
|
||||
'access_token': self.token,
|
||||
'title': f'shop{gen_id(18)}',
|
||||
'content': json.dumps([{'tag': 'p', 'children': [text]}]),
|
||||
}
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось загрузить текст в Telegraph", exc_info=True)
|
||||
return False, "None"
|
||||
else:
|
||||
if "ok" in response_data and response_data['ok']:
|
||||
return True, response_data['result']['url'].replace("\\\\", "")
|
||||
else:
|
||||
return False, response_data
|
||||
|
||||
# Генерация нового Telegraph-токена
|
||||
async def generate_token(self) -> Tuple[bool, str]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
try:
|
||||
response = await session.request(
|
||||
method="POST",
|
||||
url=f"{self.domain}/createAccount",
|
||||
data={
|
||||
'short_name': 'dj shop',
|
||||
}
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось создать Telegraph token", exc_info=True)
|
||||
return False, "None"
|
||||
else:
|
||||
if "ok" in response_data and response_data['ok']:
|
||||
await Settingsx().update(
|
||||
misc_token_telegraph=response_data['result']['access_token']
|
||||
)
|
||||
|
||||
return True, response_data['result']['access_token']
|
||||
else:
|
||||
return False, response_data
|
||||
@@ -0,0 +1,339 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import json
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiohttp import ClientConnectorCertificateError
|
||||
|
||||
from tgbot.data.config import YOOMONEY_CLIENT_ID
|
||||
from tgbot.database import Paymentsx
|
||||
from tgbot.utils.const_functions import ded, gen_id
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
from tgbot.utils.misc_functions import send_admins
|
||||
|
||||
|
||||
# АПИ для работы с ЮMoney
|
||||
class YoomoneyAPI:
|
||||
# Настройка клиента ЮMoney
|
||||
def __init__(
|
||||
self,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: str = "None",
|
||||
adding: bool = False,
|
||||
skipping_error: bool = False,
|
||||
):
|
||||
self.token = token
|
||||
self.adding = adding
|
||||
|
||||
self.base_url = 'https://yoomoney.ru/api/'
|
||||
self.headers = {
|
||||
'Authorization': f'Bearer {self.token}',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
self.bot = bot
|
||||
self.arSession = arSession
|
||||
self.update = update
|
||||
self.skipping_error = skipping_error
|
||||
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(
|
||||
cls,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: Optional[str] = None,
|
||||
skipping_error: bool = False,
|
||||
) -> "YoomoneyAPI":
|
||||
adding = token is not None
|
||||
|
||||
if token is None:
|
||||
payments = await Paymentsx().get()
|
||||
token = payments.yoomoney_token
|
||||
adding = False
|
||||
|
||||
return cls(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=update,
|
||||
token=token,
|
||||
adding=adding,
|
||||
skipping_error=skipping_error,
|
||||
)
|
||||
|
||||
# Уведомления о неработоспособности кассы/кошелька
|
||||
async def error_notification(self, error_code: str = "Unknown"):
|
||||
bot_logger.warning("ЮMoney недоступен: %s", error_code)
|
||||
|
||||
if not self.skipping_error:
|
||||
if self.adding and self.update is not None:
|
||||
await self.update.edit_text(
|
||||
f"<b>🔮 Не удалось добавить ЮMoney кассу ❌</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
)
|
||||
else:
|
||||
await send_admins(
|
||||
self.bot,
|
||||
f"<b>🔮 ЮMoney недоступен. Как можно быстрее его замените</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
)
|
||||
|
||||
# Проверка кассы/кошелька
|
||||
async def check(self) -> str:
|
||||
status, response = await self._request("account-info")
|
||||
|
||||
if status:
|
||||
if len(response) >= 1:
|
||||
if response['identified']:
|
||||
text_identified = "Присутствует"
|
||||
else:
|
||||
text_identified = "Отсутствует"
|
||||
|
||||
if response['account_status'] == "identified":
|
||||
text_status = "Идентифицированный счет"
|
||||
elif response['account_status'] == "anonymous":
|
||||
text_status = "Анонимный счет"
|
||||
elif response['account_status'] == "named":
|
||||
text_status = "Именной счет"
|
||||
else:
|
||||
text_status = response['account_status']
|
||||
|
||||
if response['account_type'] == "personal":
|
||||
text_type = "Пользовательский счет"
|
||||
elif response['account_type'] == "professional":
|
||||
text_type = "Профессиональный счет"
|
||||
else:
|
||||
text_type = response['account_type']
|
||||
|
||||
return ded(f"""
|
||||
<b>🔮 ЮMoney кошелёк полностью функционирует ✅</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Кошелёк: <code>{response['account']}</code>
|
||||
▪️ Идентификация: <code>{text_identified}</code>
|
||||
▪️ Статус аккаунта: <code>{text_status}</code>
|
||||
▪️ Тип счета: <code>{text_type}</code>
|
||||
""")
|
||||
|
||||
return "<b>🔮 Не удалось проверить ЮMoney кошелёк ❌</b>"
|
||||
|
||||
# Получение баланса
|
||||
async def balance(self) -> str:
|
||||
status, response = await self._request("account-info")
|
||||
|
||||
if status:
|
||||
wallet_balance = response['balance']
|
||||
|
||||
wallet_status, wallet_number = await self.account_info()
|
||||
|
||||
if wallet_status:
|
||||
return ded(f"""
|
||||
<b>🔮 Баланс ЮMoney кошелька составляет</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Кошелёк: <code>{wallet_number}</code>
|
||||
▪️ Баланс: <code>{wallet_balance}₽</code>
|
||||
""")
|
||||
|
||||
return "<b>🔮 Не удалось получить баланс ЮMoney кошелька ❌</b>"
|
||||
|
||||
# Информация об аккаунте
|
||||
async def account_info(self) -> Tuple[bool, str]:
|
||||
status, response = await self._request("account-info")
|
||||
|
||||
try:
|
||||
return True, response['account']
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось получить номер ЮMoney", exc_info=True)
|
||||
return False, ""
|
||||
|
||||
# Получение ссылки на авторизацию
|
||||
async def authorization_get(self) -> str:
|
||||
if not YOOMONEY_CLIENT_ID:
|
||||
return "В .env не заполнен YOOMONEY_CLIENT_ID"
|
||||
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
|
||||
url = "https://yoomoney.ru/oauth/authorize?" + urlencode({
|
||||
"client_id": YOOMONEY_CLIENT_ID,
|
||||
"response_type": "code",
|
||||
"redirect_uri": "https://yoomoney.ru",
|
||||
"scope": "account-info operation-history operation-details",
|
||||
})
|
||||
|
||||
response = await session.post(
|
||||
url=url,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
return str(response.url)
|
||||
|
||||
# Принятие кода авторизации и получение токена
|
||||
async def authorization_enter(self, get_code: str) -> Tuple[bool, str, str]:
|
||||
if not YOOMONEY_CLIENT_ID:
|
||||
return False, "", "<b>❌ В .env не заполнен YOOMONEY_CLIENT_ID</b>"
|
||||
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
|
||||
url = "https://yoomoney.ru/oauth/token?" + urlencode({
|
||||
"code": get_code,
|
||||
"client_id": YOOMONEY_CLIENT_ID,
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://yoomoney.ru",
|
||||
})
|
||||
|
||||
response = await session.post(
|
||||
url=url,
|
||||
headers=headers,
|
||||
)
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if "error" in response_data:
|
||||
error = response_data['error']
|
||||
|
||||
if error == "invalid_request":
|
||||
return_message = ded(f"""
|
||||
<b>❌ Требуемые параметры запроса отсутствуют или имеют неправильные или недопустимые значения</b>
|
||||
""")
|
||||
elif error == "unauthorized_client":
|
||||
return_message = ded(f"""
|
||||
<b>❌ Недопустимое значение параметра 'client_id' или 'client_secret', или приложение
|
||||
не имеет права запрашивать авторизацию (например, ЮMoney заблокировал его 'client_id')</b>
|
||||
""")
|
||||
elif error == "invalid_grant":
|
||||
return_message = ded(f"""
|
||||
<b>❌ В выпуске 'access_token' отказано. ЮMoney не выпускал временный токен,
|
||||
срок действия токена истек или этот временный токен уже выдан
|
||||
'access_token' (повторный запрос токена авторизации с тем же временным токеном)</b>
|
||||
""")
|
||||
else:
|
||||
return_message = f"Unknown error: {error}"
|
||||
|
||||
return False, "", return_message
|
||||
elif response_data['access_token'] == "":
|
||||
return False, "", "<b>❌ Не удалось получить токен. Попробуйте всё снова</b>"
|
||||
|
||||
return True, response_data['access_token'], "<b>🔮 ЮMoney кошелёк был успешно изменён ✅</b>"
|
||||
|
||||
# Создание счета на оплату
|
||||
async def bill(self, pay_amount: Union[float, int]) -> Tuple[Union[str, bool], str, str]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
bill_receipt = str(gen_id(12))
|
||||
url = "https://yoomoney.ru/quickpay/confirm.xml?"
|
||||
|
||||
wallet_status, wallet_number = await self.account_info()
|
||||
|
||||
if wallet_status:
|
||||
pay_amount_bill = pay_amount + (pay_amount * 0.031)
|
||||
|
||||
if float(pay_amount_bill) < 2:
|
||||
pay_amount_bill = 2.04
|
||||
|
||||
payload = {
|
||||
'receiver': wallet_number,
|
||||
'quickpay_form': 'button',
|
||||
'targets': 'Добровольное пожертвование',
|
||||
'paymentType': 'SB',
|
||||
'sum': pay_amount_bill,
|
||||
'label': bill_receipt,
|
||||
}
|
||||
|
||||
for value in payload:
|
||||
url += str(value).replace("_", "-") + "=" + str(payload[value])
|
||||
url += "&"
|
||||
|
||||
bill_link = str((await session.post(url[:-1].replace(" ", "%20"))).url)
|
||||
|
||||
bill_message = ded(f"""
|
||||
<b>💰 Пополнение баланса</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
||||
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
|
||||
▪️ У вас имеется 60 минут на оплату счета
|
||||
▪️ Сумма пополнения: <code>{pay_amount}₽</code>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
❗️ После оплаты, нажмите на <code>Проверить оплату</code>
|
||||
""")
|
||||
|
||||
return bill_message, bill_link, bill_receipt
|
||||
|
||||
return False, "", ""
|
||||
|
||||
# Проверка счета на оплату
|
||||
async def bill_check(self, bill_receipt: Optional[Union[str, int]] = None, records: int = 1) -> Tuple[int, float]:
|
||||
data = {
|
||||
'type': 'deposition',
|
||||
'details': 'true',
|
||||
}
|
||||
|
||||
if bill_receipt is not None:
|
||||
data['label'] = bill_receipt
|
||||
if records is not None:
|
||||
data['records'] = str(records)
|
||||
|
||||
status, response = await self._request("operation-history", data)
|
||||
|
||||
pay_status, pay_amount, pay_currency = 1, 0, 0
|
||||
|
||||
if status:
|
||||
pay_status = 2
|
||||
|
||||
if len(response['operations']) >= 1:
|
||||
pay_currency = response['operations'][0]['amount_currency']
|
||||
pay_amount = response['operations'][0]['amount']
|
||||
|
||||
pay_status = 3
|
||||
|
||||
if pay_currency == "RUB":
|
||||
pay_status = 0
|
||||
|
||||
return pay_status, pay_amount
|
||||
|
||||
# Генерация запроса
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[bool, Any]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
url = self.base_url + method
|
||||
|
||||
try:
|
||||
response = await session.post(
|
||||
url=url,
|
||||
headers=self.headers,
|
||||
data=data,
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if response.status == 200:
|
||||
return True, response_data
|
||||
else:
|
||||
await self.error_notification(f"{response.status} - {str(response_data)}")
|
||||
|
||||
return False, response_data
|
||||
except ClientConnectorCertificateError:
|
||||
bot_logger.warning("Ошибка SSL при запросе ЮMoney", exc_info=True)
|
||||
await self.error_notification("CERTIFICATE_VERIFY_FAILED")
|
||||
|
||||
return False, "CERTIFICATE_VERIFY_FAILED"
|
||||
except Exception as ex:
|
||||
bot_logger.warning("Ошибка запроса ЮMoney", exc_info=True)
|
||||
await self.error_notification(str(ex))
|
||||
|
||||
return False, str(ex)
|
||||
Reference in New Issue
Block a user