From 746f993a8d87f61d09e24f7582b98390d79068d5 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 23 Jan 2019 14:50:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=8A=A0=E5=85=A5=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=8A=95=E9=80=92=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.py | 17 ++- bilibili.py | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 376 insertions(+), 5 deletions(-) create mode 100644 bilibili.py diff --git a/api.py b/api.py index 5c9407a..5f28e4a 100644 --- a/api.py +++ b/api.py @@ -9,7 +9,8 @@ import time s = requests.Session() -DEBUG:bool = False +DEBUG: bool = False + class XiGuaLiveApi: isLive: bool = False @@ -21,6 +22,7 @@ class XiGuaLiveApi: roomPopularity: int = 0 roomMember: int = 0 _cursor = "" + playlist: str = None def __init__(self, room: int): self.room = room @@ -38,7 +40,6 @@ class XiGuaLiveApi: if "popularity" in json["data"]: self.roomPopularity = json["data"]["popularity"] - def apiChangedError(self, msg: str, *args): print(msg) print(*args) @@ -56,7 +57,7 @@ class XiGuaLiveApi: def onChat(self, chat: Chat): print(chat) - def onEnter(self, msg:MemberMsg): + def onEnter(self, msg: MemberMsg): print("提示 : ", msg) def onSubscribe(self, user: User): @@ -95,6 +96,12 @@ class XiGuaLiveApi: self.roomTitle = d["data"]["title"] self.roomID = d["data"]["id"] self._updateRoomInfo(d) + if "playInfo" not in d["data"] or "Main" not in d["data"]["playInfo"]: + if self.playlist is None: + self.apiChangedError("无法获取直播链接") + self.playlist = False + else: + self.playlist = d["data"]["playInfo"]["Main"]["1"]["Url"]["HlsUrl"] if "status" in d["data"] and d["data"]["status"] == 2: self.isLive = True else: @@ -117,9 +124,9 @@ class XiGuaLiveApi: else: self._cursor = d["data"]["Extra"]["Cursor"] if DEBUG: - print("Cursor",self._cursor) + print("Cursor", self._cursor) if "LiveMsgs" not in d["data"]: - return + self.updRoomInfo() for i in d['data']['LiveMsgs']: if DEBUG: print(i) diff --git a/bilibili.py b/bilibili.py new file mode 100644 index 0000000..10524d6 --- /dev/null +++ b/bilibili.py @@ -0,0 +1,364 @@ +# coding=utf-8 +""" +:author: comwrg +:license: MIT +:time: 2017/06/09 +""" + +import os +import re +import rsa +import math +import base64 +import hashlib +import requests +from urllib import parse + + +class VideoPart: + def __init__(self, path, title='', desc=''): + self.path = path + self.title = title + self.desc = desc + +class Bilibili: + def __init__(self, cookie=None): + self.session = requests.session() + if cookie: + self.session.headers["cookie"] = cookie + self.csrf = re.search('bili_jct=(.*?);', cookie).group(1) + self.mid = re.search('DedeUserID=(.*?);', cookie).group(1) + self.session.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01' + self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid) + # session.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' + # session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' + + def login(self, user, pwd): + """ + + :param user: username + :type user: str + :param pwd: password + :type pwd: str + :return: if success return True + else return msg json + """ + APPKEY = '1d8b6e7d45233436' + ACTIONKEY = 'appkey' + BUILD = 520001 + DEVICE = 'android' + MOBI_APP = 'android' + PLATFORM = 'android' + APPSECRET = '560c52ccd288fed045859ed18bffd973' + + def md5(s): + h = hashlib.md5() + h.update(s.encode('utf-8')) + return h.hexdigest() + + def sign(s): + """ + + :return: return sign + """ + return md5(s + APPSECRET) + + def signed_body(body): + """ + + :return: body which be added sign + """ + if isinstance(body, str): + return body + '&sign=' + sign(body) + elif isinstance(body, dict): + ls = [] + for k, v in body.items(): + ls.append(k + '=' + v) + body['sign'] = sign('&'.join(ls)) + return body + + def getkey(): + """ + + :return: hash, key + """ + r = self.session.post( + 'https://passport.bilibili.com/api/oauth2/getKey', + signed_body({'appkey': APPKEY}), + ) + # {"ts":1544152439,"code":0,"data":{"hash":"99c7573759582e0b","key":"-----BEGIN PUBLIC----- -----END PUBLIC KEY-----\n"}} + json = r.json() + data = json['data'] + return data['hash'], data['key'] + + def cnn_captcha(img): + url = "http://47.95.255.188:5000/code" + data = {"image": img} + r = requests.post(url, data=data) + return r.text + + self.session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' + h, k = getkey() + pwd = base64.b64encode( + rsa.encrypt( + (h + pwd).encode('utf-8'), + rsa.PublicKey.load_pkcs1_openssl_pem(k.encode()) + ) + ) + user = parse.quote_plus(user) + pwd = parse.quote_plus(pwd) + + r = self.session.post( + 'https://passport.bilibili.com/api/v2/oauth2/login', + signed_body('appkey={appkey}&password={password}&username={username}' + .format(appkey=APPKEY, username=user, password=pwd)) + ) + try: + json = r.json() + except: + return r.text + + if json['code'] == -105: + # need captcha + self.session.headers['cookie'] = 'sid=xxxxxxxx' + r = self.session.get('https://passport.bilibili.com/captcha') + captcha = cnn_captcha(base64.b64encode(r.content)) + r = self.session.post( + 'https://passport.bilibili.com/api/v2/oauth2/login', + signed_body('actionKey={actionKey}&appkey={appkey}&build={build}&captcha={captcha}&device={device}' + '&mobi_app={mobi_app}&password={password}&platform={platform}&username={username}' + .format(actionKey=ACTIONKEY, + appkey=APPKEY, + build=BUILD, + captcha=captcha, + device=DEVICE, + mobi_app=MOBI_APP, + password=pwd, + platform=PLATFORM, + username=user)), + ) + json = r.json() + + + if json['code'] is not 0: + return r.text + + ls = [] + for item in json['data']['cookie_info']['cookies']: + ls.append(item['name'] + '=' + item['value']) + cookie = '; '.join(ls) + self.session.headers["cookie"] = cookie + + self.csrf = re.search('bili_jct=(.*?);', cookie).group(1) + self.mid = re.search('DedeUserID=(.*?);', cookie).group(1) + self.session.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01' + self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid) + + return True + + + def upload(self, + parts, + title, + tid, + tag, + desc, + source='', + cover='', + no_reprint=1, + ): + """ + + :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)] + :type parts: VideoPart or list + :param title: video's title + :type title: str + :param tid: video type, see: https://member.bilibili.com/x/web/archive/pre + or https://github.com/uupers/BiliSpider/wiki/%E8%A7%86%E9%A2%91%E5%88%86%E5%8C%BA%E5%AF%B9%E5%BA%94%E8%A1%A8 + :type tid: int + :param tag: video's tag + :type tag: list + :param desc: video's description + :type desc: str + :param source: (optional) 转载地址 + :type source: str + :param cover: (optional) cover's URL, use method *cover_up* to get + :type cover: str + :param no_reprint: (optional) 0=可以转载, 1=禁止转载(default) + :type no_reprint: int + """ + + self.session.headers['Content-Type'] = 'application/json; charset=utf-8' + if not isinstance(parts, list): + parts = [parts] + + videos = [] + for part in parts: + filepath = part.path + filename = os.path.basename(filepath) + filesize = os.path.getsize(filepath) + r = self.session.get('https://member.bilibili.com/preupload?' + 'os=upos&upcdn=ws&name={name}&size={size}&r=upos&profile=ugcupos%2Fyb&ssl=0' + .format(name=filename, size=filesize)) + """return example + { + "upos_uri": "upos://ugc/i181012ws18x52mti3gg0h33chn3tyhp.mp4", + "biz_id": 58993125, + "endpoint": "//upos-hz-upcdnws.acgvideo.com", + "endpoints": [ + "//upos-hz-upcdnws.acgvideo.com", + "//upos-hz-upcdntx.acgvideo.com" + ], + "chunk_retry_delay": 3, + "chunk_retry": 200, + "chunk_size": 4194304, + "threads": 2, + "timeout": 900, + "auth": "os=upos&cdn=upcdnws&uid=&net_state=4&device=&build=&os_version=&ak=×tamp=&sign=", + "OK": 1 + } + """ + json = r.json() + upos_uri = json['upos_uri'] + endpoint = json['endpoint'] + auth = json['auth'] + biz_id = json['biz_id'] + chunk_size = json['chunk_size'] + self.session.headers['X-Upos-Auth'] = auth # add auth header + r = self.session.post('https:{}/{}?uploads&output=json'.format(endpoint, upos_uri.replace('upos://', ''))) + # {"upload_id":"72eb747b9650b8c7995fdb0efbdc2bb6","key":"\/i181012ws2wg1tb7tjzswk2voxrwlk1u.mp4","OK":1,"bucket":"ugc"} + json = r.json() + upload_id = json['upload_id'] + + with open(filepath, 'rb') as f: + chunks_num = math.ceil(filesize / chunk_size) + chunks_index = -1 + while True: + chunks_data = f.read(chunk_size) + if not chunks_data: + break + chunks_index += 1 # start with 0 + r = self.session.put('https:{endpoint}/{upos_uri}?' + 'partNumber={part_number}&uploadId={upload_id}&chunk={chunk}&chunks={chunks}&size={size}&start={start}&end={end}&total={total}' + .format(endpoint=endpoint, + upos_uri=upos_uri.replace('upos://', ''), + part_number=chunks_index+1, # starts with 1 + upload_id=upload_id, + chunk=chunks_index, + chunks=chunks_num, + size=len(chunks_data), + start=chunks_index * chunk_size, + end=chunks_index * chunk_size + len(chunks_data), + total=filesize, + ), + chunks_data, + ) + print('{}/{}'.format(chunks_index, chunks_num), r.text) + + # NOT DELETE! Refer to https://github.com/comwrg/bilibiliupload/issues/15#issuecomment-424379769 + self.session.post('https:{endpoint}/{upos_uri}?' + 'output=json&name={name}&profile=ugcupos%2Fyb&uploadId={upload_id}&biz_id={biz_id}' + .format(endpoint=endpoint, + upos_uri=upos_uri.replace('upos://', ''), + name=filename, + upload_id=upload_id, + biz_id=biz_id, + ), + {"parts": [{"partNumber": i, "eTag": "etag"} for i in range(1, chunks_num+1)]}, + ) + + videos.append({'filename': upos_uri.replace('upos://ugc/', '').split('.')[0], + 'title' : part.title, + 'desc' : part.desc}) + + # if source is empty, copyright=1, else copyright=2 + copyright = 2 if source else 1 + r = self.session.post('https://member.bilibili.com/x/vu/web/add?csrf=' + self.csrf, + json={ + "copyright" : copyright, + "source" : source, + "title" : title, + "tid" : tid, + "tag" : ','.join(tag), + "no_reprint": no_reprint, + "desc" : desc, + "cover" : cover, + "mission_id": 0, + "order_id" : 0, + "videos" : videos} + ) + print(r.text) + + def addChannel(self, name, intro=''): + """ + + :param name: channel's name + :type name: str + :param intro: channel's introduction + :type intro: str + """ + r = self.session.post( + url='https://space.bilibili.com/ajax/channel/addChannel', + data={ + 'name' : name, + 'intro': intro, + 'aids' : '', + 'csrf' : self.csrf, + }, + # name=123&intro=123&aids=&csrf=565d7ed17cef2cc8ad054210c4e64324&_=1497077610768 + + ) + # return + # {"status":true,"data":{"cid":"15812"}} + print(r.json()) + + def channel_addVideo(self, cid, aids): + """ + + :param cid: channel's id + :type cid: int + :param aids: videos' id + :type aids: list + """ + + r = self.session.post( + url='https://space.bilibili.com/ajax/channel/addVideo', + data={ + 'aids': '%2C'.join(aids), + 'cid' : cid, + 'csrf': self.csrf + } + # aids=9953555%2C9872953&cid=15814&csrf=565d7ed17cef2cc8ad054210c4e64324&_=1497079332679 + ) + print(r.json()) + + def cover_up(self, img): + """ + + :param img: img path or stream + :type img: str or BufferedReader + :return: img URL + """ + + if isinstance(img, str): + f = open(img, 'rb') + else: + f = img + r = self.session.post( + url='https://member.bilibili.com/x/vu/web/cover/up', + data={ + 'cover': b'data:image/jpeg;base64,' + (base64.b64encode(f.read())), + 'csrf': self.csrf, + } + ) + # print(r.text) + # {"code":0,"data":{"url":"http://i0.hdslb.com/bfs/archive/67db4a6eae398c309244e74f6e85ae8d813bd7c9.jpg"},"message":"","ttl":1} + return r.json()['data']['url'] + + +def main(): + pass + + +if __name__ == '__main__': + main()