From 0260fce84513f300a52c5ec4432f9a5f6e12c94d Mon Sep 17 00:00:00 2001
From: Jerry Yan <792602257@qq.com>
Date: Wed, 8 Apr 2020 12:24:23 +0800
Subject: [PATCH 1/4] =?UTF-8?q?=E4=BE=8B=E8=A1=8C=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 bilibili.py | 71 +++++++++++++++++++++++++----------------------------
 1 file changed, 33 insertions(+), 38 deletions(-)

diff --git a/bilibili.py b/bilibili.py
index 4d34055..c96ff33 100644
--- a/bilibili.py
+++ b/bilibili.py
@@ -49,8 +49,8 @@ class Bilibili:
         APPKEY = '4409e2ce8ffd12b8'
         ACTIONKEY = 'appkey'
         BUILD = 101800
-        DEVICE = 'android'
-        MOBI_APP = 'android'
+        DEVICE = 'android_tv_yst'
+        MOBI_APP = 'android_tv_yst'
         PLATFORM = 'android'
         APPSECRET = '59b43e04ad6965f34319062b478f83dd'
 
@@ -94,11 +94,16 @@ class Bilibili:
             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
+        def access_token_2_cookie(access_token):
+            r = self.session.get(
+                'https://passport.bilibili.com/api/login/sso?' + \
+                signed_body(
+                    'access_key={access_token}&appkey={appkey}&gourl=https%3A%2F%2Faccount.bilibili.com%2Faccount%2Fhome'
+                        .format(access_token=access_token, appkey=APPKEY),
+                ),
+                allow_redirects=False,
+            )
+            return r.cookies.get_dict(domain=".bilibili.com")
 
         self.session.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
         h, k = getkey()
@@ -112,9 +117,13 @@ class Bilibili:
         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))
+            'https://passport.snm0516.aisee.tv/api/tv/login',
+            signed_body(
+                'appkey={appkey}&build={build}&captcha=&channel=master&'
+                'guid=XYEBAA3E54D502E37BD606F0589A356902FCF&mobi_app={mobi_app}&'
+                'password={password}&platform={platform}&token=5598158bcd8511e2&ts=0&username={username}'
+                    .format(appkey=APPKEY, build=BUILD, platform=PLATFORM, mobi_app=MOBI_APP, username=user,
+                            password=pwd)),
         )
         try:
             json = r.json()
@@ -123,32 +132,17 @@ class Bilibili:
 
         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()
+            raise Exception('TODO: login with captcha')
 
         if json['code'] != 0:
             return r.text
 
-        ls = []
-        for item in json['data']['cookie_info']['cookies']:
-            ls.append(item['name'] + '=' + item['value'])
-        cookie = '; '.join(ls)
+        access_token = json['data']['token_info']['access_token']
+        cookie_dict = access_token_2_cookie(access_token)
+        cookie = '; '.join(
+            '%s=%s' % (k, v)
+            for k, v in cookie_dict.items()
+        )
         self.session.headers["cookie"] = cookie
 
         self.csrf = re.search('bili_jct=(.*?);', cookie).group(1)
@@ -244,7 +238,8 @@ class Bilibili:
                 chunks_num = math.ceil(filesize / chunk_size)
                 chunks_index = 0
                 chunks_data = f.read(chunk_size)
