Use qrcode to add cookies only CN users

This commit is contained in:
omg-xtao 2022-12-23 21:06:08 +08:00 committed by GitHub
parent 343c893a5c
commit c927b7b86b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 196 deletions

View File

@ -1,5 +1,12 @@
import json
import random
from asyncio import sleep
from io import BytesIO
from string import ascii_letters, digits
from typing import Dict
import qrcode
from genshin.utility import generate_cn_dynamic_secret
from httpx import AsyncClient
from ...utility.helpers import get_device_id
@ -8,69 +15,25 @@ __all__ = ("SignIn",)
class SignIn:
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
S_TOKEN_URL = (
"https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={0}&token_types=3&uid={1}"
)
BBS_URL = "https://api-takumi.mihoyo.com/account/auth/api/webLoginByMobile"
USER_AGENT = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15"
)
HEADERS = {
"Host": "webapi.account.mihoyo.com",
"Connection": "keep-alive",
"sec-ch-ua": '".Not/A)Brand";v="99", "Microsoft Edge";v="103", "Chromium";v="103"',
"DNT": "1",
"x-rpc-device_model": "OS X 10.15.7",
"sec-ch-ua-mobile": "?0",
"User-Agent": USER_AGENT,
"x-rpc-device_id": get_device_id(USER_AGENT),
"Accept": "application/json, text/plain, */*",
"x-rpc-device_name": "Microsoft Edge 103.0.1264.62",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"x-rpc-client_type": "4",
"sec-ch-ua-platform": '"macOS"',
"Origin": "https://user.mihoyo.com",
"Sec-Fetch-Site": "same-site",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://user.mihoyo.com/",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
}
BBS_HEADERS = {
"Host": "api-takumi.mihoyo.com",
"Content-Type": "application/json;charset=utf-8",
"Origin": "https://bbs.mihoyo.com",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Accept": "application/json, text/plain, */*",
"User-Agent": USER_AGENT,
"Referer": "https://bbs.mihoyo.com/",
"Accept-Language": "zh-CN,zh-Hans;q=0.9",
}
AUTHKEY_API = "https://api-takumi.mihoyo.com/binding/api/genAuthKey"
USER_INFO_API = "https://bbs-api.mihoyo.com/user/wapi/getUserFullInfo"
GACHA_HEADERS = {
"User-Agent": "okhttp/4.8.0",
"x-rpc-app_version": "2.28.1",
"x-rpc-sys_version": "12",
"x-rpc-client_type": "5",
"x-rpc-channel": "mihoyo",
"x-rpc-device_id": get_device_id(USER_AGENT),
"x-rpc-device_name": "Mi 10",
"x-rpc-device_model": "Mi 10",
"Referer": "https://app.mihoyo.com",
"Host": "api-takumi.mihoyo.com",
}
QRCODE_GEN_API = "https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch"
QRCODE_GET_API = "https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query"
GAME_TOKEN_API = "https://api-takumi.mihoyo.com/auth/api/getCookieAccountInfoByGameToken"
GAME_LTOKEN_API = "https://passport-api.mihoyo.com/account/ma-cn-session/app/getTokenByGameToken"
def __init__(self, phone: int = 0, uid: int = 0, cookie: Dict = None):
self.phone = phone
def __init__(self, uid: int = 0, cookie: Dict = None):
self.client = AsyncClient()
self.uid = uid
self.cookie = cookie if cookie is not None else {}
self.parse_uid()
self.ticket = None
self.device_id = None
def parse_uid(self):
"""
@ -87,34 +50,6 @@ class SignIn:
for item in ["login_uid", "stuid", "ltuid", "account_id"]:
self.cookie[item] = self.uid
@staticmethod
def check_error(data: dict) -> bool:
"""
检查是否有错误
:param data:
:return:
"""
res_data = data.get("data", {})
return res_data.get("msg") == "验证码错误" or res_data.get("info") == "Captcha not match Err"
async def login(self, captcha: int) -> bool:
data = await self.client.post(
self.LOGIN_URL,
data={"mobile": str(self.phone), "mobile_captcha": str(captcha), "source": "user.mihoyo.com"},
headers=self.HEADERS,
)
res_json = data.json()
if self.check_error(res_json):
return False
for k, v in data.cookies.items():
self.cookie[k] = v
if "login_ticket" not in self.cookie:
return False
self.parse_uid()
return bool(self.uid)
async def get_s_token(self):
if not self.cookie.get("login_ticket") or not self.uid:
return
@ -127,23 +62,85 @@ class SignIn:
if i.get("name") and i.get("token"):
self.cookie[i.get("name")] = i.get("token")
async def get_token(self, captcha: int) -> bool:
data = await self.client.post(
self.BBS_URL,
headers=self.BBS_HEADERS,
json={
"is_bh2": False,
"mobile": str(self.phone),
"captcha": str(captcha),
"action_type": "login",
"token_type": 6,
},
async def get_ltoken_by_game_token(self, game_token: str):
data = {"account_id": self.uid, "game_token": game_token}
headers = {
"x-rpc-app_version": "2.41.2",
"DS": generate_cn_dynamic_secret(body=data, salt="osgT0DljLarYxgebPPHJFjdaxPfoiHGt"),
"x-rpc-aigis": "",
"Content-Type": "application/json",
"Accept": "application/json",
"x-rpc-game_biz": "bbs_cn",
"x-rpc-sys_version": "11",
"x-rpc-device_id": get_device_id(self.USER_AGENT),
"x-rpc-device_fp": "".join(random.choices((ascii_letters + digits), k=13)),
"x-rpc-device_name": "Chrome 108.0.0.0",
"x-rpc-device_model": "Windows 10 64-bit",
"x-rpc-app_id": "bll8iq97cem8",
"x-rpc-client_type": "4",
"User-Agent": "okhttp/4.8.0",
}
res = await self.client.post(
self.GAME_LTOKEN_API,
headers=headers,
json={"account_id": self.uid, "game_token": game_token},
)
res_json = data.json()
if self.check_error(res_json):
return res.json()
async def create_login_data(self) -> str:
self.device_id = get_device_id("".join(random.choices((ascii_letters + digits), k=64)))
data = {"app_id": "4", "device": self.device_id}
res = await self.client.post(self.QRCODE_GEN_API, json=data)
res_json = res.json()
url = res_json.get("data", {}).get("url", "")
if not url:
return ""
self.ticket = url.split("ticket=")[1]
return url
async def get_cookie_token_data(self, game_token: str) -> Dict:
res = await self.client.get(
self.GAME_TOKEN_API,
params={"game_token": game_token, "account_id": self.uid},
)
return res.json()
async def set_cookie(self, data: Dict) -> bool:
self.cookie = {}
game_token = json.loads(data.get("payload", {}).get("raw", "{}"))
if not game_token:
return False
self.uid = int(game_token["uid"])
for item in ["login_uid", "stuid", "ltuid", "account_id"]:
self.cookie[item] = str(self.uid)
cookie_token_data = await self.get_cookie_token_data(game_token["token"])
ltoken_data = await self.get_ltoken_by_game_token(game_token["token"])
self.cookie["cookie_token"] = cookie_token_data["data"]["cookie_token"]
for item in ["account_mid_v2", "ltmid_v2"]:
self.cookie[item] = ltoken_data["data"]["user_info"]["mid"]
self.cookie["ltoken_v2"] = ltoken_data["data"]["token"]["token"]
return True
for k, v in data.cookies.items():
self.cookie[k] = v
async def check_login(self):
data = {"app_id": "4", "ticket": self.ticket, "device": self.device_id}
for _ in range(20):
await sleep(10)
res = await self.client.post(self.QRCODE_GET_API, json=data)
res_json = res.json()
ret_code = res_json.get("retcode", 1)
if ret_code != 0:
print(res_json)
return False
data = res_json.get("data", {})
if data.get("stat", "") == "Confirmed":
return await self.set_cookie(res_json.get("data", {}))
return "cookie_token" in self.cookie or "cookie_token_v2" in self.cookie
@staticmethod
def generate_qrcode(url: str) -> bytes:
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
bio = BytesIO()
img.save(bio, format="PNG")
return bio.getvalue()

View File

@ -6,6 +6,7 @@ from arkowrapper import ArkoWrapper
from genshin import DataNotPublic, GenshinException, InvalidCookies, types
from genshin.models import GenshinAccount
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, TelegramObject, Update
from telegram.constants import ParseMode
from telegram.ext import CallbackContext, ConversationHandler, filters
from telegram.helpers import escape_markdown
@ -34,7 +35,7 @@ class AddUserCommandData(TelegramObject):
sign_in_client: Optional[SignIn] = None
CHECK_SERVER, CHECK_PHONE, CHECK_CAPTCHA, INPUT_COOKIES, COMMAND_RESULT = range(10100, 10105)
CHECK_SERVER, INPUT_COOKIES, COMMAND_RESULT = range(10100, 10103)
class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
@ -85,20 +86,41 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
return CHECK_SERVER
@conversation.entry_point
@handler.command(command="mlogin", filters=filters.ChatType.PRIVATE, block=True)
@handler.command("qlogin", filters=filters.ChatType.PRIVATE, block=True)
@error_callable
async def choose_method(self, update: Update, context: CallbackContext) -> int:
async def qrcode_login(self, update: Update, context: CallbackContext):
user = update.effective_user
message = update.effective_message
logger.info(f"用户 {user.full_name}[{user.id}] 绑定账号命令请求")
logger.info("用户 %s[%s] 绑定账号命令请求", user.full_name, user.id)
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
if add_user_command_data is None:
cookies_command_data = AddUserCommandData()
cookies_command_data.region = RegionEnum.HYPERION
context.chat_data["add_user_command_data"] = cookies_command_data
text = f'你好 {user.mention_markdown_v2()} {escape_markdown("!该绑定方法仅支持国服,请发送 11 位手机号码!或回复退出取消操作")}'
await message.reply_markdown_v2(text)
return CHECK_PHONE
add_user_command_data = AddUserCommandData()
add_user_command_data.region = RegionEnum.HYPERION
context.chat_data["add_user_command_data"] = add_user_command_data
try:
user_info = await self.user_service.get_user_by_id(user.id)
except UserNotFoundError:
user_info = None
if user_info is not None:
try:
cookies_database_data = await self.cookies_service.get_cookies(user.id, RegionEnum.HYPERION)
add_user_command_data.cookies_database_data = cookies_database_data
except CookiesNotFoundError:
await message.reply_text("你已经绑定UID如果继续操作会覆盖当前UID。")
else:
await message.reply_text("警告你已经绑定Cookie如果继续操作会覆盖当前Cookie。")
add_user_command_data.user = user_info
sign_in_client = SignIn()
url = await sign_in_client.create_login_data()
data = sign_in_client.generate_qrcode(url)
text = f"你好 {user.mention_html()} 该绑定方法仅支持国服请在3分钟内使用米游社扫码并确认进行绑定。"
await message.reply_photo(data, caption=text, parse_mode=ParseMode.HTML)
if await sign_in_client.check_login():
add_user_command_data.cookies = sign_in_client.cookie
return await self.check_cookies(update, context)
else:
await message.reply_markdown_v2("可能是验证码已过期或者你没有同意授权,请重新发送命令进行绑定。")
return ConversationHandler.END
@conversation.state(state=CHECK_SERVER)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@ -177,79 +199,6 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
await message.reply_html(help_message, disable_web_page_preview=True)
return INPUT_COOKIES
@conversation.state(state=CHECK_PHONE)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable
async def check_phone(self, update: Update, context: CallbackContext) -> int:
message = update.effective_message
if message.text == "退出":
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
try:
if not message.text.startswith("1"):
raise ValueError
phone = int(message.text)
if len(str(phone)) != 11:
raise ValueError
except ValueError:
await message.reply_text("手机号码输入错误,请重新输入!或回复退出取消操作")
return CHECK_PHONE
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
add_user_command_data.phone = phone
await message.reply_text(
"请打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码," "然后将收到的验证码发送给我(请不要在网页上进行登录)"
)
return CHECK_CAPTCHA
@conversation.state(state=CHECK_CAPTCHA)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable
async def check_captcha(self, update: Update, context: CallbackContext) -> int:
user = update.effective_user
message = update.effective_message
if message.text == "退出":
await message.reply_text("退出任务", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
try:
captcha = int(message.text)
if len(str(captcha)) != 6:
raise ValueError
except ValueError:
await message.reply_text("验证码输入错误,请重新输入!或回复退出取消操作")
return CHECK_CAPTCHA
add_user_command_data: AddUserCommandData = context.chat_data.get("add_user_command_data")
if not add_user_command_data.sign_in_client:
phone = add_user_command_data.phone
client = SignIn(phone)
try:
success = await client.login(captcha)
if not success:
await message.reply_text("登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
return ConversationHandler.END
await client.get_s_token()
except Exception as exc: # pylint: disable=W0703
logger.error(f"用户 {user.full_name}[{user.id}] 登录失败 {repr(exc)}")
await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!")
return ConversationHandler.END
add_user_command_data.sign_in_client = client
await message.reply_text(
"请再次打开 https://user.mihoyo.com/#/login/captcha ,输入手机号并获取验证码(需要等待一分钟)," "然后将收到的验证码发送给我(请不要在网页上进行登录)"
)
return CHECK_CAPTCHA
else:
client = add_user_command_data.sign_in_client
try:
success = await client.get_token(captcha)
if not success:
await message.reply_text("登录失败:可能是验证码错误,注意不要在登录页面使用掉验证码,如果验证码已经使用,请重新获取验证码!")
return ConversationHandler.END
except Exception as exc: # pylint: disable=W0703
logger.error(f"用户 {user.full_name}[{user.id}] 登录失败 {repr(exc)}")
await message.reply_text("登录失败:米游社返回了错误的数据,请稍后再试!")
return ConversationHandler.END
add_user_command_data.cookies = client.cookie
return await self.check_cookies(update, context)
@conversation.state(state=INPUT_COOKIES)
@handler.message(filters=filters.TEXT & ~filters.COMMAND, block=True)
@error_callable
@ -325,7 +274,7 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END
except (AttributeError, ValueError) as exc:
logger.warning("用户 %s[%s] Cookies错误", user.full_name, user.id)
logger.debug("用户 %s[%s] Cookies错误" % (user.full_name, user.id), exc_info=exc)
logger.debug("用户 %s[%s] Cookies错误", user.full_name, user.id, exc_info=exc)
await message.reply_text("Cookies错误请检查是否正确", reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
with contextlib.suppress(Exception):
@ -347,7 +296,7 @@ class SetUserCookies(Plugin.Conversation, BasePlugin.Conversation):
add_user_command_data.game_uid = user_info.uid
reply_keyboard = [["确认", "退出"]]
await message.reply_text("获取角色基础信息成功,请检查是否正确!")
logger.info(f"用户 {user.full_name}[{user.id}] 获取账号 {user_info.nickname}[{user_info.uid}] 信息成功")
logger.info("用户 %s[%s] 获取账号 {%s}[%s] 信息成功", user.full_name, user.id, user_info.nickname, user_info.uid)
text = (
f"*角色信息*\n"
f"角色名称:{escape_markdown(user_info.nickname, version=2)}\n"

55
poetry.lock generated
View File

@ -167,7 +167,7 @@ python-versions = ">=3.5"
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
[[package]]
name = "backports.zoneinfo"
@ -242,7 +242,7 @@ optional = false
python-versions = ">=3.6.0"
[package.extras]
unicode-backport = ["unicodedata2"]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
@ -304,7 +304,7 @@ dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version
[[package]]
name = "enkanetwork.py"
version = "1.3.0"
version = "1.2.10"
description = "Library for fetching JSON data from site https://enka.network/"
category = "main"
optional = false
@ -392,8 +392,8 @@ python-versions = ">=3.7"
[[package]]
name = "genshin"
version = "1.4.0"
description = ""
version = "1.3.0"
description = "An API wrapper for Genshin Impact."
category = "main"
optional = false
python-versions = ">=3.8"
@ -413,7 +413,7 @@ geetest = ["rsa"]
type = "git"
url = "https://github.com/thesadru/genshin.py"
reference = "HEAD"
resolved_reference = "072c7c101b648f7a847ab27a4b1ae47c943ee316"
resolved_reference = "6685886c4651e489c0fc145af4a61e826c7750ac"
[[package]]
name = "gitdb"
@ -890,6 +890,24 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "qrcode"
version = "7.3.1"
description = "QR Code image generator"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
all = ["pillow", "pytest", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"]
dev = ["pytest", "tox"]
maintainer = ["zest.releaser[recommended]"]
pil = ["pillow"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "redis"
version = "4.3.4"
@ -960,7 +978,7 @@ falcon = ["falcon (>=1.4)"]
fastapi = ["fastapi (>=0.79.0)"]
flask = ["blinker (>=1.1)", "flask (>=0.11)"]
httpx = ["httpx (>=0.16.0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
pure_eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"]
quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]
@ -1039,19 +1057,19 @@ aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mssql_pymssql = ["pymssql"]
mssql_pyodbc = ["pyodbc"]
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
mysql-connector = ["mysql-connector-python"]
mysql_connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
postgresql-psycopg2cffi = ["psycopg2cffi"]
postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
postgresql_psycopg2binary = ["psycopg2-binary"]
postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql", "pymysql (<1)"]
sqlcipher = ["sqlcipher3_binary"]
@ -1294,7 +1312,7 @@ test = ["pytest", "pytest-asyncio", "flaky"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "762203f9de83ba0b8003e9e7ba233477e701d234ac0c782635c4870537a4fe93"
content-hash = "e1cf967bf3a56d440f3caadfa5bc93bf5b58a218c0a010d15f0fc3db1d8635f7"
[metadata.files]
aiofiles = [
@ -1761,6 +1779,7 @@ lxml = [
{file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"},
{file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"},
{file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"},
{file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"},
{file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"},
{file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"},
{file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"},
@ -1770,6 +1789,7 @@ lxml = [
{file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"},
{file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"},
{file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"},
{file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"},
{file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"},
{file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"},
{file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"},
@ -2147,6 +2167,9 @@ PyYAML = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
qrcode = [
{file = "qrcode-7.3.1.tar.gz", hash = "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578"},
]
redis = [
{file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"},
{file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"},

View File

@ -41,6 +41,7 @@ GitPython = "^3.1.29"
openpyxl = "^3.0.10"
async-lru = "^1.0.3"
thefuzz = "^0.19.0"
qrcode = "^7.3.1"
[tool.poetry.extras]
pyro = ["Pyrogram", "TgCrypto"]