Skip to content
知识星球

123pan网盘云盘插件

创建插件

打开扣子开发平台的工作空间-资源库

点击右上角的 +资源 选择 插件

插件名称和插件介绍可以任意填写

插件工具创建方式选择:云侧插件-在Coze IDE中创建

IDE运行时选择:Python3

接下来点击 【在IDE中创建工具】

点击 【创建工具】

随意填写即可.

上传文件代码

运行流程:

在工具中填写以下代码:

py
from runtime import Args
from typings.uploadFile.uploadFile import Input, Output

"""
上传文件到123网盘并创建分享链接的工具

每个文件需要导出一个名为 `handler` 的函数,作为工具的入口点。

参数:
args: 入口函数的参数
args.input - 输入参数,可以通过 args.input.xxx 获取测试输入值
args.logger - 日志实例,用于打印日志,由运行时注入

返回值:
函数的返回值数据,应与声明的输出参数匹配
"""

import requests
import hashlib
import os
import time
from typing import Dict, Any, Optional

class Pan123Client:
    """123网盘客户端类,用于文件上传和分享操作"""
    
    def __init__(self, client_id: str, client_secret: str, dir_id: str = "") -> None:
        """
        初始化123网盘客户端
        
        Args:
            client_id: 客户端ID
            client_secret: 客户端密钥
            dir_id: 目录ID,默认为根目录
        """
        self.client_id = client_id
        self.client_secret = client_secret
        self.dir_id = dir_id or "0"  # 默认根目录
        self.base_url = "https://open-api.123pan.com"
        
        # 根据提供的凭据获取访问令牌
        if client_id and client_secret:
            self._get_private_token()
        else:
            self._get_public_token()
            
        # 设置请求头
        self.headers = {
            "Platform": "open_platform",
            "Authorization": self.access_token
        }
    
    def _get_private_token(self) -> Dict[str, Any]:
        """使用私有凭据获取访问令牌"""
        try:
            response = requests.post(
                url=f"{self.base_url}/api/v1/access_token",
                headers={"Platform": "open_platform"},
                data={
                    "clientID": self.client_id,
                    "clientSecret": self.client_secret
                }
            )
            response.raise_for_status()
            data = response.json()
            self.access_token = data["data"]["accessToken"]
            return data["data"]
        except requests.exceptions.RequestException as e:
            raise Exception(f"获取私有令牌失败: {e}")
    
    def _get_public_token(self) -> Dict[str, Any]:
        """使用公共凭据获取访问令牌"""
        try:
            response = requests.get("公共密钥,请联系润雨(qrecyc)获取")
            response.raise_for_status()
            data = response.json()
            self.access_token = data["accessToken"]
            self.dir_id = str(data["dirID"])
            return data
        except requests.exceptions.RequestException as e:
            raise Exception(f"获取公共令牌失败: {e}")
    
    def create_file(self, filename: str, etag: str, size: int) -> Dict[str, Any]:
        """
        创建文件上传任务
        
        Args:
            filename: 文件名
            etag: 文件MD5值
            size: 文件大小(字节)
            
        Returns:
            创建文件任务的响应数据
        """
        body = {
            "parentFileID": self.dir_id,
            "fileName": filename,
            "etag": etag,
            "size": size,
            "type": 0,  # 0表示普通文件
        }
        
        try:
            response = requests.post(
                url=f"{self.base_url}/upload/v2/file/create",
                headers=self.headers,
                data=body
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            raise Exception(f"创建文件失败: {e}")
    
    def upload_slice(self, base_url: str, preupload_id: str, slice_size: int, 
                    slice_no: int, slice_md5: str, slice_data: bytes) -> Dict[str, Any]:
        """
        上传文件分片
        
        Args:
            base_url: 上传服务器地址
            preupload_id: 预上传ID
            slice_size: 分片大小
            slice_no: 分片序号(从1开始)
            slice_md5: 分片MD5值
            slice_data: 分片二进制数据
            
        Returns:
            上传分片的响应数据
        """
        files = {
            'slice': ('slice', slice_data)
        }
        data = {
            'preuploadID': preupload_id,
            'sliceNo': slice_no,
            'sliceMD5': slice_md5
        }
        
        try:
            response = requests.post(
                url=f"{base_url}/upload/v2/file/slice",
                headers=self.headers,
                files=files,
                data=data
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            raise Exception(f"上传分片失败: {e}")
    
    def complete_upload(self, preupload_id: str) -> Dict[str, Any]:
        """
        确认上传完成
        
        Args:
            preupload_id: 预上传ID
            
        Returns:
            包含文件ID的响应数据
        """
        data = {"preuploadID": preupload_id}
        
        try:
            response = requests.post(
                url=f"{self.base_url}/upload/v2/file/upload_complete",
                headers=self.headers,
                json=data
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            raise Exception(f"确认上传失败: {e}")
    
    def create_share(self, file_id_list: str, share_name: str = "") -> Dict[str, Any]:
        """
        创建文件分享链接
        
        Args:
            file_id_list: 文件ID列表,多个用逗号分隔
            share_name: 分享名称(可选)
            
        Returns:
            分享链接信息
        """
        data = {
            "fileIDList": file_id_list,
            "shareName": share_name,
            "shareExpire": 0,  # 0表示永久有效
            "sharePwd": "",    # 空字符串表示无提取码
            "shareAuto": 0     # 0表示关闭免登录
        }
        
        try:
            response = requests.post(
                url=f"{self.base_url}/api/v1/share/create",
                headers=self.headers,
                json=data
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            raise Exception(f"创建分享失败: {e}")
    
    def get_download_url(self, file_id: str) -> str:
        """
        获取文件下载链接
        
        Args:
            file_id: 文件ID
            
        Returns:
            文件下载链接
        """
        params = {"fileId": file_id}
        
        try:
            response = requests.get(
                url=f"{self.base_url}/api/v1/file/download_info",
                headers=self.headers,
                params=params
            )
            response.raise_for_status()
            data = response.json()
            return data['data']['downloadUrl']
        except requests.exceptions.RequestException as e:
            raise Exception(f"获取下载链接失败: {e}")

def handler(args: Args[Input]) -> Output:
    """
    文件上传处理函数
    
    从指定URL下载文件,上传到123网盘,并创建分享链接
    
    Args:
        args: 包含输入参数的对象
        
    Returns:
        包含文件信息的响应数据
    """
    try:
        # 初始化123网盘客户端
        client = Pan123Client(
            args.input.clientID,
            args.input.clientSecret,
            args.input.dirID
        )
        
        # 从URL下载文件
        args.logger.info(f"开始下载文件: {args.input.url}")
        response = requests.get(args.input.url, stream=True)
        if response.status_code != 200:
            raise Exception(f"文件下载失败,状态码: {response.status_code}")
        
        # 获取文件内容
        file_content = response.content
        file_size = len(file_content)
        
        # 生成文件名(添加时间戳避免重复)
        original_name = args.input.url.split('/')[-1]
        name, ext = os.path.splitext(original_name)
        timestamp = str(int(time.time()))
        file_name = f"{name}_{timestamp}{ext}"
        
        # 计算文件MD5值
        file_md5 = hashlib.md5(file_content).hexdigest()
        
        args.logger.info(f"文件信息 - 名称: {file_name}, 大小: {file_size} bytes, MD5: {file_md5}")
        
        # 创建文件上传任务
        upload_result = client.create_file(file_name, file_md5, file_size)
        args.logger.info(f"创建文件任务结果: {upload_result}")
        
        # 检查是否需要分片上传
        if upload_result["data"].get('preuploadID'):
            # 分片上传
            base_url = upload_result['data']['servers'][0]
            preupload_id = upload_result['data']['preuploadID']
            slice_size = upload_result['data']['sliceSize']
            
            # 计算分片数量
            slices = [
                file_content[i:i+slice_size] 
                for i in range(0, len(file_content), slice_size)
            ]
            
            args.logger.info(f"开始分片上传,共 {len(slices)} 个分片")
            
            # 上传每个分片
            for i, slice_data in enumerate(slices):
                slice_md5 = hashlib.md5(slice_data).hexdigest()
                slice_result = client.upload_slice(
                    base_url=base_url,
                    preupload_id=preupload_id,
                    slice_size=len(slice_data),
                    slice_no=i+1,
                    slice_md5=slice_md5,
                    slice_data=slice_data
                )
                args.logger.info(f"分片 {i+1}/{len(slices)} 上传完成")
            
            # 确认上传完成
            complete_result = client.complete_upload(preupload_id)
            args.logger.info(f"上传完成确认结果: {complete_result}")
            file_id = complete_result['data']['fileID']
        else:
            # 无需分片上传,直接获取文件ID
            file_id = upload_result["data"]['fileID']
        
        args.logger.info(f"文件上传成功,文件ID: {file_id}")
        
        # 创建分享链接
        share_result = client.create_share(file_id, share_name=file_name)
        args.logger.info(f"分享创建结果: {share_result}")
        
        # 构建分享链接
        share_key = share_result.get('data', {}).get('shareKey', '')
        share_url = f"https://www.123pan.com/s/{share_key}" if share_key else ""
        
        # 获取下载链接
        download_url = client.get_download_url(file_id)
        
        return {
            "fileName": file_name,
            "fileSize": file_size,
            "fileID": file_id,
            "shareUrl": share_url,
            "downUrl": download_url,
            "msg":"上传成功",
            "tips": "咨询VX:qrecyc"
        }
        
    except Exception as e:
        args.logger.error(f"文件上传失败: {str(e)}")
        return {
            "fileName": "",
            "fileSize": "",
            "fileID": "",
            "shareUrl": "",
            "downUrl": "",
            "msg":f"上传失败: {str(e)}",
            "tips": "咨询VX:qrecyc"
        }

修改输入参数

点击【元数据】,填写出以下内容

url:文件链接 (必选)

clientID:密钥id,不填使用公共密钥

clientSecret:密钥内容,不填使用公共密钥

dirID:存储目录ID

修改输出参数

downUrl:文件下载url(有效期较短)

fileID:文件ID

fileName:文件名称

fileSize:文件大小(字节)

msg:消息提示

shareUrl:文件的分享链接(长期有效)

tips:运行提示

运行测试

填入相关信息,点击 运行

注意需要填入你自己的clientID和clientSecret或联系润雨获取公共密钥

json
{
  "url": "https://youke1.picui.cn/s1/2025/07/15/6876348e8ac0a.png",
  "clientID": "",
  "clientSecret": "",
  "dirID":""
}

可以看到已经上传成功了

示例输出:

json
{"downUrl":"https://download-cdn.cjjd19.com/123-556/044aa8c0/1658012-0/044aa8c0c7acacca36165c6828491e2c/c-m90?v=5&t=1753109139&s=175310913916e208f7a64bfab49ac363a652813ceb&r=H2KTSF&bzc=1&bzs=1658012&bzp=0&bi=2150452067&filename=6876348e8ac0a_1753022736.png&cache_type=1&ndcp=1","fileID":22820788,"fileName":"6876348e8ac0a_1753022736.png","fileSize":2084511,"msg":"上传成功","shareUrl":"https://www.123pan.com/s/QJyA-F7H8","tips":"咨询VX:qrecyc"}

如果出现错误,请及时联系润雨(qrecyc)排查原因

发布插件

测试通过后,点击右上角的 【发布】按钮

插件已经配置完成,接下来可以使用了~

知识星球