-                Common.modifyLastUploadStatus("Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
+                Common.modifyLastUploadStatus(
+                    "Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
                 while True:
                     _d = datetime.now()
                     if not chunks_data:
@@ -268,8 +263,9 @@ class Bilibili:
                         continue
                     chunks_data = f.read(chunk_size)
                     chunks_index += 1  # start with 0
-                    Common.modifyLastUploadStatus("Uploading >{}< @ {:.2f}%".format(filepath, 100.0*chunks_index/chunks_num))
-                    if (datetime.now()-_d).seconds < 2:
+                    Common.modifyLastUploadStatus(
+                        "Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
+                    if (datetime.now() - _d).seconds < 2:
                         sleep(1)
 
                 # NOT DELETE! Refer to https://github.com/comwrg/bilibiliupload/issues/15#issuecomment-424379769
@@ -287,10 +283,9 @@ class Bilibili:
                                 'title': part.title,
                                 'desc': part.desc})
             Common.modifyLastUploadStatus("Upload >{}< Finished".format(filepath))
-            __f = open("uploaded.json","w")
+            __f = open("uploaded.json", "w")
             JSON.dump(self.videos, __f)
 
-
     def finishUpload(self,
                      title,
                      tid,
@@ -356,7 +351,7 @@ class Bilibili:
     def clear(self):
         self.files.clear()
         self.videos.clear()
-        if(os.path.exists("uploaded.json")):
+        if (os.path.exists("uploaded.json")):
             os.remove("uploaded.json")
 
     def appendUpload(self,

From 866b789a3d0f114af2dd70f60258f1acfe85a3ba Mon Sep 17 00:00:00 2001
From: Jerry Yan <792602257@qq.com>
Date: Wed, 8 Apr 2020 15:55:58 +0800
Subject: [PATCH 2/4] =?UTF-8?q?=E4=BE=8B=E8=A1=8C=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 bilibili.py | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/bilibili.py b/bilibili.py
index c96ff33..5308abe 100644
--- a/bilibili.py
+++ b/bilibili.py
@@ -44,7 +44,7 @@ class Bilibili:
         :param pwd: password
         :type pwd: str
         :return: if success return True
-                 else return msg json
+                 else raise Exception
         """
         APPKEY = '4409e2ce8ffd12b8'
         ACTIONKEY = 'appkey'
@@ -122,20 +122,16 @@ class Bilibili:
                 'appkey={appkey}&build={build}&captcha=&channel=master&'
                 'guid=XYEBAA3E54D502E37BD606F0589A356902FCF&mobi_app={mobi_app}&'
                 'password={password}&platform={platform}&token=5598158bcd8511e2&ts=0&username={username}'
-                    .format(appkey=APPKEY, build=BUILD, platform=PLATFORM, mobi_app=MOBI_APP, username=user,
-                            password=pwd)),
+                .format(appkey=APPKEY, build=BUILD, platform=PLATFORM, mobi_app=MOBI_APP, username=user, password=pwd)),
         )
-        try:
-            json = r.json()
-        except:
-            return r.text
+        json = r.json()
 
         if json['code'] == -105:
             # need captcha
             raise Exception('TODO: login with captcha')
 
         if json['code'] != 0:
-            return r.text
+            raise Exception(r.text)
 
         access_token = json['data']['token_info']['access_token']
         cookie_dict = access_token_2_cookie(access_token)
@@ -144,14 +140,12 @@ class Bilibili:
             for k, v in cookie_dict.items()
         )
         self.session.headers["cookie"] = cookie
-
-        self.csrf = re.search('bili_jct=(.*?);', cookie).group(1)
-        self.mid = re.search('DedeUserID=(.*?);', cookie).group(1)
+        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,

From 9e9cb5883872d8a3ebf3cb18ed5f1573220cff97 Mon Sep 17 00:00:00 2001
From: Jerry Yan <792602257@qq.com>
Date: Wed, 8 Apr 2020 16:11:51 +0800
Subject: [PATCH 3/4] =?UTF-8?q?=E4=BE=8B=E8=A1=8C=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 bilibili.py | 81 ++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 56 insertions(+), 25 deletions(-)

diff --git a/bilibili.py b/bilibili.py
index 5308abe..8d27040 100644
--- a/bilibili.py
+++ b/bilibili.py
@@ -5,6 +5,10 @@ import re
 import json as JSON
 from datetime import datetime
 from time import sleep
+
+from requests.adapters import HTTPAdapter
+from urllib3 import Retry
+
 import Common
 import rsa
 import math
@@ -146,6 +150,7 @@ class Bilibili:
         self.session.headers['Referer'] = 'https://space.bilibili.com/{mid}/#!/'.format(mid=self.mid)
 
         return True
+
     def upload(self,
                parts,
                title,
@@ -180,8 +185,9 @@ class Bilibili:
         self.finishUpload(title, tid, tag, desc, source, cover, no_reprint)
         self.clear()
 
-    def preUpload(self, parts):
+    def preUpload(self, parts, max_retry=5):
         """
+        :param max_retry:
         :param parts: e.g. VideoPart('part path', 'part title', 'part desc'), or [VideoPart(...), VideoPart(...)]
         :type parts: VideoPart or list<VideoPart>
         """
@@ -189,6 +195,16 @@ class Bilibili:
         if not isinstance(parts, list):
             parts = [parts]
 
+        # retry by status
+        retries = Retry(
+            total=max_retry,
+            backoff_factor=1,
+            status_forcelist=(504, ),
+        )
+        self.session.mount('https://', HTTPAdapter(max_retries=retries))
+        self.session.mount('http://', HTTPAdapter(max_retries=retries))
+        #
+
         for part in parts:
             filepath = part.path
             filename = os.path.basename(filepath)
@@ -197,7 +213,7 @@ class Bilibili:
             self.files.append(part)
             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))
+                                 .format(name=parse.quote_plus(filename), size=filesize))
             """return example
             {
                 "upos_uri": "upos://ugc/i181012ws18x52mti3gg0h33chn3tyhp.mp4",
@@ -223,8 +239,7 @@ class Bilibili:
             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://', '')))
+            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']
@@ -235,32 +250,47 @@ class Bilibili:
                 Common.modifyLastUploadStatus(
                     "Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
                 while True:
-                    _d = datetime.now()
                     if not chunks_data:
                         break
-                    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,
-                                         )
-                    if r.status_code != 200:
+
+                    def upload_chunk():
+                        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,
+                                             )
+                        return r
+
+                    def retry_upload_chunk():
+                        """return :class:`Response` if upload success, else return None."""
+                        for i in range(max_retry):
+                            r = upload_chunk()
+                            if r.status_code == 200:
+                                return r
+                            Common.modifyLastUploadStatus(
+                                "Uploading >{}< @ {:.2f}% RETRY[{}]".format(filepath, 100.0 * chunks_index / chunks_num, max_retry))
+                        return None
+
+                    r = retry_upload_chunk()
+                    if r:
+                        Common.modifyLastUploadStatus(
+                            "Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
+                    else:
+                        Common.modifyLastUploadStatus(
+                            "Uploading >{}< FAILED @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
                         continue
                     chunks_data = f.read(chunk_size)
                     chunks_index += 1  # start with 0
-                    Common.modifyLastUploadStatus(
-                        "Uploading >{}< @ {:.2f}%".format(filepath, 100.0 * chunks_index / chunks_num))
-                    if (datetime.now() - _d).seconds < 2:
-                        sleep(1)
 
                 # NOT DELETE! Refer to https://github.com/comwrg/bilibiliupload/issues/15#issuecomment-424379769
                 self.session.post('https:{endpoint}/{upos_uri}?'
@@ -279,6 +309,7 @@ class Bilibili:
             Common.modifyLastUploadStatus("Upload >{}< Finished".format(filepath))
             __f = open("uploaded.json", "w")
             JSON.dump(self.videos, __f)
+            __f.close()
 
     def finishUpload(self,
                      title,

From 47cccb4aa8ef128f9369e9e120460ade169a1498 Mon Sep 17 00:00:00 2001
From: Jerry Yan <792602257@qq.com>
Date: Wed, 8 Apr 2020 16:12:11 +0800
Subject: [PATCH 4/4] =?UTF-8?q?=E4=BE=8B=E8=A1=8C=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 bilibili.py | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/bilibili.py b/bilibili.py
index 8d27040..25de102 100644
--- a/bilibili.py
+++ b/bilibili.py
@@ -3,12 +3,6 @@
 import os
 import re
 import json as JSON
-from datetime import datetime
-from time import sleep
-
-from requests.adapters import HTTPAdapter
-from urllib3 import Retry
-
 import Common
 import rsa
 import math
@@ -16,6 +10,8 @@ import base64
 import hashlib
 import requests
 from urllib import parse
+from requests.adapters import HTTPAdapter
+from urllib3 import Retry
 
 
 class VideoPart: