├── LICENSE ├── NFT.py ├── README.md └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Aristore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NFT.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import qrcode 4 | import time 5 | import requests 6 | from urllib.parse import urlencode 7 | from hashlib import md5 8 | from typing import Union 9 | from requests_toolbelt.multipart.encoder import MultipartEncoder 10 | import imghdr 11 | import os 12 | import sys 13 | print("哔哩哔哩钻石标自定义头像程序") 14 | print("作者:Aristore 转发请注明出处") 15 | print("制作不易 求关注") 16 | print("本程序仅供学习交流,请勿用于违规用途") 17 | path = os.path.dirname(os.path.realpath(sys.argv[0])) 18 | new_path = "/".join(path.split("\\")) 19 | if os.path.exists("{}/face.jpg".format(new_path)): 20 | FACE_PATH = "{}/face.jpg".format(new_path) 21 | elif os.path.exists("{}/face.png".format(new_path)): 22 | FACE_PATH = "{}/face.png".format(new_path) 23 | else: 24 | pass 25 | login_method = str(input("您希望以什么方式登录呢?(1:复制链接 2:扫码 3:自行输入数据)\n")) 26 | if login_method == "1" or login_method == "2": 27 | def tvsign(params, appkey='4409e2ce8ffd12b8', appsec='59b43e04ad6965f34319062b478f83dd'): 28 | params.update({'appkey': appkey}) 29 | params = dict(sorted(params.items())) 30 | query = urlencode(params) 31 | sign = md5((query+appsec).encode()).hexdigest() 32 | params.update({'sign':sign}) 33 | return params 34 | loginInfo = requests.post('https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code',params=tvsign({ 35 | 'local_id':'0', 36 | 'ts':int(time.time()) 37 | })).json() 38 | if login_method == "1": 39 | print("以下为登录链接:") 40 | print(loginInfo['data']['url']) 41 | elif login_method == "2": 42 | creat_qrcode = qrcode.make(loginInfo['data']['url']) 43 | with open('{}/qrcode.jpg'.format(new_path), 'wb') as f: 44 | creat_qrcode.save(f) 45 | print("已在本目录下生成登录二维码,用手机打开B站扫码登录") 46 | while True: 47 | pollInfo = requests.post('https://passport.bilibili.com/x/passport-tv-login/qrcode/poll',params=tvsign({ 48 | 'auth_code':loginInfo['data']['auth_code'], 49 | 'local_id':'0', 50 | 'ts':int(time.time()) 51 | })).json() 52 | if pollInfo['code'] == 0: 53 | loginData = pollInfo['data'] 54 | print("登录成功!") 55 | break 56 | elif pollInfo['code'] == -3: 57 | print('API校验密匙错误') 58 | raise 59 | elif pollInfo['code'] == -400: 60 | print('请求错误') 61 | raise 62 | elif pollInfo['code'] == 86038: 63 | print('二维码已失效') 64 | raise 65 | elif pollInfo['code'] == 86039: 66 | time.sleep(5) 67 | else: 68 | print('未知错误') 69 | raise 70 | UID = pollInfo['data']['mid'] 71 | ACCESS_KEY = pollInfo['data']['access_token'] 72 | print("UID:{}".format(UID)) 73 | print("ACCESS_KEY:{}".format(ACCESS_KEY)) 74 | elif login_method == "3": 75 | UID = input("UID:\n") 76 | ACCESS_KEY = input("ACCESS_KEY:\n") 77 | else: 78 | print("输入有误") 79 | card_type = str(input("请在选择您想使用数字周边的卡片种类后再下方输入其对应id后按下回车\n目前存在的数字周边:\nSNH48荣耀时刻数字写真集:1\n胶囊计划数字典藏集:4\n天官赐福动画2周年数字典藏:5\nA-AKB48TSH四周年数字集换卡:6\nB-AKB48TSH四周年数字集换卡:7\nC-AKB48TSH四周年数字集换卡:8\nD-AKB48TSH四周年数字集换卡:9\nE-AKB48TSH四周年数字集换卡:10\nF-AKB48TSH四周年数字集换卡:11\nG-AKB48TSH四周年数字集换卡:12\nH-AKB48TSH四周年数字集换卡:13\n三体动画数字周边:14\n2022百大UP主数字卡集:18\n")) 80 | class Crypto: 81 | APPKEY = '4409e2ce8ffd12b8' 82 | APPSECRET = '59b43e04ad6965f34319062b478f83dd' 83 | @staticmethod 84 | def md5(data: Union[str, bytes]) -> str: 85 | '''generates md5 hex dump of `str` or `bytes`''' 86 | if type(data) == str: 87 | return md5(data.encode()).hexdigest() 88 | return md5(data).hexdigest() 89 | @staticmethod 90 | def sign(data: Union[str, dict]) -> str: 91 | '''salted sign funtion for `dict`(converts to qs then parse) & `str`''' 92 | if isinstance(data, dict): 93 | _str = urlencode(data) 94 | elif type(data) != str: 95 | raise TypeError 96 | return Crypto.md5(_str + Crypto.APPSECRET) 97 | class SingableDict(dict): 98 | @property 99 | def sorted(self): 100 | '''returns a alphabetically sorted version of `self`''' 101 | return dict(sorted(self.items())) 102 | @property 103 | def signed(self): 104 | '''returns our sorted self with calculated `sign` as a new key-value pair at the end''' 105 | _sorted = self.sorted 106 | return {**_sorted, 'sign': Crypto.sign(_sorted)} 107 | def get_image_type(file_path): 108 | with open(file_path, 'rb') as f: 109 | data = f.read() 110 | return imghdr.what(None, data) 111 | def get_one_card_id(): 112 | url = "https://api.bilibili.com/x/vas/nftcard/cardlist" 113 | params = SingableDict( 114 | { 115 | "access_key": ACCESS_KEY, 116 | "act_id": card_type, 117 | "appkey": "4409e2ce8ffd12b8", 118 | "disable_rcmd": "0", 119 | "ruid": UID, 120 | "statistics": "{\"appId\":1,\"platform\":3,\"version\":\"7.9.0\",\"abtest\":\"\"}", 121 | "ts": int(time.time()), 122 | } 123 | ).signed 124 | response = requests.request("GET", url, params=params) 125 | data = response.json() 126 | print("请在下列卡片中选择一个(卡片名称:对应卡片id)") 127 | for round in data['data']['round_list']: 128 | for card in round['card_list']: 129 | if card['card_id_list']: 130 | print(card['card_name'], ":", card['card_id_list'][0]['card_id']) 131 | if data['data']['pre_list']: 132 | for pre in data['data']['pre_list']: 133 | if pre['card_id_list']: 134 | print(pre['card_name'], ":", pre['card_id_list'][0]['card_id']) 135 | choose_card_id = input("在下方输入其id后回车继续(如果没有就回车退出):\n") 136 | return choose_card_id 137 | def set_face(card_id): 138 | api = "https://api.bilibili.com/x/member/app/face/digitalKit/update" 139 | params = SingableDict( 140 | { 141 | "access_key": ACCESS_KEY, 142 | "appkey": "4409e2ce8ffd12b8", 143 | "build": "7090300", 144 | "c_locale": "zh_CN", 145 | "channel": "xiaomi", 146 | "disable_rcmd": "0", 147 | "mobi_app": "android", 148 | "platform": "android", 149 | "s_locale": "zh_CN", 150 | "statistics": "{\"appId\":1,\"platform\":3,\"version\":\"7.9.0\",\"abtest\":\"\"}", 151 | "ts": int(time.time()), 152 | } 153 | ).signed 154 | m = MultipartEncoder( 155 | fields={ 156 | 'digital_kit_id': str(card_id), 157 | 'face': ('face', open(FACE_PATH, 'rb'), 'application/octet-stream'), 158 | } 159 | ) 160 | headers = { 161 | "Content-Type": m.content_type, 162 | } 163 | response = requests.request("POST", api, data=m, headers=headers, params=params) 164 | if response.json()['code'] != 0: 165 | print(response.json()) 166 | return 167 | print('设置头像成功, 请等待审核') 168 | def main(): 169 | card_id = get_one_card_id() 170 | if not card_id: 171 | return 172 | set_face(card_id) 173 | if __name__ == '__main__': 174 | main() 175 | input("按下回车键结束") 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bilibili修改小钻石程序 2 | 3 | 本程序不会再进行维护更新,此为最终版本 4 | 5 | 配合教程视频[BV1WD4y1V7SE](https://www.bilibili.com/video/BV1WD4y1V7SE/)食用更佳,欢迎关注 6 | 7 | # 声明 8 | 9 | **本程序仅供学习使用,请勿用于违法违规用途,使用后出现任何问题需自行承担!** 10 | 11 | **代码开源免费,拿我项目收费的都是骗子** 12 | 13 | **本项目仅在[GitHub](https://github.com/aristorechina/NFT_auto)和[Gitee](https://gitee.com/aristore/NFT_auto)开源发布,要是从其他渠道下载此程序请自行甄别程序的安全性与可行性** 14 | 15 | **打包好的程序目前可能不支持低于或等于Windows7系统** 16 | 17 | # 你需要准备的 18 | 19 | 1. 任意一个数字周边卡片 20 | 2. 一张`正方形`的,名为`face`,且格式为`jpg`或`png`的头像图片,图片大小需小于`2M` 21 | 3. 一个可以正常使用的bilibili账号 22 | 4. 一台电脑(release下打包的程序仅支持`Windows`系统) 23 | 5. (可选)自行下载程序运行 24 | 25 | # 操作步骤 26 | 27 | 1. 前往[Releases](https://github.com/aristorechina/NFT_auto/releases/tag/The_final_release)下载打包好的程序后运行`NFT.exe` 28 | 2. 将用于更换的头像文件置于程序所在目录下(注意,头像名称应为`face`,格式应为`jpg`或`png`,程序默认优先识别`face.jpg`) 29 | 3. 根据程序指引选择登录方式 30 | - 若选择`复制链接`登录,请将程序输出的链接复制下来打开登录 31 | - 若选择`扫码`登录,程序将会在程序所在目录下生成一个二维码,请打开手机B站扫码登录 32 | - 若选择`自行输入数据`,请按照程序指引输入您的UID和ACCESS_KEY登录 33 | 4. 输入你所拥有的且你想更改的数字藏品项目id(例如:如果你拥有的数字周边是`三体动画数字周边`,那就输入`14`)后按下回车键继续 34 | - SNH48荣耀时刻数字写真集:1 35 | - 胶囊计划数字典藏集:4 36 | - 天官赐福动画2周年数字典藏:5 37 | - A-AKB48TSH四周年数字集换卡:6 38 | - B-AKB48TSH四周年数字集换卡:7 39 | - C-AKB48TSH四周年数字集换卡:8 40 | - D-AKB48TSH四周年数字集换卡:9 41 | - E-AKB48TSH四周年数字集换卡:10 42 | - F-AKB48TSH四周年数字集换卡:11 43 | - G-AKB48TSH四周年数字集换卡:12 44 | - H-AKB48TSH四周年数字集换卡:13 45 | - 三体动画数字周边:14 46 | - 2022百大UP主数字卡集:18 47 | 5. 接下来会出现当前数字周边下你所拥有的卡片名称及其id,复制卡片id后输入,按下回车继续 48 | 6. 恭喜🎉此时就大功告成了,等待头像审核完成即可 49 | 50 | # 自行运行程序指南 51 | 52 | 1. 前往官网下载安装[Python](https://www.python.org/),建议运行版本:`3.8+` 53 | 54 | - 查看Python版本的方式:打开控制台输入以下命令 55 | 56 | ```bash 57 | python -V 58 | ``` 59 | 60 | 61 | 62 | 2. 打开控制台,输入以下命令安装依赖库 63 | 64 | ```bash 65 | pip install -r requirements.txt 66 | ``` 67 | 68 | 3. 运行程序,详细操作步骤请见上文 69 | 70 | # 参考的开源仓库 71 | 72 | https://github.com/XiaoMiku01/custom_bilibili_nft 73 | 74 | https://github.com/cibimo/bilibiliLogin 75 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow 2 | qrcode 3 | requests 4 | typing 5 | requests_toolbelt --------------------------------------------------------------------------------