From 59848e15fc4aa18536ed56ce302b4cc59d1e78ce Mon Sep 17 00:00:00 2001 From: cosven Date: Sun, 2 Jun 2024 23:38:02 +0800 Subject: [PATCH] refactor login process --- fuo_bilibili/api/__init__.py | 28 ++--------- fuo_bilibili/const.py | 1 + fuo_bilibili/model.py | 12 +++-- fuo_bilibili/provider.py | 14 ++---- fuo_bilibili/ui.py | 90 ++++++++++++++++++++---------------- 5 files changed, 66 insertions(+), 79 deletions(-) diff --git a/fuo_bilibili/api/__init__.py b/fuo_bilibili/api/__init__.py index 7d2b2c9..38d3e7a 100644 --- a/fuo_bilibili/api/__init__.py +++ b/fuo_bilibili/api/__init__.py @@ -35,41 +35,21 @@ def __init__(self): ' AppleWebKit/537.36 (KHTML, like Gecko)' ' Chrome/33.0.1750.152 Safari/537.36' } - self._cookie = MozillaCookieJar(PLUGIN_API_COOKIEJAR_FILE) self._session = requests.Session() # UPDATE(2024-01-11): To make self.nav_info work, the header needs to be added # UPDATE(2023-xx-xx): The header cause some API failing: self.nav_info self._session.headers.update(self._headers) - self._session.cookies = self._cookie self._wbi: Optional[NavInfoResponse.NavInfoResponseData.Wbi] = None - @staticmethod - def cookie_check(): - if not PLUGIN_API_COOKIEJAR_FILE.exists(): - return False - return True - def get_session(self): return self._session - def get_cookies(self): + def get_cookiejar(self): return self._session.cookies - @staticmethod - def remove_cookie(): - PLUGIN_API_COOKIEJAR_FILE.unlink(missing_ok=True) - - def from_cookiejar(self, jar: CookieJar): - for cookie in jar: - if 'bilibili.com' in cookie.domain: - self._cookie.set_cookie(cookie) - self._dump_cookie_to_file() - - def load_cookies(self): - self._cookie.load() - - def _dump_cookie_to_file(self): - self._cookie.save() + def from_cookiedict(self, cookies): + cookiejar = requests.cookies.cookiejar_from_dict(cookies) + self._session.cookies = cookiejar def set_wbi(self, wbi: NavInfoResponse.NavInfoResponseData.Wbi): self._wbi = wbi diff --git a/fuo_bilibili/const.py b/fuo_bilibili/const.py index f57156a..c39bb34 100644 --- a/fuo_bilibili/const.py +++ b/fuo_bilibili/const.py @@ -9,6 +9,7 @@ # Cookiejar file PLUGIN_API_COOKIEJAR_FILE = PLUGIN_DATA_DIRECTORY / 'bilibili_api.cookie' +PLUGIN_API_COOKIEDICT_FILE = PLUGIN_DATA_DIRECTORY / 'bilibili_api_cookie.json' # Ensure directories PLUGIN_DATA_DIRECTORY.mkdir(parents=True, exist_ok=True) diff --git a/fuo_bilibili/model.py b/fuo_bilibili/model.py index 35d8a3a..8605fc5 100644 --- a/fuo_bilibili/model.py +++ b/fuo_bilibili/model.py @@ -23,6 +23,12 @@ def get_text_from_html(html): return BeautifulSoup(html, features="html.parser").get_text() +def wrap_pic(pic): + if pic.startswith('//'): + return f'http:{pic}' + return pic + + class BBriefAlbumModel(BriefAlbumModel): cover: str = '' @@ -112,7 +118,7 @@ def create_info_model(cls, response: VideoInfoResponse) -> 'BSongModel': title=page.part, artists=[], duration=page.duration.total_seconds() * 1000, - pic_url=result.pic, + pic_url=wrap_pic(result.pic), ) children.append(song) @@ -134,7 +140,7 @@ def create_info_model(cls, response: VideoInfoResponse) -> 'BSongModel': )], duration=result.duration.total_seconds() * 1000, lyric=lrc or '', - pic_url=result.pic, + pic_url=wrap_pic(result.pic), children=children, ) @@ -495,7 +501,7 @@ def create_video_model(cls, result: SearchResultVideo) -> 'VideoModel': identifier=result.mid )], duration=result.duration.total_seconds(), - cover=result.pic, + cover=wrap_pic(result.pic), play_count=result.play, released=result.pubdate.strftime('%Y-%m-%d'), ) diff --git a/fuo_bilibili/provider.py b/fuo_bilibili/provider.py index 2909def..e01ce7b 100644 --- a/fuo_bilibili/provider.py +++ b/fuo_bilibili/provider.py @@ -130,17 +130,8 @@ def request_captcha(self) -> RequestCaptchaResponse.RequestCaptchaResponseData: def request_key(self) -> RequestLoginKeyResponse: return self._api.request_login_key() - def cookie_check(self): - return self._api.cookie_check() - - def auth(self, _): - self._api.load_cookies() - try: - self._user = self.user_info() - except RuntimeError as re: - self._api.remove_cookie() - print(str(re) + ' 请重新登录') - return self._user + def auth(self, user): + self._user = user def has_current_user(self) -> bool: return self._user is not None @@ -159,6 +150,7 @@ def user_info(self) -> UserModel: name=data.uname, avatar_url=data.face ) + print(user.avatar_url) return user def cookiejar_login(self, jar): diff --git a/fuo_bilibili/ui.py b/fuo_bilibili/ui.py index bb13266..0d6b1c8 100644 --- a/fuo_bilibili/ui.py +++ b/fuo_bilibili/ui.py @@ -1,5 +1,6 @@ import asyncio import logging +import json from pathlib import Path from typing import Optional @@ -9,42 +10,53 @@ QAction, QInputDialog, QWidget from feeluown.app.gui_app import GuiApp from feeluown.gui import ProviderUiManager +from feeluown.gui.widgets.login import CookiesLoginDialog, InvalidCookies from feeluown.library import UserModel +from feeluown.utils.aio import run_fn from fuo_bilibili import __identifier__, __alias__, BilibiliProvider from fuo_bilibili.api.schema.requests import PasswordLoginRequest, SendSmsCodeRequest, SmsCodeLoginRequest from fuo_bilibili.api.schema.responses import RequestLoginKeyResponse from fuo_bilibili.geetest.server import GeetestAuthServer from fuo_bilibili.util import rsa_encrypt, get_random_available_port +from fuo_bilibili.const import PLUGIN_API_COOKIEDICT_FILE logger = logging.getLogger(__name__) -class KeyringLoginWidget(QWidget): - finished = pyqtSignal() +class LoginDialog(CookiesLoginDialog): def __init__(self, provider: BilibiliProvider, *args, **kwargs): super().__init__(*args, **kwargs) - self._provider = provider - self._chrome_btn = QPushButton('从 Chrome 中读取 Cookie') + self.provider = provider - self._layout = QVBoxLayout(self) - self._layout.addWidget(self._chrome_btn) - self._chrome_btn.clicked.connect(self._get_cookies_from_chrome) + def setup_user(self, user): + self.provider.auth(user) - def _get_cookies_from_chrome(self): - from feeluown.utils.yt_dlp_cookies import load_cookies - jar = load_cookies(None, ['chrome'], None) - self._provider.cookiejar_login(jar) - self.finished.emit() + async def user_from_cookies(self, cookies): + if not cookies: # is None or empty + raise InvalidCookies('empty cookies') - @classmethod - def is_supported(cls): + self.provider._api.from_cookiedict(cookies) try: - from feeluown.utils.yt_dlp_cookies import load_cookies # noqa - except ImportError: - return False - return True + user = await run_fn(self.provider.user_info) + except RuntimeError as e: + raise InvalidCookies(f'get user with cookies failed: {e}') + return user + + def load_user_cookies(self): + if PLUGIN_API_COOKIEDICT_FILE.exists(): + with PLUGIN_API_COOKIEDICT_FILE.open('r', encoding='utf-8') as f: + try: + cookie_dict = json.load(f) + except Exception: + logger.warning('parse cookies(json) failed') + return None + return cookie_dict + + def dump_user_cookies(self, _, cookies): + with PLUGIN_API_COOKIEDICT_FILE.open('w', encoding='utf-8') as f: + json.dump(cookies, f, indent=2) class BAuthDialog(QDialog): @@ -197,16 +209,6 @@ def __init__(self, parent=None, provider=None, ui_manger=None): self._pw_tab.hide() # self._tab.addTab(self._sms_tab, '验证码登录') # self._tab.addTab(self._pw_tab, '密码登录') - if KeyringLoginWidget.is_supported(): - # keyring 登录 - self._keyring_tab = KeyringLoginWidget(self._provider, parent=self._tab) - self._tab.addTab(self._keyring_tab, 'Keyring 登录') - self._keyring_tab.finished.connect(self._finish_keyring_login) - else: - self._tab.addTab( - QLabel('登录功能从 2023-08-01 开始,已经无法使用', self), - '提示', - ) # auth back signal # noinspection PyUnresolvedReferences @@ -303,9 +305,11 @@ def __init__(self, app: GuiApp, provider: BilibiliProvider): desc='点击登录', colorful_svg=(Path(__file__).parent / 'assets' / 'icon.svg').as_posix() ) - self._pvd_item.clicked.connect(self._login_or_get_user) + self._pvd_item.clicked.connect(self.login_or_go_home) self._pvd_uimgr.add_item(self._pvd_item) - self.login_dialog = BLoginDialog(None, self._provider) + + self.login_dialog = None + # 新建收藏夹 pl_header = self._app.ui.left_panel.playlists_header pl_header.setContextMenuPolicy(Qt.ActionsContextMenu) @@ -315,8 +319,6 @@ def __init__(self, app: GuiApp, provider: BilibiliProvider): new_pl_action.triggered.connect(self.new_playlist) self._initial_pages() - self.login_dialog.login_succeed.connect(self._login_or_get_user) - def new_playlist(self): name, o1 = QInputDialog.getText(self._app.ui.left_panel.playlists_header, '新建收藏夹', '收藏夹标题') if not o1: @@ -370,14 +372,20 @@ async def load_user_content(self): self._app.pl_uimgr.add(audio_fav_list) self._app.pl_uimgr.add(audio_coll_list, is_fav=True) - def _login(self): - self._user = self._provider.auth(None) - if self._user is not None: - self._pvd_item.text = f'{__alias__}已登录:{self._user.name} UID:{self._user.identifier}' + def after_login_succeed(self): + user = self._provider.get_current_user() + assert user is not None + self._user = user + self._pvd_item.text = f'{__alias__}已登录:{self._user.name} UID:{self._user.identifier}' asyncio.ensure_future(self.load_user_content()) - def _login_or_get_user(self): - if self._provider.cookie_check(): - self._login() - return - self.login_dialog.show() + def login_or_go_home(self): + if self._provider._user is None: + # keyring 登录 + self.login_dialog = LoginDialog( + self._provider, 'https://bilibili.com', ['SESSDATA']) + self.login_dialog.login_succeed.connect(self.after_login_succeed) + self.login_dialog.autologin() + self.login_dialog.show() + else: + self.after_login_succeed()