bilibili.py 8.2 KB
Newer Older
H
Hsury 已提交
1
#!/usr/bin/env python3.7
H
Hsury 已提交
2 3 4 5 6 7 8 9
# -*- coding: utf-8 -*-

import base64
import hashlib
import random
import requests
import rsa
import time
W
wizardforcel 已提交
10
import re
H
Hsury 已提交
11
from urllib import parse
W
wizardforcel 已提交
12
from BiliDriveEx.util import *
H
Hsury 已提交
13 14 15 16

class Bilibili:
    app_key = "1d8b6e7d45233436"

W
wizardforcel 已提交
17
    default_hdrs = {'User-Agent': "Mozilla/5.0 BiliDroid/5.51.1 (bbcallen@gmail.com)"}
W
wizardforcel 已提交
18 19 20 21
    
    default_url = lambda self, sha1: f"http://i0.hdslb.com/bfs/album/{sha1}.png"
    meta_string = lambda self, url: ("bdex://" + re.findall(r"[a-fA-F0-9]{40}", url)[0]) if re.match(r"^http(s?)://i0.hdslb.com/bfs/album/[a-fA-F0-9]{40}.png$", url) else url
    
W
wizardforcel 已提交
22
    get_cookies = lambda self: self.cookies
W
wizardforcel 已提交
23
    
H
Hsury 已提交
24
    def __init__(self):
W
wizardforcel 已提交
25
        self.cookies = {}
W
wizardforcel 已提交
26
        self.load_cookies()
H
Hsury 已提交
27

W
wizardforcel 已提交
28 29 30 31 32 33 34 35 36
    def set_cookies(self, cookie_str):
        self.cookies = {}
        for kv in cookie_str.split('; '):
            kv = kv.split('=')
            if len(kv) != 2: continue
            self.cookies[kv[0]] = kv[1]
        self.save_cookies()
            
        
H
Hsury 已提交
37 38 39
    def _solve_captcha(self, image):
        url = "https://bili.dev:2233/captcha"
        payload = {'image': base64.b64encode(image).decode("utf-8")}
W
wizardforcel 已提交
40 41 42 43 44 45 46 47
        try:
            j = request_retry("post", url, 
                headers=Bilibili.default_hdrs,
                json=payload
            ).json()
        except:
            return None
        return j['message'] if j['code'] == 0 else None
H
Hsury 已提交
48 49 50 51 52 53 54 55

    @staticmethod
    def calc_sign(param):
        salt = "560c52ccd288fed045859ed18bffd973"
        sign_hash = hashlib.md5()
        sign_hash.update(f"{param}{salt}".encode())
        return sign_hash.hexdigest()

W
wizardforcel 已提交
56
        
W
wizardforcel 已提交
57
    def _get_key(self):
W
wizardforcel 已提交
58 59 60 61 62
        url = f"https://passport.bilibili.com/api/oauth2/getKey"
        payload = {
            'appkey': Bilibili.app_key,
            'sign': self.calc_sign(f"appkey={Bilibili.app_key}"),
        }
W
wizardforcel 已提交
63 64 65 66 67 68 69
        try:
            r = request_retry("post", url, data=payload, 
                headers=Bilibili.default_hdrs,
                cookies=self.cookies, retry=999999
            )
        except:
            return None
W
wizardforcel 已提交
70 71
        for k, v in r.cookies.items(): self.cookies[k] = v
        j = r.json()
W
wizardforcel 已提交
72
        if j['code'] == 0:
W
wizardforcel 已提交
73
            return {
W
wizardforcel 已提交
74 75
                'key_hash': j['data']['hash'],
                'pub_key': rsa.PublicKey.load_pkcs1_openssl_pem(j['data']['key'].encode()),
W
wizardforcel 已提交
76
            }
W
wizardforcel 已提交
77 78
        else:
            return None
W
wizardforcel 已提交
79
        
W
wizardforcel 已提交
80 81 82
    def _login_once(self, username, password, captcha=None):
        key = self._get_key()
        if not key: return {'code': 114514, 'message': 'key 获取失败'}
