百度云盘批量转存工具

更新时间:2023/09/04
2023.09.04
由于百度更新接口,秒传已失效
2023.06.01 ver1.2
1.修复了一个验证链接失败的问题
2.修复了http链接被识别为未知链接的问题
3.修复了链接根目录为多个文件时,重命名仅第一个成功的问题
2022.03.21 ver1.1
1.修复了一个转存失败200025的问题
2.修复了一个重复创建文件夹的问题

import re
import json
import threading
import time
import random
import requests
import requests.cookies
import webbrowser
from tkinter import *
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from retrying import retry

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

S_WIDTH = 600
S_HEIGHT = 500

HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Connection': 'keep-alive',
    'Host': 'pan.baidu.com',
    'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'Referer': 'https://pan.baidu.com'
}

BDSTOKEN_URL = 'https://pan.baidu.com/api/loginStatus?clienttype=0&web=1'
VERIFY_URL = 'https://pan.baidu.com/share/verify'
TRANSFER_URL = 'https://pan.baidu.com/share/transfer'
TRANSFER_REPID_URL = 'https://pan.baidu.com/api/rapidupload'
TRANSFER_RENAME_URL = 'https://pan.baidu.com/api/filemanager?async=2&onnest=fail&clienttype=0&opera=rename&app_id=250528&web=1'
GET_DIR_LIST_URL = 'https://pan.baidu.com/api/list?order=time&desc=1&showempty=0&web=1&page=1&num=1000'
CREATE_DIR_URL = 'https://pan.baidu.com/api/create?a=commit'


class Thread:
    def __init__(self, func, *args):
        self.func = func
        self.args = args

    def thread_start(self):
        t = threading.Thread(target=self.func, args=self.args)
        t.setDaemon(True)
        t.start()


