From 021373601b534cf1a10973b9247c8d9a05fdffd6 Mon Sep 17 00:00:00 2001 From: ForgQi <34411314+ForgQi@users.noreply.github.com> Date: Tue, 20 Oct 2020 13:29:57 +0800 Subject: [PATCH] Add username and password way to login --- .gitignore | 2 +- common/__init__.py | 13 ++-- config(demo).yaml | 10 +-- engine/__init__.py | 2 +- engine/plugins/bilibili.py | 149 +++++++++++++++++++++++++++++-------- requirements.txt | 3 +- 6 files changed, 133 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index c2338dc..ec944d0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ chromedriver.* cookie.py download.py ds_update.log* -Engine/bilibili.cookie +*.cookie test.py /upload.py /work.py diff --git a/common/__init__.py b/common/__init__.py index c8e1201..e6f769a 100644 --- a/common/__init__.py +++ b/common/__init__.py @@ -3,7 +3,6 @@ import os import pkgutil from datetime import datetime, timezone, timedelta import logging.config -import engine.plugins def time_now(): @@ -19,15 +18,15 @@ def new_hook(t, v, tb): # @singleton -def load_plugins(path): +def load_plugins(): """Attempt to load plugins from the path specified. - - :param path: full path to a directory where to look for plugins - + engine.plugins.__path__[0]: full path to a directory where to look for plugins """ + import engine.plugins + plugins = [] - for loader, name, ispkg in pkgutil.iter_modules([path]): + for loader, name, ispkg in pkgutil.iter_modules([engine.plugins.__path__[0]]): # set the full plugin module name module_name = "engine.plugins.{0}".format(name) # print(module_name) @@ -40,7 +39,7 @@ def load_plugins(path): return plugins -PLUGINS = load_plugins(engine.plugins.__path__[0]) +load_plugins() log_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'configlog.ini') diff --git a/config(demo).yaml b/config(demo).yaml index fa683cd..e5d851e 100644 --- a/config(demo).yaml +++ b/config(demo).yaml @@ -1,13 +1,13 @@ -user: # cookies和account二选一填写即可 - cookies: # 默认通过网页接口上传 +user: # 在填了cookies的情况下优先使用cookies上传,如需使用用户名密码上传请注释掉cookies + cookies: SESSDATA: your SESSDATA bili_jct: your bili_jct DedeUserID: your DedeUserID - account: # 这种方式目前依赖chromedriver + account: username: your usrname password: your password - -chromedrive_path: /usr/local/bin/chromedriver +# 默认通过网页接口上传,可选通过操作chrome上传,此时需要填写chromedriver路径 +chromedriver_path: /usr/local/bin/chromedriver streamers: 星际2INnoVation吕布卫星人族天梯第一视角: diff --git a/engine/__init__.py b/engine/__init__.py index 86d2413..2e89aae 100644 --- a/engine/__init__.py +++ b/engine/__init__.py @@ -9,7 +9,7 @@ with open(r'config.yaml', encoding='utf-8') as stream: streamers = config['streamers'] user_name = config['user']['account']['username'] pass_word = config['user']['account']['password'] -chromedrive_path = config['chromedrive_path'] +chromedriver_path = config.get('chromedriver_path') def invert_dict(d: dict): diff --git a/engine/plugins/bilibili.py b/engine/plugins/bilibili.py index 3722a8a..90b86aa 100644 --- a/engine/plugins/bilibili.py +++ b/engine/plugins/bilibili.py @@ -1,12 +1,16 @@ +import base64 +import hashlib import json import os import time from dataclasses import dataclass, field, InitVar, asdict from os.path import basename, splitext from typing import Any, Union +from urllib import parse from urllib.parse import quote import requests +import rsa import selenium.common from requests import utils from selenium import webdriver @@ -23,7 +27,7 @@ from engine.slider import slider_cracker @Plugin.upload("bilibili") -class Bilibili(UploadBase): +class BiliChrome(UploadBase): def __init__(self, principal, data): super().__init__(principal, data, 'engine/bilibili.cookie') # self.title = title @@ -61,7 +65,7 @@ class Bilibili(UploadBase): options = webdriver.ChromeOptions() options.add_argument('headless') - self.driver = webdriver.Chrome(executable_path=engine.chromedrive_path, chrome_options=options) + self.driver = webdriver.Chrome(executable_path=engine.chromedriver_path, chrome_options=options) # service_log_path=service_log_path) try: self.driver.get("https://www.bilibili.com") @@ -230,56 +234,128 @@ class Bilibili(UploadBase): @Plugin.upload(platform="bili_web") -class Upload(UploadBase): +class BiliWeb(UploadBase): def __init__(self, principal, data): - super().__init__(principal, data) + super().__init__(principal, data, persistence_path='engine/bili.cookie') # cookie = data['cookie'] # self.__data: Upload.Data = data['config'] - self.video = Data() - self.__bili_jct = '' - self.__session = None def upload(self, file_list): - with requests.Session() as self.__session: - self.login() + video = Data() + with BiliBili(video) as bili: + self.login(bili) for file in file_list: - video_part = self.upload_file(file) # 上传视频 - self.video.videos.append(video_part) # 添加已经上传的视频 - # bilivideo.setDesc(f'转载于:{url}') #添加简介 - self.video.title = self.data["format_title"] - self.video.desc = '''这个自动录制上传的小程序开源在Github:http://t.cn/RgapTpf(或者在Github搜索ForgQi) + video_part = bili.upload_file(file) # 上传视频 + video.videos.append(video_part) # 添加已经上传的视频 + video.title = self.data["format_title"] + video.desc = '''这个自动录制上传的小程序开源在Github:http://t.cn/RgapTpf(或者在Github搜索ForgQi) 交流群:837362626''' - self.video.source = self.data["url"] # 添加转载地址说明 - # 设置视频分区,默认为 生活,其他分区 + video.source = self.data["url"] # 添加转载地址说明 + # 设置视频分区,默认为174 生活,其他分区 tid = engine.config['streamers'][self.principal].get('tid') if tid: - self.video.tid = tid + video.tid = tid tags = engine.config['streamers'][self.principal].get('tags', ['星际争霸2', '电子竞技']) if tags: - self.video.set_tag(tags) + video.set_tag(tags) img_path = engine.config['streamers'][self.principal].get('cover_path') if img_path: - self.video.cover = self.cover_up(img_path).replace('http:', '') - ret = self.submit() # 提交视频 - logger.info(f"upload_success:{ret}") - self.remove_filelist(file_list) + video.cover = bili.cover_up(img_path).replace('http:', '') + ret = bili.submit() # 提交视频 + logger.info(f"upload_success:{ret}") + self.remove_filelist(file_list) + + def login(self, b): + user = engine.config['user'] + cookies = None + if os.path.isfile(self.persistence_path): + print('使用持久化内容上传') + with open(self.persistence_path) as f: + cookies = json.load(f) + elif user.get('cookies'): + cookies = user['cookies'] + if cookies: + try: + b.login_by_cookies(cookies) + except: + logger.exception('login error') + cookies = b.login_by_password(**user['account']) + else: + cookies = b.login_by_password(**user['account']) + with open(self.persistence_path, "w") as f: + json.dump(cookies, f) + + +class BiliBili: + def __init__(self, video: 'Data'): + self.app_key = 'bca7e84c2d947ac6' + self.__session = requests.Session() + self.video = video - def login(self): - cookie = engine.config['user']['cookies'] - # self.__session = requests.session() self.__session.headers.update({ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108", "Referer": "https://www.bilibili.com/", 'Connection': 'keep-alive' }) + self.__bili_jct = None + + def login_by_password(self, username, password): + print('使用账号上传') + key_hash, pub_key = self.get_key() + encrypt_password = base64.b64encode(rsa.encrypt(f'{key_hash}{password}'.encode(), pub_key)) + payload = { + "actionKey": 'appkey', + "appkey": self.app_key, + "build": 6040500, + "captcha": '', + "challenge": '', + "channel": 'bili', + "device": 'phone', + "mobi_app": 'android', + "password": encrypt_password, + "permission": 'ALL', + "platform": 'android', + "seccode": "", + "subid": 1, + "ts": int(time.time()), + "username": username, + "validate": "", + } + response = self.__session.post("https://passport.bilibili.com/api/v3/oauth2/login", + data={**payload, 'sign': self.sign(parse.urlencode(payload))}) + r = response.json() + if r['code'] != 0 and r.get('data') and r['data'].get('status'): + raise RuntimeError(r) + for cookie in r['data']['cookie_info']['cookies']: + self.__session.cookies.set(cookie['name'], cookie['value']) + if 'bili_jct' == cookie['name']: + self.__bili_jct = cookie['value'] + return self.__session.cookies.get_dict() + + def login_by_cookies(self, cookie): + print('使用cookies上传') requests.utils.add_dict_to_cookiejar(self.__session.cookies, cookie) if 'bili_jct' in cookie: self.__bili_jct = cookie["bili_jct"] data = self.__session.get("https://api.bilibili.com/x/web-interface/nav").json() if data["code"] != 0: - raise Exception(data["message"]) - # self.__name = data["data"]["uname"] - # self.__uid = data["data"]["mid"] + raise Exception(data) + + @staticmethod + def sign(param): + salt = '60698ba2f68e01ce44738920a0ffe768' + return hashlib.md5(f"{param}{salt}".encode()).hexdigest() + + def get_key(self): + url = "https://passport.bilibili.com/api/oauth2/getKey" + payload = { + 'appkey': f'{self.app_key}', + 'sign': self.sign(f"appkey={self.app_key}"), + } + response = self.__session.post(url, data=payload) + r = response.json() + if r and r["code"] == 0: + return r['data']['hash'], rsa.PublicKey.load_pkcs1_openssl_pem(r['data']['key'].encode()) def upload_file(self, filepath: str): """上传本地视频文件,返回视频信息dict, fsize=8388608""" @@ -313,7 +389,7 @@ class Upload(UploadBase): f'&start={i * chunk_size}&end={i * chunk_size + len(chunks_data)}&total={total}', data=chunks_data, headers={"X-Upos-Auth": auth}) parts.append({"partNumber": i + 1, "eTag": "etag"}) # 添加分块信息,partNumber从1开始 - print(f'{(i+1)/chunks:.1%}') # 输出上传进度 + print(f'{(i + 1) / chunks:.1%}') # 输出上传进度 prefix = splitext(name)[0] r = self.__session.post( @@ -339,7 +415,6 @@ class Upload(UploadBase): :param img: img path or stream :return: img URL """ - import base64 # from PIL import Image # from io import BytesIO # @@ -348,9 +423,10 @@ class Upload(UploadBase): # xsize, ysize = im.size # if xsize / ysize > 1.6: # delta = xsize - ysize * 1.6 + # region = im.crop((delta / 2, 0, xsize - delta / 2, ysize)) # else: # delta = ysize - xsize * 10 /16 - # region = im.crop((delta/2, 0, xsize-delta/2, ysize)) + # region = im.crop((0, delta / 2, xsize, ysize - delta / 2)) # buffered = BytesIO() # region.save(buffered, format=im.format) with open(img, 'rb') as f: @@ -382,6 +458,16 @@ class Upload(UploadBase): f'&groupid={groupid}&vfea={vfea}' return self.__session.get(url=url).json() + def __enter__(self): + return self + + def __exit__(self, e_t, e_v, t_b): + self.close() + + def close(self): + """Closes all adapters and as such the session""" + self.__session.close() + @dataclass class Data: @@ -402,6 +488,7 @@ class Data: videos: list = field(default_factory=list) dtime: Any = None open_subtitle: InitVar[bool] = False + # interactive: int = 0 # no_reprint: int 1 # open_elec: int 1 diff --git a/requirements.txt b/requirements.txt index 0620721..fbb75b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ Pillow>=5.2.0 Requests>=2.20.0 PyYAML>=4.2b1 streamlink>=0.14.2 -git+https://github.com/zhangn1985/ykdl.git \ No newline at end of file +git+https://github.com/zhangn1985/ykdl.git +rsa>=4.6 \ No newline at end of file