W
wizardforcel 已提交
83
        key_hash, pub_key = key['key_hash'], key['pub_key']
W
wizardforcel 已提交
84
        
W
wizardforcel 已提交
85 86
        username = parse.quote_plus(username)
        password = parse.quote_plus(base64.b64encode(rsa.encrypt(f'{key_hash}{password}'.encode(), pub_key)))
W
wizardforcel 已提交
87
        
W
wizardforcel 已提交
88 89 90 91 92
        url = f"https://passport.bilibili.com/api/v2/oauth2/login"
        param = f"appkey={Bilibili.app_key}"
        if captcha: param += f'&captcha={captcha}'
        param += f"&password={password}&username={username}"
        payload = f"{param}&sign={self.calc_sign(param)}"
W
wizardforcel 已提交
93 94
        headers = Bilibili.default_hdrs.copy()
        headers.update({'Content-type': "application/x-www-form-urlencoded"})
W
wizardforcel 已提交
95 96 97 98 99 100 101 102
        
        try:
            j = request_retry("POST", url, data=payload, 
                headers=headers, 
                cookies=self.cookies
            ).json()
        except Exception as ex:
            return {'code': 114514, 'message': str(ex)}
W
wizardforcel 已提交
103
        return j
W
wizardforcel 已提交
104
        
W
wizardforcel 已提交
105
    def _get_captcha(self):
W
wizardforcel 已提交
106
        url = f"https://passport.bilibili.com/captcha"
W
wizardforcel 已提交
107 108 109 110 111 112 113 114
        try:
            img = request_retry('GET', url, 
                headers=Bilibili.default_hdrs, 
                cookies=self.cookies
            ).content
        except:
            return None
        return img
W
wizardforcel 已提交
115
        
H
Hsury 已提交
116
    # 登录
H
Hsury 已提交
117 118
    def login(self, username, password):

W
wizardforcel 已提交
119
        captcha = None
H
Hsury 已提交
120
        while True:
W
wizardforcel 已提交
121
            j = self._login_once(username, password, captcha)
W
wizardforcel 已提交
122
            
W
wizardforcel 已提交
123
            if 'code' not in j:
W
wizardforcel 已提交
124 125 126 127
                log(f"当前IP登录过于频繁, 1分钟后重试")
                time.sleep(60)
                continue
                
W
wizardforcel 已提交
128 129 130 131 132 133 134
            if j['code'] == -105:
                img = self._get_captcha()
                if not img:
                    log(f"验证码获取失败")
                    time.sleep(1)
                    continue
                captcha = self._solve_captcha(img)
W
wizardforcel 已提交
135 136
                if captcha:
                    log(f"登录验证码识别结果: {captcha}")
H
Hsury 已提交
137
                else:
W
wizardforcel 已提交
138 139 140 141
                    log(f"登录验证码识别服务暂时不可用, 10秒后重试")
                    time.sleep(10)
                continue
                
W
wizardforcel 已提交
142
            if j['code'] == -449:
W
wizardforcel 已提交
143 144 145
                time.sleep(1)
                continue
            
W
wizardforcel 已提交
146
            if j['code'] == 0 and j['data']['status'] == 0:
W
wizardforcel 已提交
147
                self.cookies = {}
W
wizardforcel 已提交
148
                for cookie in j['data']['cookie_info']['cookies']:
W
wizardforcel 已提交
149
                    self.cookies[cookie['name']] = cookie['value']
W
wizardforcel 已提交
150
                log("登录成功")
W
wizardforcel 已提交
151
                self.save_cookies()
W
wizardforcel 已提交
152 153
                return True
            
W
wizardforcel 已提交
154
            log(f"登录失败 {j['message']}")
W
wizardforcel 已提交
155 156
            return False

H
Hsury 已提交
157 158

    # 获取用户信息
W
wizardforcel 已提交
159
    def get_user_info(self, fmt=True):
W
wizardforcel 已提交
160
        url = f"https://api.bilibili.com/x/space/myinfo"
W
wizardforcel 已提交
161 162
        headers = Bilibili.default_hdrs.copy()
        headers.update({
W
wizardforcel 已提交
163
            'Referer': f"https://space.bilibili.com",
W
wizardforcel 已提交
164
        })