class GUI:
    def __init__(self, init_window_name):
        self.init_window_name = init_window_name

    def set_init_window(self):
        self.init_window_name.title("百度云批量转存工具_v1.2 / by mobclix")
        self.init_window_name.geometry(str(S_WIDTH) + 'x' + str(S_HEIGHT) + '+'
                                       + str((self.init_window_name.winfo_screenwidth() - S_WIDTH) // 2) + '+'
                                       + str((self.init_window_name.winfo_screenheight() - S_HEIGHT) // 2 - 18))
        self.init_window_name.attributes("-alpha", 0.9)

        self.cookie_data_label = Label(self.init_window_name, anchor='w', text="cookie:")
        self.cookie_data_label.place(relx=0.025, rely=0.01, relheight=0.04, relwidth=0.32)

        self.dirname_data_label = Label(self.init_window_name, anchor='w', text="保存路径:")
        self.dirname_data_label.place(relx=0.025, rely=0.12, relheight=0.04, relwidth=0.32)

        self.link_data_label = Label(self.init_window_name, anchor='w', text="链接:")
        self.link_data_label.place(relx=0.025, rely=0.21, relheight=0.04, relwidth=0.32)

        self.cookie_data_Text = Text(self.init_window_name)
        self.cookie_data_Text.place(relx=0.025, rely=0.05, relheight=0.06, relwidth=0.42)

        self.dirname_data_Text = Text(self.init_window_name)
        self.dirname_data_Text.place(relx=0.025, rely=0.16, relheight=0.04, relwidth=0.42)

        self.scrollbar_link = Scrollbar(self.init_window_name)
        self.scrollbar_link.place(relx=0.445, rely=0.25, relheight=0.65, width=18)
        self.link_data_Text = Text(self.init_window_name, yscrollcommand=self.scrollbar_link.set)
        self.link_data_Text.place(relx=0.025, rely=0.25, relheight=0.65, relwidth=0.42)
        self.scrollbar_link.configure(command=self.link_data_Text.yview)

        self.scrollbar_log = Scrollbar(self.init_window_name)
        self.scrollbar_log.place(relx=0.95, rely=0.05, relheight=0.85, width=18)
        self.log_data_Text = Text(self.init_window_name, yscrollcommand=self.scrollbar_log.set)
        self.log_data_Text.place(relx=0.5, rely=0.05, relheight=0.85, relwidth=0.45)
        self.scrollbar_log.configure(command=self.log_data_Text.yview)

        self.start_button = Button(self.init_window_name, text="开始", bg="#fff", width=10,
                                   command=lambda: Thread(main, self).thread_start())
        self.start_button.place(relx=0.025, rely=0.92, relheight=0.06, relwidth=0.1)

        self.label_update = Label(self.init_window_name, text='查看教程', font=('Arial', 9, 'underline'),
                                  foreground="#0000ff", cursor='mouse')
        self.label_update.place(relx=0.70, rely=0.92, relheight=0.06, relwidth=0.1)
        self.label_update.bind("<Button-1>",
                               lambda e: webbrowser.open("https://blog.csdn.net/mobclix/article/details/123068801", new=0))

        self.label_example = Label(self.init_window_name, text='检查新版', font=('Arial', 9, 'underline'),
                                   foreground="#0000ff", cursor='mouse')
        self.label_example.place(relx=0.82, rely=0.92, relheight=0.06, relwidth=0.1)
        self.label_example.bind("<Button-1>",
                                lambda e: webbrowser.open("https://github.com/mobclix/PanTransfers", new=0))


def random_sleep(start=1, end=3):
    sleep_time = random.randint(start, end)
    time.sleep(sleep_time)


def check_link_type(link):
    if link.find('pan.baidu.com/s/') != -1:
        link_type = 'common'
    elif link.count('#') > 2:
        link_type = 'rapid'
    else:
        link_type = 'unknown'
    return link_type


def link_format(links):
    link_list = [link for link in links if link]
    link_list = [link + ' ' for link in link_list]
    return link_list


def parse_url_and_code(url):
    url = url.lstrip('链接:').strip()
    res = re.sub(r'提取码*[::](.*)', r'\1', url).split(' ', maxsplit=2)
    link_url = res[0]
    pass_code = res[1]
    unzip_code = None
    if len(res) == 3:
        unzip_code = res[2]
    link_url = re.sub(r'\?pwd=(.*)', '', link_url)
    return link_url, pass_code, unzip_code


class PanTransfer:
    def __init__(self, cookie, user_agent, dir_name, gui):
        self.headers = dict(HEADERS)
        self.headers['Cookie'] = cookie
        self.dir_name = dir_name
        self.user_agent = user_agent
        self.gui = gui
        self.bdstoken = None
        self.timeout = 10
        self.session = requests.Session()
        self.session.verify = False
        self.session.headers.update(self.headers)
        self.get_bdstoken()
        self.create_dir()

    @retry(stop_max_attempt_number=5, wait_fixed=1000)
    def post(self, url, post_data):
        return self.session.post(url=url, data=post_data, timeout=self.timeout, allow_redirects=False, verify=False)

    @retry(stop_max_attempt_number=5, wait_fixed=1000)
    def get(self, url):
        return self.session.get(url=url, timeout=self.timeout, allow_redirects=True)

    def get_bdstoken(self):
        response = self.get(BDSTOKEN_URL)
        bdstoken_list = re.findall('"bdstoken":"(.*?)"', response.text)
        if bdstoken_list:
            self.bdstoken = bdstoken_list[0]
        else:
            raise ValueError('获取bdstoken失败!')

    def transfer_files_repid(self, rapid_data, dir_name):
        url = TRANSFER_REPID_URL + f'?bdstoken={self.bdstoken}'
        post_data = {'path': dir_name + '/' + rapid_data[3], 'content-md5': rapid_data[0],
                     'slice-md5': rapid_data[1], 'content-length': rapid_data[2]}
        response = self.post(url, post_data)
        if response.json()['errno'] == 404:
            post_data = {'path': dir_name + '/' + rapid_data[3], 'content-md5': rapid_data[0].lower(),
                         'slice-md5': rapid_data[1].lower(), 'content-length': rapid_data[2]}
            response = self.post(url, post_data)
        data = response.json()
        if data['errno'] == 0:
            self.logs(END, '转存成功!保存位置:' + data['info']['path'] + '\n\n')
        else:
            raise ValueError('转存失败!errno:' + str(data['errno']))

    def transfer_files(self, shareid, user_id, fs_id_list, dir_name, unzip_code):
        url = TRANSFER_URL + f'?shareid={shareid}&from={user_id}&bdstoken={self.bdstoken}'
        if not dir_name.strip().startswith('/'):
            dir_name = '/' + dir_name.strip()
        fsidlist = f"[{','.join(i for i in fs_id_list)}]"
        post_data = {'fsidlist': fsidlist, 'path': dir_name}
        response = self.post(url, post_data)
        data = response.json()
        if data['errno'] == 0:
            for each in data['extra']['list']:
                self.logs(END, '转存成功!保存位置:' + each['to'] + '\n')
                if unzip_code is not None:
                    self.transfer_files_rename(each['to_fs_id'], each['to'], each['from'].replace('/', ''), unzip_code)
                self.logs(END, '\n')
        else:
            raise ValueError('转存失败!errno:' + str(data['errno']))

    def get_dir_list(self, dir_name):
        url = GET_DIR_LIST_URL + f'&dir={dir_name}&bdstoken={self.bdstoken}'
        response = self.get(url)
        data = response.json()
        if data['errno'] == 0:
            dir_list_json = data['list']
            if type(dir_list_json) != list:
                raise ValueError('没获取到网盘目录列表,请检查cookie和网络后重试!\n\n')
            else:
                return dir_list_json
        else:
            ValueError('获取网盘目录列表失败! errno:' + data['errno'] + '\n\n')

    def create_dir(self):
        if self.dir_name != '/' and self.dir_name != '':
            # dir_list_json = self.get_dir_list()
            # dir_list = [dir_json['server_filename'] for dir_json in dir_list_json]
            dir_name_list = self.dir_name.split('/')
            dir_name = dir_name_list[len(dir_name_list) - 1]
            dir_name_list.pop()
            path = '/'.join(dir_name_list) + '/'
            dir_list_json = self.get_dir_list(path)
            dir_list = [dir_json['server_filename'] for dir_json in dir_list_json]
            if dir_name and dir_name not in dir_list:
                url = CREATE_DIR_URL + f'&bdstoken={self.bdstoken}'
                post_data = {'path': self.dir_name, 'isdir': '1', 'block_list': '[]', }
                response = self.post(url, post_data)
                data = response.json()
                if data['errno'] == 0:
                    self.logs(END, '创建目录成功!\n\n')
                else:
                    self.logs(END, '创建目录失败!路径中不能包含以下任何字符: \\:*?"<>|\n\n')

    def transfer_files_rename(self, fs_id, path, name, unzip_code):
        try:
            url = TRANSFER_RENAME_URL + f'&bdstoken={self.bdstoken}'
            newname = name + ' ' + unzip_code
            post_data = {'filelist': f'[{{"id": {fs_id}, "path": "{path}", "newname": "{newname}"}}]'}
            response = self.post(url, post_data)
            data = response.json()
            if data['errno'] == 0:
                self.logs(END, '重命名成功!:' + newname + '\n')
            else:
                self.logs(END, '重命名失败!errno:' + str(data['errno']) + '\n')
            time.sleep(1)
        except Exception as e:
            self.logs(END, '重命名失败!err:' + str(e) + '\n')

    def verify_link(self, link_url, pass_code):
        sp = link_url.split('/')
        url = VERIFY_URL + '?surl=' + sp[len(sp)-1][1:]
        post_data = {'pwd': pass_code, 'vcode': '', 'vcode_str': '', }
        response = self.post(url, post_data)
        data = response.json()
        if data['errno'] == 0:
            bdclnd = data['randsk']
            cookie = self.session.headers['Cookie']
            if 'BDCLND=' in cookie:
                cookie = re.sub(r'BDCLND=(\S+?);', f'BDCLND={bdclnd};', cookie)
            else:
                cookie += f';BDCLND={bdclnd};'
            self.session.headers['Cookie'] = cookie
            return data
        elif data['errno'] == -9:
            raise ValueError('提取码错误!')
        elif data['errno'] == -62 or data['errno'] == -19 or data['errno'] == -63:
            raise ValueError('错误尝试次数过多,请稍后再试!')
        else:
            raise ValueError('验证链接失败!errno:' + str(data['errno']))

    def get_share_link_info(self, link_url, pass_code):
        self.verify_link(link_url, pass_code)
        random_sleep(start=1, end=3)
        response = self.get(link_url)
        info = re.findall(r'locals\.mset\((.*)\);', response.text)
        if len(info) == 0:
            raise ValueError("获取分享信息失败!")
        else:
            link_info = json.loads(info[0])
        return link_info

    def get_link_data(self, link_url, pass_code):
        link_info = self.get_share_link_info(link_url, pass_code)
        shareid = link_info['shareid']
        user_id = link_info['share_uk']
        file_list = [{'fs_id': i['fs_id'], 'filename': i['server_filename'], 'isdir': i['isdir']} for i in
                     link_info['file_list']]
        if len(file_list) == 0:
            raise ValueError('文件列表为空!')
        return {'shareid': shareid, 'user_id': user_id, 'file_list': file_list}

    def transfer_common(self, link):
        link_url, pass_code, unzip_code = parse_url_and_code(link)
        link_data = self.get_link_data(link_url, pass_code)
        shareid, user_id = link_data['shareid'], link_data['user_id']
        fs_id_list = [str(data['fs_id']) for data in link_data['file_list']]
        self.transfer_files(shareid, user_id, fs_id_list, self.dir_name, unzip_code)

    def transfer_repid(self, link):
        rapid_data = link.split('#', maxsplit=3)
        self.transfer_files_repid(rapid_data, self.dir_name)

    def transfer(self, link_list):
        link_list = link_format(link_list)
        for link in link_list:
            try:
                self.logs(END, '正在转存:' + link + '\n')
                link_type = check_link_type(link)
                if link_type == 'common':
                    self.transfer_common(link)
                elif link_type == 'rapid':
                    self.transfer_repid(link)
                else:
                    raise ValueError('未知链接类型')
            except Exception as e:
                try:
                    with open('error.txt', 'a') as f:
                        f.write(link + '\n')
                except BaseException:
                    print('export_txt error')
                self.logs(END, 'Transfer Error --- ' + str(e) + '\n\n')
        self.logs(END, '转存完成!' + '\n')

    def logs(self, index, text):
        self.gui.log_data_Text.insert(index, text)


def main(gui):
    try:
        gui.log_data_Text.delete(1.0, END)
        if gui.link_data_Text.get(1.0, END) == '\n' or gui.cookie_data_Text.get(1.0, END) == '\n':
            gui.log_data_Text.insert(END, 'cookie或链接不能为空! ' + '\n\n')
            return
        dir_name = "".join(gui.dirname_data_Text.get(1.0, END).split())
        cookie = "".join(gui.cookie_data_Text.get(1.0, END).split())
        user_agent = ''
        link_list = gui.link_data_Text.get(1.0, END).split('\n')

        pan_transfer = PanTransfer(cookie, user_agent, dir_name, gui)
        pan_transfer.transfer(link_list)
    except Exception as e:
        gui.log_data_Text.insert(END, 'Error --- ' + str(e) + '\n\n')


def gui_start():
    init_window = Tk()
    gui = GUI(init_window)
    gui.set_init_window()
    init_window.mainloop()


gui_start()



评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值