🔖 Update to v1.2.25

Support qr login.
This commit is contained in:
xtaodada 2023-01-20 21:57:30 +08:00
parent efea7afd01
commit aa42ed7372
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
13 changed files with 139 additions and 209 deletions

1
.gitignore vendored
View File

@ -131,6 +131,7 @@ unknown_errors.txt
pagermaid*.txt
exception*.txt
output.log
qrcode.png
# Spyder project settings
.spyderproject

View File

@ -10,6 +10,7 @@
# API Credentials of your telegram application created at https://my.telegram.org/apps
api_id: "ID_HERE"
api_hash: "HASH_HERE"
qrcode_login: "False"
# Either debug logging is enabled or not
debug: "False"

View File

@ -12,7 +12,7 @@ from pagermaid.scheduler import scheduler
import pyromod.listen
from pyrogram import Client
pgm_version = "1.2.24"
pgm_version = "1.2.25"
CMD_LIST = {}
module_dir = __path__[0]
working_dir = getcwd()

View File

@ -10,6 +10,7 @@ from pagermaid.hook import Hook
from pagermaid.modules import module_list, plugin_list
from pagermaid.single_utils import safe_remove
from pagermaid.utils import lang, process_exit
from pyromod.methods.sign_in_qrcode import start_client
path.insert(1, f"{working_dir}{sep}plugins")
@ -18,7 +19,7 @@ async def main():
logs.info(lang('platform') + platform + lang('platform_load'))
try:
await bot.start()
await start_client(bot)
except AuthKeyUnregistered:
safe_remove("pagermaid.session")
exit()

View File

@ -43,6 +43,7 @@ class Config:
# TGX
API_ID = DEFAULT_API_ID
API_HASH = DEFAULT_API_HASH
QRCODE_LOGIN = strtobool(os.environ.get("QRCODE_LOGIN", config.get("qrcode_login", "false")))
STRING_SESSION = os.environ.get("STRING_SESSION")
DEBUG = strtobool(os.environ.get("PGM_DEBUG", config["debug"]))
ERROR_REPORT = strtobool(os.environ.get("PGM_ERROR_REPORT", config["error_report"]), True)

View File

@ -1,20 +0,0 @@
"""
pyromod - A monkeypatcher add-on for Pyrogram
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
This file is part of pyromod.
pyromod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyromod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>.
"""
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply

View File

@ -1,75 +0,0 @@
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ForceReply
def ikb(rows=None):
if rows is None:
rows = []
lines = []
for row in rows:
line = []
for button in row:
button = btn(*button) # InlineKeyboardButton
line.append(button)
lines.append(line)
return InlineKeyboardMarkup(inline_keyboard=lines)
# return {'inline_keyboard': lines}
def btn(text, value, type='callback_data'):
return InlineKeyboardButton(text, **{type: value})
# return {'text': text, type: value}
# The inverse of above
def bki(keyboard):
lines = []
for row in keyboard.inline_keyboard:
line = []
for button in row:
button = ntb(button) # btn() format
line.append(button)
lines.append(line)
return lines
# return ikb() format
def ntb(button):
for btn_type in ['callback_data', 'url', 'switch_inline_query', 'switch_inline_query_current_chat',
'callback_game']:
value = getattr(button, btn_type)
if value:
break
button = [button.text, value]
if btn_type != 'callback_data':
button.append(btn_type)
return button
# return {'text': text, type: value}
def kb(rows=None, **kwargs):
if rows is None:
rows = []
lines = []
for row in rows:
line = []
for button in row:
button_type = type(button)
if button_type == str:
button = KeyboardButton(button)
elif button_type == dict:
button = KeyboardButton(**button)
line.append(button)
lines.append(line)
return ReplyKeyboardMarkup(keyboard=lines, **kwargs)
kbtn = KeyboardButton
def force_reply(selective=True):
return ForceReply(selective=selective)
def array_chunk(input_, size):
return [input_[i:i + size] for i in range(0, len(input_), size)]

View File

View File