W
wizardforcel 已提交
165
        
W
wizardforcel 已提交
166 167 168 169 170 171
        try:
            j = request_retry("get", url, 
                headers=headers, 
                cookies=self.cookies
            ).json()
        except:
W
wizardforcel 已提交
172
            return
W
wizardforcel 已提交
173
        
W
wizardforcel 已提交
174 175
        if j['code'] != 0: return
        
W
wizardforcel 已提交
176 177 178 179 180 181 182 183 184 185 186 187
        info = {
            'ban': False,
            'coins': 0,
            'experience': {
                'current': 0,
                'next': 0,
            },
            'face': "",
            'level': 0,
            'nickname': "",
            'uid': 0,
        }
W
wizardforcel 已提交
188 189 190 191 192 193 194 195
        info['ban'] = bool(j['data']['silence'])
        info['coins'] = j['data']['coins']
        info['experience']['current'] = j['data']['level_exp']['current_exp']
        info['experience']['next'] = j['data']['level_exp']['next_exp']
        info['face'] = j['data']['face']
        info['level'] = j['data']['level']
        info['nickname'] = j['data']['name']
        info['uid'] = j['data']['mid']
W
wizardforcel 已提交
196 197 198 199 200
        
        if fmt:
            return f"{info['nickname']}(UID={info['uid']}), Lv.{info['level']}({info['experience']['current']}/{info['experience']['next']}), 拥有{info['coins']}枚硬币, 账号{'状态正常' if not info['ban'] else '被封禁'}"
        else:
            return info
W
wizardforcel 已提交
201
            
W
wizardforcel 已提交
202 203 204 205 206 207 208 209 210 211
    def save_cookies(self):
        with open(os.path.join(bundle_dir, "cookies.json"), "w", encoding="utf-8") as f:
            f.write(json.dumps(self.cookies, ensure_ascii=False, indent=2))
            
    def load_cookies(self):
        try:
            with open(os.path.join(bundle_dir, "cookies.json"), "r", encoding="utf-8") as f:
                self.cookies = json.loads(f.read())
        except:
            pass
W
wizardforcel 已提交
212 213
            
    def exist(self, sha1):
W
wizardforcel 已提交
214
        url = self.default_url(sha1)
W
wizardforcel 已提交
215
        try:
W
wizardforcel 已提交
216
            r = request_retry('HEAD', url, headers=Bilibili.default_hdrs)
W
wizardforcel 已提交
217
        except:
W
wizardforcel 已提交
218
            return
W
wizardforcel 已提交
219
        return url if r.status_code == 200 else None
W
wizardforcel 已提交
220
        
W
wizardforcel 已提交
221
    def image_upload(self, data):
W
wizardforcel 已提交
222 223 224
        sha1 = calc_sha1(data)
        url = self.exist(sha1)
        if url: return {'code': 0, 'data': {'image_url': url}}
W
wizardforcel 已提交
225
        
W
wizardforcel 已提交
226
        url = "https://api.vc.bilibili.com/api/v1/drawImage/upload"
W
wizardforcel 已提交
227 228
        headers = Bilibili.default_hdrs.copy()
        headers.update({
W
wizardforcel 已提交
229 230
            'Origin': "https://t.bilibili.com",
            'Referer': "https://t.bilibili.com/",
W
wizardforcel 已提交
231
        })
W
wizardforcel 已提交
232 233 234 235 236 237 238 239
        files = {
            'file_up': (f"{int(time.time() * 1000)}.png", data),
        }
        data = {
            'biz': "draw",
            'category': "daily",
        }
        try:
W
wizardforcel 已提交
240
            r = requests.post(url, data=data, 
W
wizardforcel 已提交
241 242 243
                headers=headers, 
                cookies=self.cookies, 
                files=files, timeout=300
W
wizardforcel 已提交
244 245 246 247 248 249 250
            )
        except Exception as ex:
            return {'code': 114514, 'message': str(ex)}
            
        if r.status_code != 200:
            return {'code': r.status_code, 'message': r.text}
        return r.json()