import tkinter as tk
from tkinter import filedialog, scrolledtext
import urllib.request
import urllib.error
import re
import os
from io import BytesIO
import gzip
import zlib
import json
import logging
import brotli # 添加这行来导入 brotli

class BilibiliUploader:
def __init__(self, master):
self.master = master
master.title("Bilibili图片上传器")

# 文件选择
self.select_button = tk.Button(master, text="选择图片", command=self.select_file)
self.select_button.pack()

self.file_label = tk.Label(master, text="未选择文件")
self.file_label.pack()

# 上传按钮
self.upload_button = tk.Button(master, text="上传到Bilibili", command=self.upload_file)
self.upload_button.pack()

# 日志显示
self.log_text = scrolledtext.ScrolledText(master, height=20)
self.log_text.pack()

self.file_path = None

def select_file(self):
self.file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif")])
if self.file_path:
self.file_label.config(text=f"已选择: {self.file_path}")
else:
self.file_label.config(text="未选择文件")

def upload_file(self):
if not self.file_path:
self.log("请先选择一个图片文件")
return

self.log(f"开始上传图片到Bilibili: {self.file_path}")
self.make_requests(self.file_path)
self.log("上传过程完成")

def log(self, message):
self.log_text.insert(tk.END, message + "n")
self.log_text.see(tk.END)

def make_requests(self, image_path):
response = [None]
responseText = None

if self.request_api_bilibili_com(response, image_path):
self.log("请求成功发送")
try:
responseText = self.read_response(response[0])
self.log("响应内容:")
self.log(responseText)
json_response = json.loads(responseText)
if json_response.get('code') == 0:
self.log("上传成功!")
image_url = json_response.get('data', {}).get('image_url', '')
if image_url:
self.log(f"图片URL: {image_url}")
else:
self.log(f"上传失败。错误信息: {json_response.get('message', '未知错误')}")
except json.JSONDecodeError:
self.log("无法解析JSON响应")
except Exception as e:
self.log(f"处理响应时发生错误: {str(e)}")
finally:
response[0].close()
else:
self.log("请求失败")

def write_multipart_body_to_request(self, req, body):
multiparts = re.compile("<!>").split(body)
with BytesIO() as bytes_io:
for part in multiparts:
if os.path.exists(part):
with open(part, "rb") as file:
bytes_io.write(file.read())
else:
bytes_io.write(part.replace("rn", "n").replace("r", "n").replace("n", "rn").encode("utf-8"))

body = bytes_io.getvalue()
req.add_header("Content-Length", str(len(body)))
req.data = body

def read_response(self, response):
content = response.read()
content_encoding = response.info().get('Content-Encoding', '').lower()

if content_encoding == 'gzip':
buf = BytesIO(content)
with gzip.GzipFile(fileobj=buf) as gf:
content = gf.read()
elif content_encoding == 'deflate':
content = zlib.decompress(content, -zlib.MAX_WBITS)
elif content_encoding == 'br':
content = brotli.decompress(content)

try:
return content.decode('utf-8')
except UnicodeDecodeError:
return content.decode('utf-8', errors='ignore')

def request_api_bilibili_com(self, response, image_path):
response[0] = None

try:
req = urllib.request.Request("https://api.bilibili.com/x/dynamic/feed/draw/upload_bfs")

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Referer": "https://t.bilibili.com/",
"Origin": "https://t.bilibili.com",
"Connection": "keep-alive",
"Cookie": "buvid3=DEEE7B07-FFBF-0ACA-1A8B-BA9B3B98D6F234696infoc; b_nut=1697693535; _uuid=6AB48D3E-CAFB-4210D-ACE3-92C4B2102F46538381infoc; buvid4=A1F0A409-3626-8333-8964-2B9F32D92EC840243-023101913-MnLxL6Vqo8JzkbhjsXOcgQ%3D%3D; buvid_fp=5e9a8088e2093db52ac4432fb2bb81c2; enable_web_push=DISABLE; header_theme_version=CLOSE; home_feed_column=5; browser_resolution=1536-703; fingerprint=5e9a8088e2093db23432fb2bb81c2; buvid_fp_plain=undefined; CURRENT_FNVAL=4048; rpdid=|(J~J|Rm|RuJ0J'uYm~~)kmY|; b_lsid=C342BDD2_192541C1C36; bsource=search_baidu; SESSDATA=2c853358%2C1743540007%2C1c395%2Aa1CjBoQPqw1p54Ut1L3sUZ-iKwDSkFrIWeDJfsmBhzhgNoqGgTCiG-SoohDFKKH0WS5PUSVjRZY0FnQWREVTZQbDFGS0J5d2FiTU9XeVNFU0huOEZES3B1c19UaEFiT1NwYTdBSlhTZWJmczQ5bFJNaWx4YTVkcEljdS1FaVhoVnY5Ql9JNTFJbkl3IIEC; bili_jct=f4a19205ce0e5fdfd26441438b9fd811; DedeUserID=15621741; DedeUserID__ckMd5=a7b49f848348c797; sid=h6yrhg31; hit-dyn-v2=1; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjgyNDcyMTQsImlhdCI6MTcyNzk4Nzk1NCwicGx0IjotMX0._g00SN27Nu3SJHAshhrK1WSCVryiM5PsYy2j1VNhSsQ; bili_ticket_expires=1728247154",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site"
}
for key, value in headers.items():
req.add_header(key, value)

boundary = "---------------------------76447336634624058473024722700"
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")

body = f"""--{boundary}
Content-Disposition: form-data; name="file_up"; filename="{os.path.basename(image_path)}"
Content-Type: image/jpeg

<!>{image_path}<!>
--{boundary}
Content-Disposition: form-data; name="biz"

new_dyn
--{boundary}
Content-Disposition: form-data; name="category"

daily
--{boundary}
Content-Disposition: form-data; name="csrf"

f4a19205ce0e5fdfd26441438b9fd811
--{boundary}--
"""
self.write_multipart_body_to_request(req, body)

self.log("正在发送请求...")
response[0] = urllib.request.urlopen(req)
self.log("请求已发送,等待响应...")

except urllib.error.URLError as e:
self.log(f"发生URLError: {e}")
if not hasattr(e, "code"):
return False
response[0] = e
except Exception as e:
self.log(f"发生异常: {e}")
return False

return True

def main():
root = tk.Tk()
app = BilibiliUploader(root)
root.mainloop()

if __name__ == "__main__":
main()