@ -0,0 +1,127 @@
import asyncio
import base64
from typing import Optional
import pyrogram
from pyqrcode import QRCode
from pyrogram import Client
from pyrogram.errors import SessionPasswordNeeded, BadRequest
from pyrogram.session import Auth, Session
from pyrogram.utils import ainput
from pagermaid import Config
async def sign_in_qrcode(
client: Client,
) -> Optional[str]:
req = await client.invoke(
pyrogram.raw.functions.auth.ExportLoginToken(
api_id=client.api_id,
api_hash=client.api_hash,
except_ids=[],
)
)
if isinstance(req, pyrogram.raw.types.auth.LoginToken):
token = base64.b64encode(req.token)
return f"tg://login?token={token.decode('utf-8')}"
elif isinstance(req, pyrogram.raw.types.auth.LoginTokenMigrateTo):
await client.session.stop()
await client.storage.dc_id(req.dc_id)
await client.storage.auth_key(
await Auth(client, await client.storage.dc_id(), await client.storage.test_mode()).create()
)
client.session = Session(
client, await client.storage.dc_id(),
await client.storage.auth_key(), await client.storage.test_mode()
)
await client.session.start()
req = await client.invoke(pyrogram.raw.functions.auth.ImportLoginToken(token=req.token))
await client.storage.user_id(req.user.id)
await client.storage.is_bot(False)
return pyrogram.types.User._parse(client, req.user)
elif isinstance(req, pyrogram.raw.types.auth.LoginTokenSuccess):
await client.storage.user_id(req.authorization.user.id)
await client.storage.is_bot(False)
return pyrogram.types.User._parse(client, req.authorization.user)
async def authorize_by_qrcode(
client: Client,
):
print(f"Welcome to Pyrogram (version {pyrogram.__version__})")
print(f"Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed\n"
f"under the terms of the {pyrogram.__license__}.\n")
while True:
qrcode = None
try:
qrcode = await sign_in_qrcode(client)
except BadRequest as e:
print(e.MESSAGE)
except SessionPasswordNeeded as e:
print(e.MESSAGE)
while True:
print(f"Password hint: {await client.get_password_hint()}")
if not client.password:
client.password = await ainput("Enter password (empty to recover): ", hide=client.hide_password)
try:
if client.password:
return await client.check_password(client.password)
confirm = await ainput("Confirm password recovery (y/n): ")
if confirm == "y":
email_pattern = await client.send_recovery_code()
print(f"The recovery code has been sent to {email_pattern}")
while True:
recovery_code = await ainput("Enter recovery code: ")
try:
return await client.recover_password(recovery_code)
except BadRequest as e:
print(e.MESSAGE)
else:
client.password = None
except BadRequest as e:
print(e.MESSAGE)
client.password = None
if isinstance(qrcode, str):
qr_obj = QRCode(qrcode)
try:
qr_obj.png("qrcode.png", scale=6)
except Exception:
print("Save qrcode.png failed.")
print(qr_obj.terminal())
print(f"Scan the QR code above, the qrcode.png file or visit {qrcode} to log in.\n")
print("QR code will expire in 20 seconds. If you have scanned it, please wait...")
await asyncio.sleep(20)
elif isinstance(qrcode, pyrogram.types.User):
return qrcode
async def start_client(client: Client):
is_authorized = await client.connect()
try:
if not is_authorized:
if Config.QRCODE_LOGIN:
await authorize_by_qrcode(client)
else:
await client.authorize()
if not await client.storage.is_bot() and client.takeout:
client.takeout_id = (await client.invoke(pyrogram.raw.functions.account.InitTakeoutSession())).id
await client.invoke(pyrogram.raw.functions.updates.GetState())
except (Exception, KeyboardInterrupt):
await client.disconnect()
raise
else:
client.me = await client.get_me()
await client.initialize()
return client

View File

@ -1,20 +0,0 @@
"""
pyromod - A monkeypatcher add-on for Pyrogram
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
This file is part of pyromod.
pyromod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyromod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>.
"""
from .pagination import Pagination

View File

@ -1,91 +0,0 @@
"""
pyromod - A monkeypatcher add-on for Pyrogram
Copyright (C) 2020 Cezar H. <https://github.com/usernein>
This file is part of pyromod.
pyromod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyromod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>.
"""
import math
from ..helpers import array_chunk
class Pagination:
def __init__(self, objects, page_data=None, item_data=None, item_title=None):
default_page_callback = (lambda x: str(x))
default_item_callback = (lambda i, pg: f'[{pg}] {i}')
self.objects = objects
self.page_data = page_data or default_page_callback
self.item_data = item_data or default_item_callback
self.item_title = item_title or default_item_callback
def create(self, page, lines=5, columns=1):
quant_per_page = lines * columns
page = 1 if page <= 0 else page
offset = (page - 1) * quant_per_page
stop = offset + quant_per_page
cutted = self.objects[offset:stop]
total = len(self.objects)
pages_range = [*range(1, math.ceil(total / quant_per_page) + 1)] # each item is a page
last_page = len(pages_range)
nav = []
if page <= 3:
for n in [1, 2, 3]:
if n not in pages_range:
continue
text = f"· {n} ·" if n == page else n
nav.append((text, self.page_data(n)))
if last_page >= 4:
nav.append(
('4 ' if last_page > 5 else 4, self.page_data(4))
)
if last_page > 4:
nav.append(
(f'{last_page} »' if last_page > 5 else last_page, self.page_data(last_page))
)
elif page >= last_page - 2:
nav.extend(
[
('« 1' if last_page > 5 else 1, self.page_data(1)),
(
f' {last_page - 3}' if last_page > 5 else last_page - 3,
self.page_data(last_page - 3),
),
]
)
for n in range(last_page - 2, last_page + 1):
text = f"· {n} ·" if n == page else n
nav.append((text, self.page_data(n)))
else:
nav = [
('« 1', self.page_data(1)),
(f' {page - 1}', self.page_data(page - 1)),
(f'· {page} ·', "noop"),
(f'{page + 1} ', self.page_data(page + 1)),
(f'{last_page} »', self.page_data(last_page)),
]
buttons = [
(self.item_title(item, page), self.item_data(item, page))
for item in cutted
]
kb_lines = array_chunk(buttons, columns)
if last_page > 1:
kb_lines.append(nav)
return kb_lines

View File

@ -10,3 +10,5 @@ apscheduler
sqlitedict
casbin==1.17.5
sentry-sdk==1.13.0
PyQRCode>=1.2.1
PyPng

View File

@ -19,7 +19,10 @@ configure () {
printf "请输入应用程序 api_hash不懂请直接回车"
read -r api_hash <&1
sed -i "s/HASH_HERE/$api_hash/" $config_file
printf "请输入应用程序语言默认zh-cn"
read -p "二维码扫码登录?(避免无法收到验证码) [Y/n]" choi
if [ "$choi" == "y" ] || [ "$choi" == "Y" ]; then
sed -i "s/qrcode_login: \"False\"/qrcode_login: \"True\"/" $config_file
fi
read -r application_language <&1
if [ -z "$application_language" ]
then