#!/usr/bin/env python3
# encoding: utf-8
from __future__ import annotations
__all__ = ["check_response", "P115OpenClient", "P115Client"]
__doc__ = "115 客户端模块"
from asyncio import Lock as AsyncLock
from base64 import b64encode
from collections import UserString
from collections.abc import (
AsyncIterable, Awaitable, Buffer, Callable, Coroutine,
Iterable, Iterator, Mapping, MutableMapping, Sequence,
)
from datetime import date, datetime, timedelta
from hashlib import md5, sha1
from http.cookiejar import Cookie, CookieJar
from http.cookies import Morsel, BaseCookie
from inspect import isawaitable
from operator import itemgetter
from os import fsdecode, isatty, PathLike
from pathlib import Path, PurePath
from re import compile as re_compile, Match, MULTILINE
from string import digits
from threading import Lock
from time import time
from typing import cast, overload, Any, Final, Literal, Self
from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit
from uuid import uuid4
from warnings import warn
from asynctools import ensure_async
from cookietools import cookies_to_dict, update_cookies
from dicttools import (
get_first, dict_update, dict_key_to_lower_merge, iter_items, KeyLowerDict,
)
from ensure import ensure_bytes
from errno2 import errno
from filewrap import to_bytes_view, SupportsRead
from http_request import complete_url as make_url, SupportsGeturl
from http_response import get_status_code
from httpfile import HTTPFileReader, AsyncHTTPFileReader
from iterutils import run_gen_step
from orjson import dumps, loads
from p115cipher import (
rsa_encrypt, rsa_decrypt, ecdh_aes_encrypt, ecdh_aes_decrypt,
ecdh_encode_token, make_upload_payload,
)
from p115oss import upload_file
from p115pickcode import to_id, to_pickcode
from property import locked_cacheproperty
from temporary import temp_globals
from yarl import URL
from .const import (
CLIENT_API_METHODS_MAP, CLIENT_METHOD_API_MAP,
SSOENT_TO_APP,
)
from .exception import (
throw, P115OSError, P115Warning, P115AccessTokenError,
P115AuthenticationError, P115LoginError, P115OpenAppAuthLimitExceeded,
P115OperationalError, P115DownloadFileNotFoundError,
)
from .type import P115Cookies, P115URL
from .util import complete_url, share_extract_payload, normalize_pickcode
CRE_SET_COOKIE: Final = re_compile(r"[0-9a-f]{32}=[0-9a-f]{32}.*")
CRE_COOKIES_UID_search: Final = re_compile(r"(?<=\bUID=)[^\s;]+").search
CRE_AREA_DATA_search: Final = re_compile(r"(?<=n=)\{[\s\S]+?\}(?=;)").search
_default_k_ec = {"k_ec": ecdh_encode_token(0).decode()}
_default_code_verifier = "0" * 64
_default_code_challenge = b64encode(md5(b"0" * 64).digest()).decode()
_default_code_challenge_method = "md5"
_app_version = "36.2.28"
def content_maybe_decrypt(content: Buffer, /) -> Buffer:
data = to_bytes_view(content)
if data[:1].tobytes() + data[-1:].tobytes() not in (b"{}", b"[]", b'""'):
return ecdh_aes_decrypt(data)
return content
def json_loads(content: Buffer, /):
try:
return loads(to_bytes_view(content))
except Exception:
throw(errno.ENODATA, bytes(content))
def json_decrypt_loads(content: Buffer, /):
return json_loads(ecdh_aes_decrypt(content))
def json_maybe_decrypt_loads(content: Buffer, /):
return json_loads(content_maybe_decrypt(content))
def json_parse(_, content: Buffer, /):
return json_loads(content)
def json_decrypt_parse(_, content: Buffer, /):
return json_decrypt_loads(content)
def json_maybe_decrypt_parse(_, content: Buffer, /):
return json_maybe_decrypt_loads(content)
def get_request(
url: str,
method: str = "GET",
payload: Any = None,
headers: Any = None,
ecdh_encrypt: bool = False,
request: None | Callable = None,
self: None | ClientRequestMixin = None,
**request_kwargs,
) -> tuple[Callable, dict]:
if self is not None:
request_kwargs.update(
url=url,
method=method,
payload=payload,
headers=headers,
ecdh_encrypt=ecdh_encrypt,
request=request,
)
return self.request, request_kwargs
if request is None:
from urllib3_future_request import request
request = cast(Callable, request)
request_kwargs.update(url=url, method=method)
is_open_api = URL(url).path.startswith("/open/")
if is_open_api:
ecdh_encrypt = False
if payload is not None:
request_kwargs.setdefault(
"data" if method.upper() in ("POST", "PUT") else "params",
payload,
)
params = request_kwargs.get("params")
if isinstance(params, dict):
params.setdefault("app_ver", _app_version)
headers = request_kwargs["headers"] = dict_key_to_lower_merge(headers or ())
headers["referer"] = headers.get("referer") or str(URL(url).origin())
if ecdh_encrypt:
url = request_kwargs["url"] = make_url(url, params=_default_k_ec)
if data := request_kwargs.get("data"):
if not isinstance(data, (Buffer, str, UserString)):
data = urlencode(data)
request_kwargs["data"] = ecdh_aes_encrypt(ensure_bytes(data) + b"&")
headers["content-type"] = "application/x-www-form-urlencoded"
request_kwargs.setdefault("parse", json_decrypt_parse)
else:
request_kwargs.setdefault("parse", json_parse)
return request, request_kwargs
def md5_secret_password(password: None | int | str = "670b14728ad9902aecba32e22fa4f6bd", /) -> str:
if not password:
return "670b14728ad9902aecba32e22fa4f6bd"
if isinstance(password, str) and len(password) == 32:
return password
return md5(f"{password:>06}".encode("ascii")).hexdigest()
def parse_upload_init_response(_, content: bytes, /) -> dict:
data = ecdh_aes_decrypt(content)
if not isinstance(data, (bytes, bytearray)):
data = to_bytes_view(data)
return json_loads(data)
def expand_payload(
payload: dict[str, Any] | Iterable[tuple[str, Any]],
prefix: str = "",
enum_seq: bool | int = False,
seq_types: type | tuple[type, ...] = (tuple, list),
map_types: type | tuple[type, ...] = dict,
) -> Iterable[tuple[str, Any]]:
if prefix:
prefix = f"{prefix}["
for k, v in iter_items(payload):
if prefix and not k.startswith(prefix):
k = f"{prefix}{k}]"
if isinstance(v, seq_types):
v = cast(Sequence, v)
if isinstance(enum_seq, bool):
if enum_seq:
enum_seq = 0
else:
for v2 in v:
yield from expand_payload(v2, f"{k}[]")
continue
for i, v2 in enumerate(v, cast(int, enum_seq)):
yield from expand_payload(v2, f"{k}[{i}]")
elif isinstance(v, map_types):
v = cast(Mapping, v)
k2: Any
for k2, v2 in iter_items(v):
yield from expand_payload(v2, f"{k}[{k2}]")
else:
yield k, v
@overload
def check_response(resp: dict, /) -> dict:
...
@overload
def check_response(resp: Awaitable[dict], /) -> Coroutine[Any, Any, dict]:
...
[docs]
def check_response(resp: dict | Awaitable[dict], /) -> dict | Coroutine[Any, Any, dict]:
"""检测 115 的某个接口的响应,如果成功则直接返回,否则根据具体情况抛出一个异常,基本上是 OSError 的实例
"""
def check(resp, /) -> dict:
if not isinstance(resp, dict):
raise P115OSError(errno.EIO, resp)
if resp.get("state", True):
return resp
if code := get_first(resp, "errno", "errNo", "errcode", "errCode", "code", "msg_code", default=None):
resp.setdefault("errno", code)
if "error" not in resp:
resp.setdefault("error", get_first(resp, "msg", "error_msg", "message", default=None))
match code:
# {"state": False, "error": "网盘单个文件夹限5万个文件,请清理后再添加!", "errno": 2}
case 2:
raise P115OperationalError(errno.EIO, resp)
# {"state": false, "errno": 99, "error": "请重新登录"}
case 99:
raise P115LoginError(errno.EAUTH, resp)
# {"state": false, "errno": 911, "error": "请验证账号"}
case 911:
throw(errno.EAUTH, resp)
# {"state": false, "errno": 1001, "error": "参数错误"}
case 1001:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 10004, "error": "错误的链接"}
case 10004:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 10014, "error": "云端目录不存在,请恢复后重新上传"}
case 10014:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 20001, "error": "目录名称不能为空"}
case 20001:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 20002, "error": "目录名称不能超出20个字。"}
case 20002:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 20003, "error": "文件名不能包含以下任意字符之一 “ " < > ”。"}
case 20003:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 20004, "error": "该目录名称已存在。"}
case 20004:
throw(errno.EEXIST, resp)
# {"state": false, "errno": 20009, "error": "父目录不存在。"}
case 20009:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 20013, "error": "文件夹不存在或已删除。"}
# {"state": false, "errno": 20018, "error": "文件不存在或已删除。"}
# {"state": false, "errno": 31003, "error": "文件不存在或已删除。"}
# {"state": false, "errno": 50015, "error": "文件不存在或已删除。"}
# {"state": false, "errno": 70005, "error": "文件不存在或已删除"}
# {"state": false, "errno": 70008, "error": "文件不存在或已删除"}
# {"state": false, "errno": 90008, "error": "文件(夹)不存在或已经删除。"}
# {"state": false, "errno": 430004, "error": "文件(夹)不存在或已删除。"}
case 20013 | 20018 | 31003 | 50015 | 70005 | 70008 | 90008 | 430004:
if resp.get("is_download"):
raise P115DownloadFileNotFoundError(errno.ENOENT, resp)
throw(errno.ENOENT, resp)
# {"state": false, "errno": 20020, "error": "后缀名不正确,请重新输入"}
# {"state": false, "errno": 20021, "error": "后缀名不正确,请重新输入"}
case 20020 | 20021:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 31001, "error": "所预览的文件不存在。"}
case 31001:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 31004, "error": "文档未上传完整,请上传完成后再进行查看。"}
case 31004:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 50003, "error": "很抱歉,该文件提取码不存在。"}
case 50003:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 50028, "error": "文件大小超出限制,请使用115电脑端下载"}
case 50028:
throw(errno.EFBIG, resp)
# {"state": false, "errno": 50038, "error": "下载失败,含违规内容"}
case 50038:
throw(errno.EACCES, resp)
# {"state": false, "errno": 51011, "error": "不允许转存空文件夹"}
case 51011:
raise P115OperationalError(errno.EPERM, resp)
# {"state": false, "errno": 51012, "error": "已有文件正在解压中,请稍后再试"}
case 51012:
throw(errno.EBUSY, resp)
# {"state": false, "errno": 70004, "error": "文件上传不完整"}
case 70004:
throw(errno.EISDIR, resp)
# {"state": false, "errno": 91002, "error": "不能将文件复制到自身或其子目录下。"}
case 91002:
throw(errno.ENOTSUP, resp)
# {"state": false, "errno": 91004, "error": "操作的文件(夹)数量超过5万个"}
case 91004:
throw(errno.ENOTSUP, resp)
# {"state": false, "errno": 91005, "error": "空间不足,复制失败。"}
case 91005:
throw(errno.ENOSPC, resp)
# {"state": false, "errno": 231011, "error": "文件已删除,请勿重复操作"}
case 231011:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 300104, "error": "文件超过200MB,暂不支持播放"}
case 300104:
throw(errno.EFBIG, resp)
# {"state": false, "errno": 300105, "error": "文件超过500MB,暂不支持添加到我听"}
case 300105:
throw(errno.EFBIG, resp)
# {"state": false, "errno": 320001, "error": "很抱歉,安全密钥不正确"}
case 320001:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 590075, "error": "操作太频繁,请稍候再试"}
case 590075:
throw(errno.EBUSY, resp)
# {"state": false, "errno": 800001, "error": "目录不存在。"}
case 800001:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 980006, "error": "404 Not Found"}
case 980006:
throw(errno.ENOSYS, resp)
# {"state": false, "errno": 990001, "error": "登陆超时,请重新登陆。"}
case 990001:
# NOTE: 可能就是被下线了
throw(errno.EAUTH, resp)
# {"state": false, "errno": 990002, "error": "参数错误。"}
case 990002:
throw(errno.EINVAL, resp)
# {"state": false, "errno": 990003, "error": "操作失败。"}
case 990003:
raise P115OperationalError(errno.EIO, resp)
# {"state": false, "errno": 990005, "error": "你的账号有类似任务正在处理,请稍后再试!"}
case 990005:
throw(errno.EBUSY, resp)
# {"state": false, "errno": 990009, "error": "删除[...]操作尚未执行完成,请稍后再试!"}
# {"state": false, "errno": 990009, "error": "还原[...]操作尚未执行完成,请稍后再试!"}
# {"state": false, "errno": 990009, "error": "复制[...]操作尚未执行完成,请稍后再试!"}
# {"state": false, "errno": 990019, "error": "移动[...]操作尚未执行完成,请稍后再试!"}
case 990009 | 990019:
throw(errno.EBUSY, resp)
# {"state": false, "errno": 990023, "error": "操作的文件(夹)数量超过5万个"}
case 990023:
throw(errno.ENOTSUP, resp)
# {"state": false, "errno": 4100026, "error": "该文件分享链接不存在或已被删除"}
case 4100026:
throw(errno.ENOENT, resp)
# {"state": false, "errno": 4100030, "error": "接收文件过多"}
case 4100030:
throw(errno.EIO, resp)
# {"state": 0, "errno": 40100000, "error": "参数错误!"}
# {"state": 0, "errno": 40100000, "error": "参数缺失"}
case 40100000:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40101004, "error": "IP登录异常,请稍候再登录!"}
case 40101004:
raise P115LoginError(errno.EAUTH, resp)
# {"state": 0, "errno": 40101017, "error": "用户验证失败!"}
case 40101017:
throw(errno.EAUTH, resp)
# {"state": 0, "errno": 40101032, "error": "请重新登录"}
case 40101032:
raise P115LoginError(errno.EAUTH, resp)
#################################################################
# Reference: https://www.yuque.com/115yun/open/rnq0cbz8tt7cu43i #
#################################################################
# {"state": 0, "errno": 40110000, "error": "请求异常需要重试"}
case 40110000:
raise P115OperationalError(errno.EAGAIN, resp)
# {"state": 0, "errno": 40140100, "error": "client_id 错误"}
case 40140100:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140101, "error": "code_challenge 必填"}
case 40140101:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140102, "error": "code_challenge_method 必须是 sha256、sha1、md5 之一"}
case 40140102:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140103, "error": "sign 必填"}
case 40140103:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140104, "error": "sign 签名失败"}
case 40140104:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140105, "error": "生成二维码失败"}
case 40140105:
raise P115OperationalError(errno.EIO, resp)
# {"state": 0, "errno": 40140106, "error": "APP ID 无效"}
case 40140106:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140107, "error": "应用不存在"}
case 40140107:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140108, "error": "应用未审核通过"}
case 40140108:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140109, "error": "应用已被停用"}
case 40140109:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140110, "error": "应用已过期"}
case 40140110:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140111, "error": "APP Secret 错误"}
case 40140111:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140112, "error": "code_verifier 长度要求43~128位"}
case 40140112:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140113, "error": "code_verifier 验证失败"}
case 40140113:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140114, "error": "refresh_token 格式错误(防篡改)"}
case 40140114:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140115, "error": "refresh_token 签名校验失败(防篡改)"}
case 40140115:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140116, "error": "refresh_token 无效(已解除授权)"}
case 40140116:
raise P115OperationalError(errno.EIO, resp)
# {"state": 0, "errno": 40140117, "error": "access_token 刷新太频繁"}
case 40140117:
throw(errno.EBUSY, resp)
# {"state": 0, "errno": 40140118, "error": "开发者认证已过期"}
case 40140118:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140119, "error": "refresh_token 已过期"}
case 40140119:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140120, "error": "refresh_token 检验失败(防篡改)"}
case 40140120:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140121, "error": "access_token 刷新失败"}
case 40140121:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140122, "error": "超出授权应用个数上限"}
case 40140122:
raise P115OpenAppAuthLimitExceeded(errno.EDQUOT, resp)
# {"state": 0, "errno": 40140123, "error": "access_token 格式错误(防篡改)"}
case 40140123:
raise P115AccessTokenError(errno.EINVAL, resp)
# {"state": 0, "errno": 40140124, "error": "access_token 签名校验失败(防篡改)"}
case 40140124:
raise P115AccessTokenError(errno.EINVAL, resp)
# {"state": 0, "errno": 40140125, "error": "access_token 无效(已过期或者已解除授权)"}
case 40140125:
raise P115AccessTokenError(errno.EINVAL, resp)
# {"state": 0, "errno": 40140126, "error": "access_token 校验失败(防篡改)"}
case 40140126:
raise P115AccessTokenError(errno.EINVAL, resp)
# {"state": 0, "errno": 40140127, "error": "response_type 错误"}
case 40140127:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140128, "error": "redirect_uri 缺少协议"}
case 40140128:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140129, "error": "redirect_uri 缺少域名"}
case 40140129:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140130, "error": "没有配置重定向域名"}
case 40140130:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140131, "error": "redirect_uri 非法域名"}
case 40140131:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140132, "error": "grant_type 错误"}
case 40140132:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140133, "error": "client_secret 验证失败"}
case 40140133:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140134, "error": "授权码 code 验证失败"}
case 40140134:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140135, "error": "client_id 验证失败"}
case 40140135:
throw(errno.EINVAL, resp)
# {"state": 0, "errno": 40140136, "error": "redirect_uri 验证失败(防MITM)"}
case 40140136:
throw(errno.EINVAL, resp)
elif error := resp.get("error"):
if "文件不存在" in error or "目录不存在" in error:
throw(errno.ENOENT, resp)
elif "目录名称已存在" in error:
throw(errno.EEXIST, resp)
elif error == "更新的数据为空":
throw(errno.EINVAL, resp)
throw(errno.EIO, resp)
if isinstance(resp, dict):
return check(resp)
elif isawaitable(resp):
async def check_await() -> dict:
return check(await resp)
return check_await()
throw(errno.EIO, resp)
class ClientRequestMixin:
app_id: int = 0
cookies_path: PurePath
@locked_cacheproperty
def cookies(self, /) -> CookieJar:
"""公用 cookiejar
"""
return CookieJar()
@locked_cacheproperty
def headers(self, /) -> KeyLowerDict[str, str]:
"""公用请求头
"""
return KeyLowerDict[str, str]({
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"connection": "keep-alive",
"user-agent": "Mozilla/5.0",
})
@locked_cacheproperty
def user_id(self, /) -> int:
for cookie in self.cookies:
if cookie.name == "UID":
return int(cookie.value.partition("_")[0])
if isinstance(self, P115OpenClient):
resp = check_response(self.user_info_open())
return int(resp["data"]["user_id"])
raise LookupError("can't get user_id")
@locked_cacheproperty
def request_lock(self, /) -> Lock:
return Lock()
@locked_cacheproperty
def request_alock(self, /) -> AsyncLock:
return AsyncLock()
@property
def cookies_str(self, /) -> P115Cookies:
"""所有名为 *ID 的 cookie 值
"""
return P115Cookies(self.cookies)
def _read_cookies(self, encoding: str = "latin-1", /):
if cookies_path := self.__dict__.get("cookies_path"):
try:
with cookies_path.open("rb") as f:
cookies = str(f.read(), encoding)
if cookies:
update_cookies(self.cookies, cookies_to_dict(cookies))
except OSError:
pass
def _write_cookies(self, encoding: str = "latin-1", /):
if cookies_path := self.__dict__.get("cookies_path"):
cookies_bytes = bytes(self.cookies_str, encoding)
with cookies_path.open("wb") as f:
f.write(cookies_bytes)
def update_cookies(
self,
cookies: None | str | CookieJar | BaseCookie | Mapping[str, Any] | Iterable[Any] | ClientRequestMixin = None,
/,
):
"""更新 cookies(如果为 None 则是清空)
"""
if isinstance(cookies, ClientRequestMixin):
cookies = cookies.cookies
if cookies is None:
self.cookies.clear()
else:
if isinstance(cookies, str):
cookies = cookies_to_dict(cookies.strip().rstrip(";"))
if cookies:
update_cookies(self.cookies, cookies)
self._write_cookies()
def request(
self,
/,
url: str,
method: str = "GET",
payload: Any = None,
*,
check: bool = False,
ecdh_encrypt: bool = False,
request: None | Callable = None,
async_: Literal[False, True] = False,
**request_kwargs,
):
"""执行网络请求
:param url: HTTP 的请求链接
:param method: HTTP 的请求方法
:param payload: HTTP 的请求载体(``method`` 为 "POST" 或 "PUT" 时,视为 ``data``,否则视为 ``params``)
:param ecdh_encrypt: 是否加密通信,如果是 open 接口则无效
:param check: 是否检查响应
:param request: HTTP 请求调用,如果为 None,则用默认设置
如果传入调用,则必须至少能接受以下几个关键词参数(如果接收到了不用的参数,也要能自动忽略):
- url: HTTP 的请求链接
- method: HTTP 的请求方法
- params: HTTP 的请求链接附加的查询参数
- data: HTTP 的请求体
- json: JSON 数据(往往未被序列化)作为请求体
- files: 要用 multipart 上传的若干文件
- headers: HTTP 的请求头
- follow_redirects: 是否跟进重定向,默认值为 True
- raise_for_status: 是否对响应码 >= 400 时抛出异常
- cookies: 至少能接受 ``http.cookiejar.CookieJar`` 和 ``http.cookies.BaseCookie``,会因响应头的 "set-cookie" 而更新
- parse: 解析 HTTP 响应的方法,默认会构建一个 Callable,会把响应的字节数据视为 JSON 进行反序列化解析
- 如果为 None,则直接把响应对象返回
- 如果为 ...(Ellipsis),则把响应对象关闭后将其返回
- 如果为 True,则根据响应头来确定把响应得到的字节数据解析成何种格式(反序列化),请求也会被自动关闭
- 如果为 False,则直接返回响应得到的字节数据,请求也会被自动关闭
- 如果为 Callable,则使用此调用来解析数据,接受 1-2 个位置参数,并把解析结果返回给 `request` 的调用者,请求也会被自动关闭
- 如果只接受 1 个位置参数,则把响应对象传给它
- 如果能接受 2 个位置参数,则把响应对象和响应得到的字节数据(响应体)传给它
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 直接返回 `request` 执行请求后的返回值
.. note::
`request` 可以由不同的请求库来提供,下面是封装了一些模块
1. `httpcore_request <https://pypi.org/project/httpcore_request/>`_,由 `httpcore <https://pypi.org/project/httpcore/>`_ 封装,支持同步和异步请求
.. code:: python
from httpcore_request import request
2. `httpx_request <https://pypi.org/project/httpx_request/>`_,由 `httpx <https://pypi.org/project/httpx/>`_ 封装,支持同步和异步请求
.. code:: python
from httpx_request import request
3. `http_client_request <https://pypi.org/project/http_client_request/>`_,由 `http.client <https://docs.python.org/3/library/http.client.html>`_ 封装,支持同步请求
.. code:: python
from http_client_request import request
4. `python-urlopen <https://pypi.org/project/python-urlopen/>`_,由 `urllib.request.urlopen <https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen>`_ 封装,支持同步请求
.. code:: python
from urlopen import request
5. `urllib3_request <https://pypi.org/project/urllib3_request/>`_,由 `urllib3 <https://pypi.org/project/urllib3/>`_ 封装,支持同步请求
.. code:: python
from urllib3_request import request
6. `requests_request <https://pypi.org/project/requests_request/>`_,由 `requests <https://pypi.org/project/requests/>`_ 封装,支持同步请求
.. code:: python
from requests_request import request
7. `aiohttp_client_request <https://pypi.org/project/aiohttp_client_request/>`_,由 `aiohttp <https://pypi.org/project/aiohttp/>`_ 封装,支持异步请求
.. code:: python
from aiohttp_client_request import request
8. `blacksheep_client_request <https://pypi.org/project/blacksheep_client_request/>`_,由 `blacksheep <https://pypi.org/project/blacksheep/>`_ 封装,支持异步请求
.. code:: python
from blacksheep_client_request import request
9. `asks_request <https://pypi.org/project/asks_request/>`_,由 `asks <https://pypi.org/project/asks/>`_ 封装,支持异步请求
.. code:: python
from asks_request import request
10. `pycurl_request <https://pypi.org/project/pycurl_request/>`_,由 `pycurl <https://pypi.org/project/pycurl/>`_ 封装,支持同步请求
.. code:: python
from pycurl_request import request
11. `curl_cffi_request <https://pypi.org/project/curl_cffi_request/>`_,由 `curl_cffi <https://pypi.org/project/curl_cffi/>`_ 封装,支持同步和异步请求
.. code:: python
from curl_cffi_request import request
12. `aiosonic_request <https://pypi.org/project/aiosonic_request/>`_,由 `aiosonic <https://pypi.org/project/aiosonic/>`_ 封装,支持异步请求
.. code:: python
from aiosonic_request import request
13. `tornado_client_request <https://pypi.org/project/tornado_client_request/>`_,由 `tornado <https://www.tornadoweb.org/en/latest/httpclient.html>`_ 封装,支持同步和异步请求
.. code:: python
from tornado_client_request import request
14. `urllib3_future_request <https://pypi.org/project/urllib3_future_request/>`_,由 `urllib3.future <https://urllib3future.readthedocs.io/en/latest/>`_ 封装,支持同步和异步请求
.. code:: python
from urllib3_future_request import request
15. `niquests_request <https://pypi.org/project/niquests_request/>`_,由 `niquests <https://niquests.readthedocs.io/en/latest/>`_ 封装,支持同步和异步请求
.. code:: python
from niquests_request import request
"""
headers = self.headers.copy()
headers.update(request_kwargs.pop("headers", None) or ())
request, request_kwargs = get_request(
url=url,
method=method,
payload=payload,
headers=headers,
ecdh_encrypt=ecdh_encrypt,
request=request,
**request_kwargs,
)
headers = request_kwargs["headers"]
if URL(url).path.startswith("/open/"):
if "authorization" not in headers:
assert isinstance(self, P115OpenClient)
def gen_step():
if async_:
lock: Lock | AsyncLock = self.request_alock
else:
lock = self.request_lock
if not hasattr(self, "access_token"):
yield lock.acquire()
try:
if not getattr(self, "access_token", None):
if getattr(self, "refresh_token", None):
yield self.refresh_access_token(async_=async_)
elif hasattr(self, "login_another_open"):
yield self.login_another_open(replace=True, async_=async_)
else:
raise RuntimeError("can't get access token")
finally:
lock.release()
while True:
access_token = self.access_token
headers["authorization"] = "Bearer " + access_token
resp = yield request(async_=async_, **request_kwargs)
if resp.get("errno") != 40140125:
if check:
return check_response(resp)
return resp
yield lock.acquire()
try:
if access_token == self.access_token:
yield self.refresh_access_token(async_=async_)
finally:
lock.release()
return run_gen_step(gen_step, async_)
elif "cookie" not in headers:
request_kwargs["cookies"] = self.cookies
resp = request(async_=async_, **request_kwargs)
if check:
return check_response(resp)
return resp
def open(
self,
url: str,
/,
start: int | str = 0,
urlopen: None | Callable = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
):
"""打开下载链接,返回响应对象
:param url: 下载链接
:param start: 开始索引,从 0 开始
:param async_: 是否异步
:param request_kwargs: 其它请求参数
:return: 响应对象
"""
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers") or ())
headers.setdefault("user-agent", "")
if isinstance(start, str):
if not start.startswith("bytes="):
start = "bytes=" + start
elif start >= 0:
start = f"bytes={start}-"
else:
start = f"bytes={start}"
headers["range"] = start
if isinstance(url, P115URL):
headers.update(url.get("headers") or ())
if urlopen is None:
from urllib3_future_request import request as urlopen
urlopen = cast(Callable, urlopen)
return urlopen(url, async_=async_, **request_kwargs)
########## App API ##########
@overload
@staticmethod
def app_area_list(
base_url: str | Callable[[], str] = "https://cdnres.115.com",
*,
async_: Literal[False] = False,
**request_kwargs
) -> dict:
...
@overload
@staticmethod
def app_area_list(
base_url: str | Callable[[], str] = "https://cdnres.115.com",
*,
async_: Literal[True],
**request_kwargs
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def app_area_list(
base_url: str | Callable[[], str] = "https://cdnres.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs
) -> dict | Coroutine[Any, Any, dict]:
"""获取地区编码列表
GET https://cdnres.115.com/my/m_r/setting_new/js/ylmf_area.js
"""
api = complete_url("/my/m_r/setting_new/js/ylmf_area.js", base_url=base_url)
def iter_area(data: dict, /) -> Iterator[tuple[int, str]]:
for code, detail in data.items():
if isinstance(code, str):
continue
if isinstance(detail, dict):
yield code, detail["n"]
for key in ("c", "t"):
if key in detail and detail[key]:
yield from iter_area(detail[key])
break
else:
yield code, detail
def parse(_, content, /):
data_str = cast(Match[str], CRE_AREA_DATA_search(content.decode("utf-8")))[0]
data = eval(data_str, {"n": "n", "c": "c", "t": "t", "l": "l"})
return {"state": True, "data": list(iter_area(data))}
request_kwargs.setdefault("parse", parse)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def app_face_codes(
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def app_face_codes(
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def app_face_codes(
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取表情包
GET https://my.115.com/api/face_code.js
"""
api = complete_url("/api/face_code.js", base_url=base_url)
def parse(_, content, /):
return json_loads(content[25:-1])
request_kwargs.setdefault("parse", parse)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def app_publick_key(
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs
) -> dict:
...
@overload
@staticmethod
def app_publick_key(
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[True],
**request_kwargs
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def app_publick_key(
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs
) -> dict | Coroutine[Any, Any, dict]:
"""获取 RSA 加密公钥,用于某些情况下的加密
GET https://passportapi.115.com/app/1.0/{app}/1.0/login/getKey
.. note::
返回的公钥是签名证书,并经过 BASE64 处理,可用下面步骤还原
.. code:: python
from base64 import b64decode
from p115client import P115Client
resp = P115Client.app_publick_key()
perm = b64decode(resp["data"]["key"])
# pip install pycryptodome
from Crypto.PublicKey import RSA
pubkey = RSA.import_key(perm)
print(repr(pubkey))
"""
api = complete_url(f"/app/1.0/{app}/1.0/login/getKey", base_url=base_url)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def app_version_list(
base_url: str | Callable[[], str] = "https://appversion.115.com",
*,
async_: Literal[False] = False,
**request_kwargs
) -> dict:
...
@overload
@staticmethod
def app_version_list(
base_url: str | Callable[[], str] = "https://appversion.115.com",
*,
async_: Literal[True],
**request_kwargs
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def app_version_list(
base_url: str | Callable[[], str] = "https://appversion.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前各平台最新版 115 app 下载链接
GET https://appversion.115.com/1.0/web/1.0/api/chrome
"""
api = complete_url("/1.0/web/1.0/api/chrome", base_url=base_url)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def app_version_list2(
base_url: str | Callable[[], str] = "https://appversion.115.com",
*,
async_: Literal[False] = False,
**request_kwargs
) -> dict:
...
@overload
@staticmethod
def app_version_list2(
base_url: str | Callable[[], str] = "https://appversion.115.com",
*,
async_: Literal[True],
**request_kwargs
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def app_version_list2(
base_url: str | Callable[[], str] = "https://appversion.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前各平台最新版 115 app 下载链接
GET https://appversion.115.com/1.0/web/1.0/api/getMultiVer
"""
api = complete_url("/1.0/web/1.0/api/getMultiVer", base_url=base_url)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
########## Qrcode API ##########
@overload
@staticmethod
def login_authorize_open(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_authorize_open(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_authorize_open(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""授权码方式请求开放接口应用授权
GET https://qrcodeapi.115.com/open/authorize
.. admonition:: Reference
https://www.yuque.com/115yun/open/okr2cq0wywelscpe#EiOrD
.. note::
同一个开放应用 id,最多同时有 3 个登入,如果有新的登录,则自动踢掉较早的那一个
:payload:
- client_id: int | str 💡 AppID
- redirect_uri: str 💡 授权成功后重定向到指定的地址并附上授权码 code,需要先到 https://open.115.com/ 应用管理应用域名设置
- response_type: str = "code" 💡 授权模式,固定为 code,表示授权码模式
- state: int | str = <default> 💡 随机值,会通过 redirect_uri 原样返回,可用于验证以防 MITM 和 CSRF
"""
api = complete_url("/open/authorize", base_url=base_url)
request_kwargs["follow_redirects"] = False
def parse(resp, content, /):
if get_status_code(resp) == 302:
return {
"state": True,
"url": resp.headers["location"],
"data": dict(parse_qsl(urlsplit(resp.headers["location"]).query)),
"headers": dict(resp.headers),
}
else:
return json_maybe_decrypt_loads(content)
request_kwargs.setdefault("parse", parse)
request, request_kwargs = get_request(
url=api, params={"response_type": "code", **payload}, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_authorize_access_token_open(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_authorize_access_token_open(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_authorize_access_token_open(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""用授权码获取开放接口应用的 access_token
POST https://qrcodeapi.115.com/open/authCodeToToken
.. admonition:: Reference
https://www.yuque.com/115yun/open/okr2cq0wywelscpe#JnDgl
:payload:
- client_id: int | str 💡 AppID
- client_secret: str 💡 AppSecret
- code: str 💡 授权码,/open/authCodeToToken 重定向地址里面
- redirect_uri: str 💡 与 /open/authCodeToToken 传的 redirect_uri 一致,可用于验证以防 MITM 和 CSRF
- grant_type: str = "authorization_code" 💡 授权类型,固定为 authorization_code,表示授权码类型
"""
api = complete_url("/open/authCodeToToken", base_url=base_url)
request, request_kwargs = get_request(
url=api,
method="POST",
data={"grant_type": "authorization_code", **payload},
**request_kwargs,
)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_qrcode(
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> bytes:
...
@overload
@staticmethod
def login_qrcode(
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, bytes]:
...
@staticmethod
def login_qrcode(
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> bytes | Coroutine[Any, Any, bytes]:
"""下载登录二维码图片
GET https://qrcodeapi.115.com/api/1.0/{app}/1.0/qrcode
:param uid: 二维码的 uid
:return: 图片的二进制数据(PNG 图片)
"""
api = complete_url(f"/api/1.0/{app}/1.0/qrcode", base_url=base_url)
if isinstance(payload, str):
payload = {"uid": payload}
request_kwargs.setdefault("parse", False)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_qrcode_access_token_open(
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_qrcode_access_token_open(
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_qrcode_access_token_open(
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""绑定扫码并获取开放平台应用的 access_token 和 refresh_token
POST https://qrcodeapi.115.com/open/deviceCodeToToken
.. admonition:: Reference
https://www.yuque.com/115yun/open/shtpzfhewv5nag11#QCCVQ
:payload:
- uid: str
- code_verifier: str = <default> 💡 默认字符串是 64 个 "0"
"""
api = complete_url("/open/deviceCodeToToken", base_url=base_url)
if isinstance(payload, str):
payload = {"uid": payload, "code_verifier": _default_code_verifier}
request, request_kwargs = get_request(
url=api, method="POST", data=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
def login_qrcode_scan(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_qrcode_scan(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def login_qrcode_scan(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""扫描二维码,payload 数据取自 `login_qrcode_token` 接口响应
GET https://qrcodeapi.115.com/api/2.0/prompt.php
:payload:
- uid: str
"""
api = complete_url("/api/2.0/prompt.php", base_url=base_url)
if isinstance(payload, str):
payload = {"uid": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def login_qrcode_scan_cancel(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_qrcode_scan_cancel(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def login_qrcode_scan_cancel(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""取消扫描二维码,payload 数据取自 `login_qrcode_scan` 接口响应
GET https://qrcodeapi.115.com/api/2.0/cancel.php
:payload:
- key: str
- uid: str
- client: int = 0
"""
api = complete_url("/api/2.0/cancel.php", base_url=base_url)
if isinstance(payload, str):
payload = {"key": payload, "uid": payload, "client": 0}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def login_qrcode_scan_confirm(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_qrcode_scan_confirm(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def login_qrcode_scan_confirm(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""确认扫描二维码,payload 数据取自 `login_qrcode_scan` 接口响应
GET https://qrcodeapi.115.com/api/2.0/slogin.php
:payload:
- key: str
- uid: str
- client: int = 0
"""
api = complete_url("/api/2.0/slogin.php", base_url=base_url)
if isinstance(payload, str):
payload = {"key": payload, "uid": payload, "client": 0}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
@staticmethod
def login_qrcode_scan_result(
uid: str,
app: str = "alipaymini",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_qrcode_scan_result(
uid: str,
app: str = "alipaymini",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_qrcode_scan_result(
uid: str,
app: str = "alipaymini",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取扫码登录的结果,包含 cookie
POST https://qrcodeapi.115.com/app/1.0/{app}/1.0/login/qrcode/
.. note::
如果报错“IP登录异常”,那么要到次日零点才能解禁(用 VPN 可以绕过),其中尤其是 `app="web"` 最容易遇到此问题
:param uid: 扫码的 uid
:param app: 绑定的 app
:param request: 自定义请求函数
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口返回值
"""
if app == "desktop":
app = "web"
elif app in ("windows", "mac", "linux"):
app = "os_" + app
elif app in ("ios", "qios", "ipad", "qipad"):
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers", ()))
match app:
case "ios":
headers["user-agent"] = "UPhone/1.0.0"
case "qios":
headers["user-agent"] = "OfficePhone/1.0.0"
case "ipad":
headers["user-agent"] = "UPad/1.0.0"
case "qipad":
headers["user-agent"] = "OfficePad/1.0.0"
app = "ios"
api = complete_url(f"/app/1.0/{app}/1.0/login/qrcode/", base_url=base_url)
request, request_kwargs = get_request(
url=api, method="POST", data={"account": uid}, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_qrcode_scan_status(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_qrcode_scan_status(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_qrcode_scan_status(
payload: dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取二维码的状态(未扫描、已扫描、已登录、已取消、已过期等),payload 数据取自 `login_qrcode_token` 接口响应
GET https://qrcodeapi.115.com/get/status/
.. admonition:: Reference
https://www.yuque.com/115yun/open/shtpzfhewv5nag11#lAsp2
:payload:
- uid: str
- time: int
- sign: str
"""
api = complete_url("/get/status/", base_url=base_url)
request, request_kwargs = get_request(
url=api, params=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_qrcode_token(
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_qrcode_token(
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_qrcode_token(
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取登录二维码,扫码可用
GET https://qrcodeapi.115.com/api/1.0/{app}/1.0/token/
"""
api = complete_url(f"/api/1.0/{app}/1.0/token/", base_url=base_url)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_qrcode_token_open(
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_qrcode_token_open(
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_qrcode_token_open(
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取开放平台的登录二维码,扫码可用,采用 PKCE (Proof Key for Code Exchange)
POST https://qrcodeapi.115.com/open/authDeviceCode
.. admonition:: Reference
https://www.yuque.com/115yun/open/shtpzfhewv5nag11#WzRhM
.. tip::
此接口也可用于检测 app_id 是否可用
.. note::
同一个开放应用 id,最多同时有 3 个登入,如果有新的登录,则自动踢掉较早的那一个
.. note::
code_challenge 默认用的字符串为 64 个 0,hash 算法为 md5
.. tip::
如果仅仅想要检查 AppID 是否有效,可以用如下的代码:
.. code:: python
from p115client import P115Client
app_id = 100195125
response = P115Client.login_qrcode_token_open(app_id)
if response["code"]:
print("无效 AppID:", app_id, "因为:", response["error"])
else:
print("有效 AppID:", app_id)
.. tip::
如果想要罗列出所有可用的 AppID,可以用如下的代码:
.. code:: python
from itertools import count
from p115client import P115Client
get_qrcode_token = P115Client.login_qrcode_token_open
for app_id in count(100195125, 2):
response = get_qrcode_token(app_id)
if not response["code"]:
print(app_id)
:payload:
- client_id: int | str 💡 AppID
- code_challenge: str = <default> 💡 PKCE 相关参数,计算方式如下
.. code:: python
from base64 import b64encode
from hashlib import sha256
from secrets import token_bytes
# code_verifier 可以是 43~128 位随机字符串
code_verifier = token_bytes(64).hex()
code_challenge = b64encode(sha256(code_verifier.encode()).digest()).decode()
- code_challenge_method: str = <default> 💡 计算 `code_challenge` 的 hash 算法,支持 "md5", "sha1", "sha256"
"""
api = complete_url("/open/authDeviceCode", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {
"client_id": payload,
"code_challenge": _default_code_challenge,
"code_challenge_method": _default_code_challenge_method,
}
request, request_kwargs = get_request(
url=api, method="POST", data=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def login_refresh_token_open(
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def login_refresh_token_open(
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def login_refresh_token_open(
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""用一个 refresh_token 去获取新的 access_token 和 refresh_token,然后原来的 refresh_token 作废
POST https://qrcodeapi.115.com/open/refreshToken
.. admonition:: Reference
https://www.yuque.com/115yun/open/shtpzfhewv5nag11#ve54x
https://www.yuque.com/115yun/open/opnx8yezo4at2be6
:payload:
- refresh_token: str
"""
api = complete_url("/open/refreshToken", base_url=base_url)
if isinstance(payload, str):
payload = {"refresh_token": payload}
request, request_kwargs = get_request(
url=api, method="POST", data=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@classmethod
def login_with_qrcode(
cls,
/,
app: None | str = "",
console_qrcode: bool = True,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@classmethod
def login_with_qrcode(
cls,
/,
app: None | str = "",
console_qrcode: bool = True,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@classmethod
def login_with_qrcode(
cls,
/,
app: None | str = "",
console_qrcode: bool = True,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""二维码扫码登录
.. hint::
仅获取响应,如果需要更新此 `client` 的 `cookies`,请直接用 `login` 方法
:param app: 扫二维码后绑定的 `app` (或者叫 `device`)
:param console_qrcode: 在命令行输出二维码,否则在浏览器中打开
:param base_url: 接口的基地址
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 响应信息,如果 `app` 为 None 或 "",则返回二维码信息,否则返回绑定扫码后的信息(包含 cookies)
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def gen_step():
nonlocal console_qrcode
resp = yield cls.login_qrcode_token(
async_=async_,
base_url=base_url,
**request_kwargs,
)
qrcode_token = resp["data"]
login_uid = qrcode_token["uid"]
qrcode = qrcode_token.pop("qrcode", "")
if not qrcode:
qrcode = "https://115.com/scan/dg-" + login_uid
if not console_qrcode:
try:
from startfile import startfile, startfile_async
except ImportError:
console_qrcode = True
if console_qrcode:
from qrcode import QRCode # type: ignore
qr = QRCode(border=1)
qr.add_data(qrcode)
qr.print_ascii(tty=isatty(1))
else:
url = complete_url("/api/1.0/web/1.0/qrcode", base_url=base_url, query={"uid": login_uid})
if async_:
yield startfile_async(url)
else:
startfile(url)
while True:
try:
resp = yield cls.login_qrcode_scan_status(
qrcode_token,
base_url=base_url,
async_=async_,
**request_kwargs,
)
except Exception:
continue
match resp["data"].get("status"):
case 0:
print("[status=0] qrcode: waiting")
case 1:
print("[status=1] qrcode: scanned")
case 2:
print("[status=2] qrcode: signed in")
break
case -1:
raise P115LoginError(errno.EAUTH, "[status=-1] qrcode: expired")
case -2:
raise P115LoginError(errno.EAUTH, "[status=-2] qrcode: canceled")
case _:
raise P115LoginError(errno.EAUTH, f"qrcode: aborted with {resp!r}")
if app:
return cls.login_qrcode_scan_result(
login_uid,
app=app,
base_url=base_url,
async_=async_,
**request_kwargs,
)
else:
return qrcode_token
return run_gen_step(gen_step, async_)
@overload
@classmethod
def login_with_app_id(
cls,
/,
app_id: int,
console_qrcode: bool = True,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@classmethod
def login_with_app_id(
cls,
/,
app_id: int,
console_qrcode: bool = True,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@classmethod
def login_with_app_id(
cls,
/,
app_id: int,
console_qrcode: bool = True,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""二维码扫码登录开放平台
:param console_qrcode: 在命令行输出二维码,否则在浏览器中打开
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 响应信息
"""
def gen_step():
nonlocal console_qrcode
resp = yield cls.login_qrcode_token_open(
app_id,
base_url=base_url,
async_=async_,
**request_kwargs,
)
qrcode_token = resp["data"]
login_uid = qrcode_token["uid"]
qrcode = qrcode_token.pop("qrcode", "")
if not qrcode:
qrcode = "https://115.com/scan/dg-" + login_uid
if not console_qrcode:
try:
from startfile import startfile, startfile_async
except ImportError:
console_qrcode = True
if console_qrcode:
from qrcode import QRCode # type: ignore
qr = QRCode(border=1)
qr.add_data(qrcode)
qr.print_ascii(tty=isatty(1))
else:
url = complete_url("/api/1.0/web/1.0/qrcode", base_url=base_url, query={"uid": login_uid})
if async_:
yield startfile_async(url)
else:
startfile(url)
while True:
try:
resp = yield cls.login_qrcode_scan_status(
qrcode_token,
base_url=base_url,
async_=async_,
**request_kwargs,
)
except Exception:
continue
match resp["data"].get("status"):
case 0:
print("[status=0] qrcode: waiting")
case 1:
print("[status=1] qrcode: scanned")
case 2:
print("[status=2] qrcode: signed in")
break
case -1:
raise P115LoginError(errno.EAUTH, "[status=-1] qrcode: expired")
case -2:
raise P115LoginError(errno.EAUTH, "[status=-2] qrcode: canceled")
case _:
raise P115LoginError(errno.EAUTH, f"qrcode: aborted with {resp!r}")
return cls.login_qrcode_access_token_open(
login_uid,
base_url=base_url,
async_=async_,
**request_kwargs,
)
return run_gen_step(gen_step, async_)
########## Upload API ##########
@overload
@staticmethod
def upload_gettoken(
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def upload_gettoken(
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def upload_gettoken(
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取阿里云 OSS 的 token(上传凭证)
GET https://uplb.115.com/3.0/gettoken.php
"""
api = complete_url("/3.0/gettoken.php", base_url=base_url)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
@staticmethod
def upload_url(
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
@staticmethod
def upload_url(
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@staticmethod
def upload_url(
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用于上传的一些 http 接口,此接口具有一定幂等性,请求一次,然后把响应记下来即可
GET https://uplb.115.com/3.0/getuploadinfo.php
:response:
- endpoint: 此接口用于上传文件到阿里云 OSS
- gettokenurl: 上传前需要用此接口获取 token
"""
api = complete_url("/3.0/getuploadinfo.php", base_url=base_url)
request, request_kwargs = get_request(url=api, **request_kwargs)
return request(async_=async_, **request_kwargs)
# TODO: 支持对 access_token 和 refresh_token 保存到本地,就像 cookies 一样
[docs]
class P115OpenClient(ClientRequestMixin):
"""115 的客户端对象
.. admonition:: Reference
https://www.yuque.com/115yun/open
:param access_token: 访问令牌
:param refresh_token: 刷新令牌
:param app_id: 授权的 open 应用的 AppID
:param console_qrcode: 需要扫码登录时,是否在命令行输出二维码
"""
def __init__(
self,
/,
access_token: str = "",
refresh_token: str = "",
app_id: int = 0,
console_qrcode: bool = True,
):
self.init(
access_token=access_token,
refresh_token=refresh_token,
app_id=app_id,
console_qrcode=console_qrcode,
instance=self,
)
def __eq__(self, other, /) -> bool:
return type(self) is type(other) and self.user_id == other.user_id
def __hash__(self, /) -> int:
return id(self)
def __repr__(self, /) -> str:
cls = type(self)
return f"<{cls.__module__}.{cls.__qualname__}(app_id={self.app_id}) at {hex(id(self))}>"
@overload
@classmethod
def init(
cls,
/,
access_token: str = "",
refresh_token: str = "",
app_id: int = 0,
console_qrcode: bool = True,
instance: None | Self = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> Self:
...
@overload
@classmethod
def init(
cls,
/,
access_token: str = "",
refresh_token: str = "",
app_id: int = 0,
console_qrcode: bool = True,
instance: None | Self = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, Self]:
...
@classmethod
def init(
cls,
/,
access_token: str = "",
refresh_token: str = "",
app_id: int = 0,
console_qrcode: bool = True,
instance: None | Self = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> Self | Coroutine[Any, Any, Self]:
def gen_step():
if instance is None:
self = cls.__new__(cls)
else:
self = instance
self.app_id = app_id
self.access_token = access_token
self.refresh_token = refresh_token
if not access_token:
if refresh_token:
resp = yield self.login_refresh_token_open(
refresh_token,
async_=async_,
**request_kwargs,
)
else:
resp = yield self.login_with_app_id(
app_id or 100195125,
console_qrcode=console_qrcode,
async_=async_,
**request_kwargs,
)
check_response(resp)
data = resp["data"]
self.refresh_token = data["refresh_token"]
self.access_token = data["access_token"]
return self
return run_gen_step(gen_step, async_)
@locked_cacheproperty
def pickcode_stable_point(self, /) -> str:
"""获取 pickcode 的不动点,或者也叫本征值
.. todo::
不动点可能和用户 id 有某种联系,但目前样本不足,难以推断,以后再尝试分析
"""
from .util import get_stable_point, set_stable_point
try:
return get_stable_point(self.user_id)
except KeyError:
resp = self.fs_files({"show_dir": 1, "limit": 1, "cid": 0})
check_response(resp)
if resp["data"]:
info = resp["data"][0]
pickcode = info["pc"]
else:
if hasattr(self, "upload_file_sample"):
resp = self.upload_file_sample(b"", "U_120_0")
check_response(resp)
pickcode = resp["data"]["pick_code"]
else:
resp = self.upload_file(b"", filename="test")
pickcode = resp["data"]["pickcode"]
try:
self.fs_delete(self.to_id(pickcode))
except Exception:
pass
return set_stable_point(self.user_id, pickcode)
@overload
def refresh_access_token(
self,
/,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def refresh_access_token(
self,
/,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def refresh_access_token(
self,
/,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""更新 access_token 和 refresh_token (⚠️ 目前是 7200 秒内就要求刷新一次)
"""
def gen_step():
if refresh_token := getattr(self, "refresh_token", ""):
resp = yield self.login_refresh_token_open(
refresh_token,
async_=async_,
**request_kwargs,
)
elif app_id := self.app_id:
if hasattr(self, "login_with_open"):
resp = yield self.login_with_open(
app_id,
async_=async_,
**request_kwargs,
)
else:
resp = yield self.login_with_app_id(
app_id,
console_qrcode=True,
async_=async_,
**request_kwargs,
)
else:
raise RuntimeError("no `refresh_token` or `app_id` provided")
check_response(resp)
data = resp["data"]
self.refresh_token = data["refresh_token"]
self.access_token = data["access_token"]
return data
return run_gen_step(gen_step, async_)
########## Cloud Download API ##########
@overload
def clouddownload_quota_info(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_quota_info(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_quota_info(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取云下载配额信息
GET https://proapi.115.com/open/offline/get_quota_info
.. admonition:: Reference
https://www.yuque.com/115yun/open/gif2n3smh54kyg0p
"""
api = complete_url("/open/offline/get_quota_info", base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def clouddownload_task_add_bt(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_add_bt(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_add_bt(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加云下载 BT 任务
POST https://proapi.115.com/open/offline/add_task_bt
.. admonition:: Reference
https://www.yuque.com/115yun/open/svfe4unlhayvluly
:payload:
- info_hash: str 💡 种子文件的 info_hash
- pick_code: str 💡 种子文件的提取码
- save_path: str 💡 保存到 `wp_path_id` 对应目录下的相对路径
- torrent_sha1: str 💡 种子文件的 sha1
- wanted: str 💡 选择文件进行下载(是数字索引,从 0 开始计数,用 "," 分隔)
- wp_path_id: int | str = <default> 💡 保存目标目录 id
"""
api = complete_url("/open/offline/add_task_bt ", base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_task_add_urls(
self,
payload: str | Iterable[str] | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_add_urls(
self,
payload: str | Iterable[str] | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_add_urls(
self,
payload: str | Iterable[str] | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加云下载链接任务
POST https://proapi.115.com/open/offline/add_task_urls
.. admonition:: Reference
https://www.yuque.com/115yun/open/zkyfq2499gdn3mty
:payload:
- urls: str 💡 链接,用 "\\n" 分隔,支持HTTP、HTTPS、FTP、磁力链和电驴链接
- wp_path_id: int | str = <default> 💡 保存到目录的 id
"""
api = complete_url("/open/offline/add_task_urls", base_url)
if isinstance(payload, str):
payload = {"urls": payload.strip("\n")}
elif not isinstance(payload, dict):
payload = {"urls": ",".join(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_task_clear(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_clear(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_clear(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""清空云下载任务
POST https://proapi.115.com/open/offline/clear_task
.. admonition:: Reference
https://www.yuque.com/115yun/open/uu5i4urb5ylqwfy4
:payload:
- flag: int = 0 💡 标识,用于对应某种情况
- 0: 已完成
- 1: 全部
- 2: 已失败
- 3: 进行中
- 4: 已完成+删除源文件
- 5: 全部+删除源文件
"""
api = complete_url("/open/offline/clear_task", base_url)
if isinstance(payload, int):
payload = {"flag": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_task_del(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_del(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_del(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除云下载任务
POST https://proapi.115.com/open/offline/del_task
.. admonition:: Reference
https://www.yuque.com/115yun/open/pmgwc86lpcy238nw
:payload:
- info_hash: str 💡 待删除任务的 info_hash
- del_source_file: 0 | 1 = <default> 💡 是否删除源文件 1:删除 0:不删除
"""
api = complete_url("/open/offline/del_task", base_url)
if isinstance(payload, str):
payload = {"info_hash": payload}
return self.request(api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_task_list(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_list(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_list(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取云下载任务列表
GET https://proapi.115.com/open/offline/get_task_list
.. admonition:: Reference
https://www.yuque.com/115yun/open/av2mluz7uwigz74k
:payload:
- page: int = 1
"""
api = complete_url("/open/offline/get_task_list", base_url)
if isinstance(payload, int):
payload = {"page": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_torrent(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_torrent(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_torrent(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""解析 BT 种子
POST https://proapi.115.com/open/offline/torrent
.. admonition:: Reference
https://www.yuque.com/115yun/open/evez3u50cemoict1
:payload:
- torrent_sha1: str 💡 种子文件的 sha1
- pick_code: str 💡 种子文件的提取码
"""
api = complete_url("/open/offline/torrent", base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Download API ##########
@overload
def download_url(
self,
pickcode: int | str,
/,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115URL:
...
@overload
def download_url(
self,
pickcode: int | str,
/,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115URL]:
...
[docs]
def download_url(
self,
pickcode: int | str,
/,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115URL | Coroutine[Any, Any, P115URL]:
"""获取文件的下载链接,此接口是对 `download_url_info` 的封装
.. note::
获取的直链中,部分查询参数的解释:
- ``t``: 过期时间戳
- ``u``: 用户 id
- ``c``: 允许同时打开次数,如果为 0,则是无限次数
- ``f``: 请求时要求携带请求头
- 如果为空,则无要求
- 如果为 1,则需要 user-agent(和请求直链时的一致)
- 如果为 3,则需要 user-agent(和请求直链时的一致) 和 Cookie(由请求直链时的响应所返回的 Set-Cookie 响应头)
:param pickcode: id 或者提取码
:param strict: 如果为 True,当目标是目录时,会抛出 IsADirectoryError 异常
:param user_agent: 如果不为 None,则作为请求头 "user-agent" 的值
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 下载链接
"""
pickcode = self.to_pickcode(pickcode)
def gen_step():
resp = yield self.download_url_info_open(
pickcode,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["pickcode"] = pickcode
resp["is_download"] = True
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
check_response(resp)
for fid, info in data.items():
url = info["url"]
if strict and not url:
throw(
errno.EISDIR,
f"{fid} is a directory, with response {resp}",
)
return P115URL(
url["url"] if url else "",
id=int(fid),
pickcode=info["pick_code"],
name=info["file_name"],
size=int(info["file_size"]),
sha1=info["sha1"],
is_dir=not url,
headers=resp["headers"],
)
throw(
errno.ENOENT,
f"no such pickcode: {pickcode!r}, with response {resp}",
)
return run_gen_step(gen_step, async_)
@overload
def download_urls(
self,
pickcodes: int | str | Iterable[int | str],
/,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict[int, P115URL]:
...
@overload
def download_urls(
self,
pickcodes: int | str | Iterable[int | str],
/,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict[int, P115URL]]:
...
[docs]
def download_urls(
self,
pickcodes: int | str | Iterable[int | str],
/,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict[int, P115URL] | Coroutine[Any, Any, dict[int, P115URL]]:
"""批量获取文件的下载链接,此接口是对 `download_url_info` 的封装
.. note::
获取的直链中,部分查询参数的解释:
- ``t``: 过期时间戳
- ``u``: 用户 id
- ``c``: 允许同时打开次数,如果为 0,则是无限次数
- ``f``: 请求时要求携带请求头
- 如果为空,则无要求
- 如果为 1,则需要 user-agent(和请求直链时的一致)
- 如果为 3,则需要 user-agent(和请求直链时的一致) 和 Cookie(由请求直链时的响应所返回的 Set-Cookie 响应头)
:param pickcodes: 提取码,多个用逗号 "," 隔开
:param strict: 如果为 True,当目标是目录时,会直接忽略
:param user_agent: 如果不为 None,则作为请求头 "user-agent" 的值
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 一批下载链接
"""
if isinstance(pickcodes, (int, str)):
pickcodes = self.to_pickcode(pickcodes)
else:
pickcodes = ",".join(map(self.to_pickcode, pickcodes))
def gen_step():
resp = yield self.download_url_info_open(
pickcodes,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["pickcode"] = pickcodes
resp["is_download"] = True
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
throw(errno.EIO, resp)
urls: dict[int, P115URL] = {}
if resp.get("errno") != 50003:
check_response(resp)
for fid, info in data.items():
url = info["url"]
if strict and not url:
continue
fid = int(fid)
urls[fid] = P115URL(
url["url"] if url else "",
id=fid,
pickcode=info["pick_code"],
name=info["file_name"],
size=int(info["file_size"]),
sha1=info["sha1"],
is_dir=not url,
headers=resp["headers"],
)
return urls
return run_gen_step(gen_step, async_)
@overload
def download_url_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_url_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_url_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件的下载链接
POST https://proapi.115.com/open/ufile/downurl
.. hint::
相当于 `P115Client.download_url_app(app="chrome")`
.. admonition:: Reference
https://www.yuque.com/115yun/open/um8whr91bxb5997o
:payload:
- pick_code: str 💡 提取码,多个用逗号 "," 隔开
"""
api = complete_url("/open/ufile/downurl", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers") or ())
if user_agent is None:
headers.setdefault("user-agent", "")
else:
headers["user-agent"] = user_agent
def parse(_, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
json["headers"] = headers
return json
request_kwargs.setdefault("parse", parse)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## File System API ##########
@overload
def fs_copy(
self,
payload: int | str | Iterable[int | str] | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_copy(
self,
payload: int | str | Iterable[int | str] | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_copy(
self,
payload: int | str | Iterable[int | str] | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""文件复制
POST https://proapi.115.com/open/ufile/copy
.. admonition:: Reference
https://www.yuque.com/115yun/open/lvas49ar94n47bbk
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- file_id: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- pid: int | str = 0 💡 父目录 id
- nodupli: 0 | 1 = 0 💡 复制的文件在目标目录是否允许重复:0:可以 1:不可以
"""
api = complete_url("/open/ufile/copy", base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif not isinstance(payload, dict):
payload = {"file_id": ",".join(map(str, payload))}
cast(dict, payload).setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_delete(
self,
payload: int | str | dict | Iterable[int | str],
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_delete(
self,
payload: int | str | dict | Iterable[int | str],
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_delete(
self,
payload: int | str | dict | Iterable[int | str],
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除文件或目录
POST https://proapi.115.com/open/ufile/delete
.. admonition:: Reference
https://www.yuque.com/115yun/open/kt04fu8vcchd2fnb
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- file_ids: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
"""
api = complete_url("/open/ufile/delete", base_url)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
elif not isinstance(payload, dict):
payload = {"file_ids": ",".join(map(str, payload))}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_files(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的文件列表和基本信息
GET https://proapi.115.com/open/ufile/files
.. hint::
相当于 ``P115Client.fs_files_app()``
.. admonition:: Reference
https://www.yuque.com/115yun/open/kz9ft9a7s57ep868
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,最大值不一定,看数据量,7,000 应该总是安全的,10,000 有可能报错,但有时也可以 20,000 而成功
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数
- cur: 0 | 1 = <default> 💡 是否只显示当前目录
- custom_order: 0 | 1 | 2 = <default> 💡 是否使用记忆排序。如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 2
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- fields: str = <default>
- for: str = <default> 💡 文件格式,例如 "doc"
- is_q: 0 | 1 = <default>
- is_share: 0 | 1 = <default>
- min_size: int = 0 💡 最小的文件大小
- max_size: int = 0 💡 最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_etime": 事件时间(无效,效果相当于 "user_utime")
- "user_utime": 修改时间
- "user_ptime": 创建时间(无效,效果相当于 "user_utime")
- "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
- qid: int = <default>
- r_all: 0 | 1 = <default>
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- scid: int | str = <default>
- show_dir: 0 | 1 = 1 💡 是否显示目录
- snap: 0 | 1 = <default>
- source: str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8: 其它
- 9: 相当于 8
- 10: 相当于 8
- 11: 相当于 8
- 12: ???
- 13: ???
- 14: ???
- 15: 图片和视频,相当于 2 和 4
- >= 16: 相当于 8
"""
api = complete_url("/open/ufile/files", base_url)
if payload is None:
return self.request(url=api, async_=async_, **request_kwargs)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {
"aid": 1, "count_folders": 1, "limit": 32, "offset": 0,
"record_open_time": 1, "show_dir": 1, "cid": 0, **payload,
}
if payload.keys() & frozenset(("asc", "fc_mix", "o")):
payload["custom_order"] = 2
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
method: str = "GET",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
method: str = "GET",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
method: str = "GET",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件或目录详情
GET https://proapi.115.com/open/folder/get_info
.. note::
支持 GET 和 POST 方法。`file_id` 和 `path` 需必传一个
.. hint::
具有 ``P115Client.fs_category_get()`` 的能力,而且更强,因为支持用 path 查询
.. caution::
尝试获取目录的信息时,会去计算目录中文件和目录的数量、总文件大小 等信息,可能会消耗大量时间,但短时间内再次查询同一目录,耗时可能会大大减小
.. admonition:: Reference
https://www.yuque.com/115yun/open/rl8zrhe2nag21dfw
:payload:
- file_id: int | str 💡 文件或目录的 id
- path: str = <default> 💡 文件或目录的路径。分隔符支持 / 和 > 两种符号,最前面需分隔符开头,以分隔符分隔目录层级
"""
api = complete_url("/open/folder/get_info", base_url)
if isinstance(payload, int):
payload = {"file_id": payload}
elif isinstance(payload, str):
if payload.startswith("0") or payload.strip(digits):
payload = {"path": payload}
else:
payload = {"file_id": payload}
if path := payload.get("path"):
if path.startswith(("/", ">")):
sep = path[0]
payload["path"] = sep + path.strip(sep)
elif ">" in path:
payload["path"] = "/" + path.rstrip("/")
else:
payload["path"] = ">" + path.rstrip(">")
if method.upper() == "POST":
request_kwargs["data"] = payload
else:
request_kwargs["params"] = payload
return self.request(url=api, method=method, async_=async_, **request_kwargs)
@overload
def fs_mkdir(
self,
payload: str | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_mkdir(
self,
payload: str | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_mkdir(
self,
payload: str | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建目录
POST https://proapi.115.com/open/folder/add
.. admonition:: Reference
https://www.yuque.com/115yun/open/qur839kyx9cgxpxi
:payload:
- file_name: str 💡 新建目录名称,限制 255 个字符
- pid: int | str = 0 💡 新建目录所在的父目录ID (根目录的ID为0)
"""
api = complete_url("/open/folder/add", base_url)
if isinstance(payload, str):
payload = {"pid": pid, "file_name": payload}
payload.setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_move(
self,
payload: int | str | Iterable[int | str] | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_move(
self,
payload: int | str | Iterable[int | str] | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_move(
self,
payload: int | str | Iterable[int | str] | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""文件移动
POST https://proapi.115.com/open/ufile/move
.. admonition:: Reference
https://www.yuque.com/115yun/open/vc6fhi2mrkenmav2
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- file_ids: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- to_cid: int | str = 0 💡 父目录 id
"""
api = complete_url("/open/ufile/move", base_url)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
elif not isinstance(payload, dict):
payload = {"file_ids": ",".join(map(str, payload))}
cast(dict, payload).setdefault("to_cid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_rename(
self,
payload: tuple[int | str, str] | dict,
/,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_rename(
self,
payload: tuple[int | str, str] | dict,
/,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_rename(
self,
payload: tuple[int | str, str] | dict,
/,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""重命名文件或目录,此接口是对 `fs_update_open` 的封装
.. caution::
改名时,虽然不能修改扩展名,但是一定要带上扩展名(无论是啥),不然会把最后一个句点 . 及其之后文字截断
:payload:
- file_id: int | str 💡 文件 id
- file_name: str 💡 文件名
"""
if isinstance(payload, tuple):
payload = {"file_id": payload[0], "file_name": payload[1]}
return self.fs_update_open(payload, async_=async_, **request_kwargs)
@overload
def fs_search(
self,
payload: str | dict = ".",
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_search(
self,
payload: str | dict = ".",
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_search(
self,
payload: str | dict = ".",
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""搜索文件或目录
GET https://proapi.115.com/open/ufile/search
.. attention::
最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
.. note::
这个方法似乎不支持仅搜索目录本身,搜索范围是从指定目录开始的整个目录树
.. hint::
相当于 ``P115Client.fs_search_app2()``
.. admonition:: Reference
https://www.yuque.com/115yun/open/ft2yelxzopusus38
:payload:
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- cid: int | str = 0 💡 目录 id。cid=-1 时,表示不返回列表任何内容
- count_folders: 0 | 1 = <default>
- date: str = <default> 💡 筛选日期
- fc: 0 | 1 = <default> 💡 只显示文件或目录。1:只显示目录 2:只显示文件
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- file_label: int | str = <default> 💡 标签 id
- gte_day: str 💡 搜索结果匹配的开始时间;格式:YYYY-MM-DD
- limit: int = 32 💡 一页大小,意思就是 page_size
- lte_day: str 💡 搜索结果匹配的结束时间;格式:YYYY-MM-DD
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- search_value: str = "." 💡 搜索文本,可以是 sha1
- source: str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 99: 所有文件
- version: str = <default> 💡 版本号,比如 3.1
"""
api = complete_url("/open/ufile/search", base_url)
if isinstance(payload, str):
payload = {"search_value": payload}
payload = {
"aid": 1, "cid": 0, "limit": 32, "offset": 0,
"show_dir": 1, "search_value": ".", **payload,
}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_star_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_star_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_star_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置或取消星标,此接口是对 `fs_update_open` 的封装
.. note::
即使其中任何一个 id 目前已经被删除,也可以操作成功
:payload:
- file_id: int | str 💡 只能传入 1 个
- file_id[0]: int | str 💡 如果有多个,则按顺序给出
- file_id[1]: int | str
- ...
- star: 0 | 1 = 1
"""
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif not isinstance(payload, dict):
payload = {f"file_id[{i}]": id for i, id in enumerate(payload)}
payload.setdefault("star", int(star))
return self.fs_update_open(payload, async_=async_, **request_kwargs)
@overload
def fs_video(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频在线播放地址(和视频文件相关数据)
GET https://proapi.115.com/open/video/play
.. admonition:: Reference
https://www.yuque.com/115yun/open/hqglxv3cedi3p9dz
.. hint::
需切换音轨时,在请求返回的播放地址中增加请求参数 `&audio_track=${index}`,值就是接口响应中 `multitrack_list` 中某个成员的索引,从 0 开始计数
:payload:
- pick_code: str 💡 文件提取码
- share_id: int | str = <default> 💡 共享 id,获取共享文件播放地址所需
"""
api = complete_url("/open/video/play", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_video_history(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_history(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_history(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频播放进度
GET https://proapi.115.com/open/video/history
.. admonition:: Reference
https://www.yuque.com/115yun/open/gssqdrsq6vfqigag
:payload:
- pick_code: str 💡 文件提取码
"""
api = complete_url("/open/video/history", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_video_history_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_history_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_history_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""记忆视频播放进度
POST https://proapi.115.com/open/video/history
.. admonition:: Reference
https://www.yuque.com/115yun/open/bshagbxv1gzqglg4
:payload:
- pick_code: str 💡 文件提取码
- time: int = <default> 💡 视频播放进度时长 (单位秒)
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
"""
api = complete_url("/open/video/history", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_push(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_push(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_push(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""提交视频转码
POST https://proapi.115.com/open/video/video_push
.. admonition:: Reference
https://www.yuque.com/115yun/open/nxt8r1qcktmg3oan
:payload:
- pick_code: str 💡 文件提取码
- op: str = "vip_push" 💡 提交视频加速转码方式
- "vip_push": 根据;vip 等级加速
- "pay_push": 枫叶加速
"""
api = complete_url("/open/video/video_push", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
payload.setdefault("op", "vip_push")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_subtitle(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_subtitle(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_subtitle(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""视频字幕列表
GET https://proapi.115.com/open/video/subtitle
.. admonition:: Reference
https://www.yuque.com/115yun/open/nx076h3glapoyh7u
:payload:
- pick_code: str 💡 文件提取码
"""
api = complete_url("/open/video/subtitle", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置文件或目录(备注、标签、封面等)
POST https://proapi.115.com/open/ufile/update
.. hint::
即使文件已经被删除,也可以操作成功
.. admonition:: Reference
https://www.yuque.com/115yun/open/gyrpw5a0zc4sengm
:payload:
- file_id: int | str 💡 只能传入 1 个
- file_id[0]: int | str 💡 如果有多个,则按顺序给出
- file_id[1]: int | str
- ...
- file_name: str = <default> 💡 文件名
- star: 0 | 1 = <default> 💡 是否星标:0:取消星标 1:设置星标
- ...
"""
api = complete_url("/open/ufile/update", base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Recyclebin API ##########
@overload
def recyclebin_clean(
self,
payload: int | str | Iterable[int | str] | dict = {},
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_clean(
self,
payload: int | str | Iterable[int | str] | dict = {},
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_clean(
self,
payload: int | str | Iterable[int | str] | dict = {},
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:删除或清空
POST https://proapi.115.com/open/rb/del
.. admonition:: Reference
https://www.yuque.com/115yun/open/gwtof85nmboulrce
.. note::
``password`` 参数是不用传的
:payload:
- tid: int | str 💡 不传就是清空,多个用逗号 "," 隔开
"""
api = complete_url("/open/rb/del", base_url)
if isinstance(payload, (int, str)):
payload = {"tid": payload}
elif not isinstance(payload, dict):
payload = {"tid": ",".join(map(str, payload))}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:列表
GET https://proapi.115.com/open/rb/list
.. admonition:: Reference
https://www.yuque.com/115yun/open/bg7l4328t98fwgex
:payload:
- limit: int = 32
- offset: int = 0
"""
api = complete_url("/open/rb/list", base_url)
if isinstance(payload, int):
payload = {"limit": 32, "offset": payload}
payload.setdefault("limit", 32)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_revert(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_revert(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_revert(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:还原
POST https://proapi.115.com/open/rb/revert
.. admonition:: Reference
https://www.yuque.com/115yun/open/gq293z80a3kmxbaq
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- tid: int | str 💡 多个用逗号 "," 隔开
"""
api = complete_url("/open/rb/revert", base_url)
if isinstance(payload, (int, str)):
payload = {"tid": payload}
elif not isinstance(payload, dict):
payload = {"tid": ",".join(map(str, payload))}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Upload API ##########
@overload
def upload_gettoken_open(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_gettoken_open(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_gettoken_open(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取阿里云 OSS 的 token(上传凭证)
GET https://proapi.115.com/open/upload/get_token
.. admonition:: Reference
https://www.yuque.com/115yun/open/kzacvzl0g7aiyyn4
"""
api = complete_url("/open/upload/get_token", base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def upload_init(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_init(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_init(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""初始化上传任务,可能秒传
POST https://proapi.115.com/open/upload/init
.. admonition:: Reference
https://www.yuque.com/115yun/open/ul4mrauo5i2uza0q
:payload:
- file_name: str 💡 文件名
- fileid: str 💡 文件的 sha1 值
- file_size: int 💡 文件大小,单位是字节
- target: str 💡 上传目标,格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}"
- topupload: int = 0 💡 上传调度文件类型调度标记
- 0: 单文件上传任务标识 1 条单独的文件上传记录
- 1: 目录任务调度的第 1 个子文件上传请求标识 1 次目录上传记录
- 2: 目录任务调度的其余后续子文件不作记作单独上传的上传记录
- -1: 没有该参数
- sign_key: str = "" 💡 2 次验证时读取文件的范围
- sign_val: str = "" 💡 2 次验证的签名值
"""
api = complete_url("/open/upload/init", base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def upload_resume(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_resume(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_resume(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取恢复断点续传所需信息
POST https://proapi.115.com/open/upload/resume
.. admonition:: Reference
https://www.yuque.com/115yun/open/tzvi9sbcg59msddz
:payload:
- pick_code: str 💡 上传任务 key
- target: str 💡 上传目标,默认为 "U_1_0",格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}"
- fileid: str 💡 文件的 sha1 值(⚠️ 可以是任意值)
- file_size: int 💡 文件大小,单位是字节(⚠️ 可以是任意值)
"""
api = complete_url("/open/upload/resume", base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
else:
payload = dict(payload)
if "pick_code" not in payload:
if "pickcode" in payload:
payload["pick_code"] = payload["pickcode"]
callback_var: None | dict = None
if "callback_var" in payload:
callback_var = loads(payload["callback_var"])
elif "callback" in payload:
callback_var = loads(payload["callback"]["callback_var"])
if callback_var:
payload.update(
pick_code=callback_var["x:pick_code"],
target=callback_var["x:target"],
)
payload.setdefault("fileid", "0" * 40)
payload.setdefault("file_size", 1)
payload.setdefault("target", "U_1_0")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def upload_file_init(
self,
/,
filename: str,
filesha1: str,
filesize: int,
dirname: str = "",
read_range_bytes_or_hash: None | Callable[[str], str | Buffer] = None,
pid: int | str = 0,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_file_init(
self,
/,
filename: str,
filesha1: str,
filesize: int,
dirname: str = "",
read_range_bytes_or_hash: None | Callable[[str], str | Buffer] = None,
pid: int | str = 0,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_file_init(
self,
/,
filename: str,
filesha1: str,
filesize: int,
dirname: str = "",
read_range_bytes_or_hash: None | Callable[[str], str | Buffer] = None,
pid: int | str = 0,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""初始化上传,可能秒传,此接口是对 `upload_init_open` 的封装
.. note::
- 文件大小 和 sha1 是必需的,只有 sha1 是没用的。
- 如果文件大于等于 1 MB (1048576 B),就需要 2 次检验一个范围哈希,就必须提供 `read_range_bytes_or_hash`
:param filename: 文件名
:param filesha1: 文件的 sha1
:param filesize: 文件大小
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param read_range_bytes_or_hash: 调用以获取 2 次验证的数据或计算 sha1,接受一个数据范围,格式符合:
`HTTP Range Requests <https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests>`_,
返回值如果是 str,则视为计算好的 sha1,如果为 Buffer,则视为数据(之后会被计算 sha1)
:param pid: 上传文件到此目录的 id,或者指定的 target(格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}",但若 `aid != 1`,则会报参数错误)
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
def gen_step():
if isinstance(pid, str) and pid.startswith(("U_", "S_")):
target = pid
else:
target = f"U_1_{pid}"
payload = {
"file_name": filename,
"fileid": filesha1.upper(),
"file_size": filesize,
"target": target,
"path": dirname,
"topupload": 1,
}
resp = yield self.upload_init_open(
payload,
async_=async_,
**request_kwargs,
)
if not resp["state"]:
return resp
data = resp["data"]
if data["status"] == 7:
if read_range_bytes_or_hash is None:
raise ValueError("filesize >= 1 MB, thus need pass the `read_range_bytes_or_hash` argument")
payload["sign_key"] = data["sign_key"]
sign_check: str = data["sign_check"]
content: str | Buffer
if async_:
content = yield ensure_async(read_range_bytes_or_hash)(sign_check)
else:
content = read_range_bytes_or_hash(sign_check)
if isinstance(content, str):
payload["sign_val"] = content.upper()
else:
payload["sign_val"] = sha1(content).hexdigest().upper()
resp = yield self.upload_init_open(
payload,
async_=async_, # type: ignore
**request_kwargs,
)
resp["reuse"] = resp["data"].get("status") == 2
return resp
return run_gen_step(gen_step, async_)
@overload
def upload_file(
self,
/,
file: Buffer | str | PathLike | URL | SupportsGeturl | SupportsRead,
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
filesha1: str = "",
filesize: int = -1,
dirname: str = "",
payload: None | Mapping = None,
partsize: int = 0,
callback: None | dict = None,
upload_id: str = "",
endpoint: str = "https://oss-cn-shenzhen.aliyuncs.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_file(
self,
/,
file: Buffer | str | PathLike | URL | SupportsGeturl | SupportsRead,
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
filesha1: str = "",
filesize: int = -1,
dirname: str = "",
payload: None | Mapping = None,
partsize: int = 0,
callback: None | dict = None,
upload_id: str = "",
endpoint: str = "https://oss-cn-shenzhen.aliyuncs.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_file(
self,
/,
file: Buffer | str | PathLike | URL | SupportsGeturl | SupportsRead,
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
filesha1: str = "",
filesize: int = -1,
dirname: str = "",
payload: None | Mapping = None,
partsize: int = 0,
callback: None | dict = None,
upload_id: str = "",
endpoint: str = "https://oss-cn-shenzhen.aliyuncs.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""上传文件
.. note::
如果提供了 ``callback``,则强制为分块上传。
此时,最好提供一下 ``upload_id``,否则就是从头开始。
此时可以省略 ``pid``、``filename``、``filesha1``、``filesize``、``partsize``
.. caution::
``partsize > 0`` 时,不要把 ``partsize`` 设置得太小,起码得 10 MB (10485760) 以上
:param file: 待上传的文件
:param pid: 上传文件到此目录的 id 或 pickcode,或者指定的 target(格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}",但若 `aid != 1`,则会报参数错误)
:param share_id: 共享 id
:param filename: 文件名,如果为空,则会自动确定
:param filesha1: 文件的 sha1,如果为空,则会自动确定
:param filesize: 文件大小,如果为 -1,则会自动确定
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param payload: 其它的查询参数
:param partsize: 分块上传的分块大小。如果为 0,则不做分块上传;如果 < 0,则会自动确定
:param callback: 回调数据
:param upload_id: 上传任务 id
:param endpoint: 上传目的网址
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
request_kwargs["headers"] = dict(
request_kwargs.get("headers") or (),
authorization=self.headers["authorization"],
)
return upload_file(
file=file,
pid=pid,
share_id=share_id,
filename=filename,
filesha1=filesha1,
filesize=filesize,
dirname=dirname,
payload=payload,
partsize=partsize,
callback=callback,
upload_id=upload_id,
endpoint=endpoint,
async_=async_, # type: ignore
**request_kwargs,
)
########## User API ##########
@overload
def user_info(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_info(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_info(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户信息
GET https://proapi.115.com/open/user/info
.. admonition:: Reference
https://www.yuque.com/115yun/open/ot1litggzxa1czww
"""
api = complete_url("/open/user/info", base_url)
return self.request(url=api, async_=async_, **request_kwargs)
########## Other API ##########s
@overload
def vip_qr_url(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def vip_qr_url(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def vip_qr_url(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取产品列表地址(即引导用户扫码购买 115 的 VIP 服务,以获取提成)
GET https://proapi.115.com/open/vip/qr_url
.. admonition:: Reference
https://www.yuque.com/115yun/open/cguk6qshgapwg4qn#oByvI
:payload:
- open_device: int
- default_product_id: int = <default> 💡 打开产品列表默认选中的产品对应的产品id,如果没有则使用默认的产品顺序。
- 月费: 5
- 年费: 1
- 尝鲜1天: 101
- 长期VIP(长期): 24072401
- 超级VIP: 24072402
"""
api = complete_url("/open/vip/qr_url", base_url)
if not isinstance(payload, dict):
payload = {"open_device": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
clouddownload_quota_info_open = clouddownload_quota_info
clouddownload_task_add_bt_open = clouddownload_task_add_bt
clouddownload_task_add_urls_open = clouddownload_task_add_urls
clouddownload_task_clear_open = clouddownload_task_clear
clouddownload_task_del_open = clouddownload_task_del
clouddownload_task_list_open = clouddownload_task_list
clouddownload_torrent_open = clouddownload_torrent
download_url_open = download_url
download_urls_open = download_urls
download_url_info_open = download_url_info
fs_copy_open = fs_copy
fs_delete_open = fs_delete
fs_files_open = fs_files
fs_info_open = fs_info
fs_mkdir_open = fs_mkdir
fs_move_open = fs_move
fs_rename_open = fs_rename
fs_search_open = fs_search
fs_star_set_open = fs_star_set
fs_video_open = fs_video
fs_video_history_open = fs_video_history
fs_video_history_set_open = fs_video_history_set
fs_video_push_open = fs_video_push
fs_video_subtitle_open = fs_video_subtitle
fs_update_open = fs_update
recyclebin_clean_open = recyclebin_clean
recyclebin_list_open = recyclebin_list
recyclebin_revert_open = recyclebin_revert
upload_init_open = upload_init
upload_resume_open = upload_resume
user_info_open = user_info
upload_file_init_open = upload_file_init
upload_file_open = upload_file
vip_qr_url_open = vip_qr_url
to_id = staticmethod(to_id)
[docs]
def to_pickcode(
self,
id: int | str,
/,
prefix: Literal["a", "b", "c", "d", "e", "fa", "fb", "fc", "fd", "fe"] = "a",
stable_point: str = "",
) -> str:
"""把可能是 id 或 pickcode 的一律转换成 pickcode
.. note::
规定:空提取码 "" 对应的 id 是 0
:param id: 可能是 id 或 pickcode
:param prefix: 前缀
:param stable_point: 提取码的本征值,不同用户的值不同,你也可以直接传一个此用户的有效 pickcode
:return: pickcode
"""
return to_pickcode(
id,
stable_point=stable_point or self.pickcode_stable_point,
prefix=prefix,
)
[docs]
class P115Client(P115OpenClient):
"""115 的客户端对象
.. note::
目前允许 1 个用户同时登录多个开放平台应用(用 AppID 区别),也允许多次授权登录同 1 个应用
同一个开放应用 id,最多同时有 3 个登入,如果有新的登录,则自动踢掉较早的那一个
目前不允许短时间内再次用 ``refresh_token`` 刷新 ``access_token``,但你可以用登录的方式再次授权登录以获取 ``access_token``,即可不受频率限制
1 个 ``refresh_token`` 只能使用 1 次,可获取新的 ``refresh_token`` 和 ``access_token``,如果请求刷新时,发送成功但读取失败,可能导致 ``refresh_token`` 报废,这时需要重新授权登录
:param cookies: 115 的 cookies,要包含 ``UID``、``CID``、``KID`` 和 ``SEID`` 等
- 如果是 None,则会要求人工扫二维码登录
- 如果是 str,则要求是格式正确的 cookies 字符串,例如 "UID=...; CID=...; KID=...; SEID=..."
- 如果是 bytes 或 os.PathLike,则视为路径,当更新 cookies 时,也会往此路径写入文件,格式要求同上面的 `str`
- 如果是 collections.abc.Mapping,则是一堆 cookie 的名称到值的映射
- 如果是 collections.abc.Iterable,则其中每一条都视为单个 cookie
:param app: 重新登录时人工扫二维码后绑定的 `app` (或者叫 `device`),如果不指定,则根据 cookies 的 UID 字段来确定,如果不能确定,则用 "qandroid"
:param app_id: 授权的 open 应用的 AppID
:param console_qrcode: 在命令行输出二维码,否则在浏览器中打开
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def __init__(
self,
/,
cookies: None | str | PathLike | Mapping[str, str] | Iterable[Mapping | Cookie | Morsel] = None,
app: str = "",
app_id: int = 0,
console_qrcode: bool = True,
):
self.init(
cookies=cookies,
app=app,
app_id=app_id,
console_qrcode=console_qrcode,
instance=self,
)
def __repr__(self, /) -> str:
cls = type(self)
try:
user_id = self.user_id
except LookupError:
user_id = 0
return f"<{cls.__module__}.{cls.__qualname__}(user_id={user_id}, app={self.login_app()!r}, app_id={self.app_id}) at {hex(id(self))}>"
@overload # type: ignore
@classmethod
def init(
cls,
/,
cookies: None | str | PathLike | Mapping[str, str] | Iterable[Mapping | Cookie | Morsel] = None,
app: str = "",
app_id: int = 0,
console_qrcode: bool = True,
instance: None | Self = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115Client:
...
@overload
@classmethod
def init(
cls,
/,
cookies: None | str | PathLike | Mapping[str, str] | Iterable[Mapping | Cookie | Morsel] = None,
app: str = "",
app_id: int = 0,
console_qrcode: bool = True,
instance: None | Self = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115Client]:
...
@classmethod
def init(
cls,
/,
cookies: None | str | PathLike | Mapping[str, str] | Iterable[Mapping | Cookie | Morsel] = None,
app: str = "",
app_id: int = 0,
console_qrcode: bool = True,
instance: None | Self = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115Client | Coroutine[Any, Any, P115Client]:
def gen_step():
if instance is None:
self = cls.__new__(cls)
else:
self = instance
self.app_id = app_id
if cookies is None:
yield self.login(
app or "alipaymini",
console_qrcode=console_qrcode,
async_=async_,
**request_kwargs,
)
elif isinstance(cookies, PathLike):
if isinstance(cookies, PurePath) and hasattr(cookies, "open"):
self.cookies_path = cookies
else:
self.cookies_path = Path(fsdecode(cookies))
self._read_cookies()
elif cookies:
self.update_cookies(cookies)
return self
return run_gen_step(gen_step, async_)
@classmethod
def from_path(
cls,
/,
path: bytes | str | PathLike = Path("~/115-cookies.txt").expanduser(),
app_id: int = 0,
) -> P115Client:
if not isinstance(path, PurePath):
path = Path(fsdecode(path))
return cls(path, app_id=app_id)
@locked_cacheproperty
def user_key(self, /) -> str:
from .util import get_user_key, set_user_key
try:
return get_user_key(self.user_id)
except KeyError:
resp = self.upload_info()
check_response(resp)
return set_user_key(self.user_id, resp["userkey"])
@overload
def login(
self,
/,
app: None | str = None,
console_qrcode: bool = True,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> Self:
...
@overload
def login(
self,
/,
app: None | str = None,
console_qrcode: bool = True,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, Self]:
...
[docs]
def login(
self,
/,
app: None | str = None,
console_qrcode: bool = True,
async_: Literal[False, True] = False,
**request_kwargs,
) -> Self | Coroutine[Any, Any, Self]:
"""扫码二维码登录,如果已登录则忽略
:param app: 扫二维码后绑定的 `app` (或者叫 `device`),如果不指定,则根据 cookies 的 UID 字段来确定,如果不能确定,则用 "qandroid"
:param console_qrcode: 在命令行输出二维码,否则在浏览器中打开
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 返回对象本身
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def gen_step():
nonlocal app
status = yield self.login_status(async_=async_, **request_kwargs)
if status:
return self
if not app:
app = yield self.login_app(async_=async_, **request_kwargs)
if not app:
app = "alipaymini"
resp = yield self.login_with_qrcode(
app,
console_qrcode=console_qrcode,
async_=async_,
**request_kwargs,
)
while True:
try:
check_response(resp)
break
except P115AuthenticationError:
print("login error:", resp)
resp = yield self.login_with_qrcode(
app,
console_qrcode=console_qrcode,
async_=async_,
**request_kwargs,
)
self.update_cookies(resp["data"]["cookie"])
return self
return run_gen_step(gen_step, async_)
@overload
def login_with_app(
self,
/,
app: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_with_app(
self,
/,
app: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_with_app(
self,
/,
app: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""执行一次自动扫登录二维码,然后绑定到指定设备
:param app: 绑定的 `app` (或者叫 `device`),如果为 None 或 "",则和当前 client 的登录设备相同
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 响应信息,包含 cookies
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def gen_step():
nonlocal app
if not app:
app = yield self.login_app(async_=async_, **request_kwargs)
if not app:
raise ValueError("can't determine the login app")
uid: str = yield self.login_without_app(async_=async_, **request_kwargs)
return self.login_qrcode_scan_result(
uid,
app=app,
async_=async_,
**request_kwargs,
)
return run_gen_step(gen_step, async_)
@overload
def login_without_app(
self,
/,
show_warning: bool = False,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> str:
...
@overload
def login_without_app(
self,
/,
show_warning: bool = False,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, str]:
...
[docs]
def login_without_app(
self,
/,
show_warning: bool = False,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> str | Coroutine[Any, Any, str]:
"""执行一次自动扫登录二维码,但不绑定设备,返回扫码的 uid,可用于之后绑定设备
:param show_warning: 是否显示提示信息
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 二维码的 uid
"""
def gen_step():
uid = check_response((yield self.login_qrcode_token( # type: ignore
async_=async_,
**request_kwargs,
)))["data"]["uid"]
resp = yield self.login_qrcode_scan(
uid,
async_=async_,
**request_kwargs,
)
check_response(resp)
if show_warning:
warn(f"qrcode scanned: {resp}", category=P115Warning)
resp = yield self.login_qrcode_scan_confirm(
uid,
async_=async_,
**request_kwargs,
)
check_response(resp)
return uid
return run_gen_step(gen_step, async_)
@overload
def login_info_open(
self,
/,
app_id: int = 0,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_info_open(
self,
/,
app_id: int = 0,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_info_open(
self,
/,
app_id: int = 0,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取某个开放接口应用的信息(目前可获得名称和头像)
:param app_id: AppID
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口返回值
"""
if not app_id:
app_id = self.app_id
if not app_id:
app_id = 100195125
def gen_step():
resp = yield self.login_qrcode_token_open(app_id, async_=async_, **request_kwargs)
check_response(resp)
login_uid = resp["data"]["uid"]
resp = yield self.login_qrcode_scan(login_uid, async_=async_, **request_kwargs)
check_response(resp)
tip_txt = resp["data"]["tip_txt"]
return {
"app_id": app_id,
"name": tip_txt[:-10].removeprefix("\ufeff"),
"icon": resp["data"]["icon"],
}
return run_gen_step(gen_step, async_)
@overload
def login_with_open(
self,
/,
app_id: int = 0,
*,
show_warning: bool = False,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_with_open(
self,
/,
app_id: int = 0,
*,
show_warning: bool = False,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_with_open(
self,
/,
app_id: int = 0,
*,
show_warning: bool = False,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""登录某个开放接口应用
.. note::
同一个开放应用 id,最多同时有 3 个登入,如果有新的登录,则自动踢掉较早的那一个
:param app_id: AppID
:param show_warning: 是否显示提示信息
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口返回值
"""
if not app_id:
app_id = self.app_id
if not app_id:
app_id = 100195125
def gen_step():
resp = yield self.login_qrcode_token_open(app_id, async_=async_, **request_kwargs)
check_response(resp)
login_uid = resp["data"]["uid"]
resp = yield self.login_qrcode_scan(login_uid, async_=async_, **request_kwargs)
check_response(resp)
if show_warning:
warn(f"qrcode scanned: {resp}", category=P115Warning)
resp = yield self.login_qrcode_scan_confirm(login_uid, async_=async_, **request_kwargs)
check_response(resp)
return self.login_qrcode_access_token_open(login_uid, async_=async_, **request_kwargs)
return run_gen_step(gen_step, async_)
@overload
def login_another_app(
self,
/,
app: None | str = None,
replace: bool | Self = False,
show_warning: bool = False,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> Self:
...
@overload
def login_another_app(
self,
/,
app: None | str = None,
replace: bool | Self = False,
show_warning: bool = False,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, Self]:
...
[docs]
def login_another_app(
self,
/,
app: None | str = None,
replace: bool | Self = False,
show_warning: bool = False,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> Self | Coroutine[Any, Any, Self]:
"""登录某个设备(同一个设备可以有多个同时在线,但可以通过某些操作,把除了最近登录的那个都下线,也可以专门把最近登录那个也下线)
.. hint::
一个设备被新登录者下线,意味着这个 cookies 失效了,不能执行任何需要权限的操作
但一个设备的新登录者,并不总是意味着把较早的登录者下线,一般需要触发某个检查机制后,才会把同一设备下除最近一次登录外的所有 cookies 失效
所以你可以用一个设备的 cookies 专门用于扫码登录,获取另一个设备的 cookies 执行网盘操作,第 2 个 cookies 失效了,则用第 1 个 cookies 扫码,如此可避免单个 cookies 失效后,不能自动获取新的
:param app: 要登录的 app,如果为 None,则用当前登录设备,如果无当前登录设备,则报错
:param replace: 替换某个 ``P115Client`` 对象的 cookie
- 如果为 ``P115Client``, 则更新到此对象
- 如果为 True,则更新到 `self`
- 如果为 False,否则返回新的 ``P115Client`` 对象
:param show_warning: 是否显示提示信息
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 客户端实例
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def gen_step():
nonlocal app
if not app and isinstance(replace, P115Client):
app = yield replace.login_app(async_=True)
resp = yield self.login_with_app(
app,
show_warning=show_warning,
async_=async_,
**request_kwargs,
)
check_response(resp)
cookies = resp["data"]["cookie"]
ssoent = self.login_ssoent
if isinstance(replace, P115Client):
inst = replace
inst.update_cookies(cookies)
elif replace:
inst = self
inst.update_cookies(cookies)
else:
inst = type(self)(cookies)
if self is not inst and ssoent == inst.login_ssoent:
warn(f"login with the same ssoent {ssoent!r}, {self!r} will expire within 60 seconds", category=P115Warning)
return inst
return run_gen_step(gen_step, async_)
@overload
def login_another_open(
self,
/,
app_id: int = 0,
replace: bool | P115OpenClient = False,
show_warning: bool = False,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115OpenClient:
...
@overload
def login_another_open(
self,
/,
app_id: int = 0,
replace: bool | P115OpenClient = False,
show_warning: bool = False,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115OpenClient]:
...
[docs]
def login_another_open(
self,
/,
app_id: int = 0,
replace: bool | P115OpenClient = False,
show_warning: bool = False,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115OpenClient | Coroutine[Any, Any, P115OpenClient]:
"""登录某个开放接口应用
.. note::
同一个开放应用 id,最多同时有 3 个登入,如果有新的登录,则自动踢掉较早的那一个
:param app_id: AppID
:param replace: 替换某个 client 对象的 `access_token` 和 `refresh_token`
- 如果为 ``P115Client``, 则更新到此对象
- 如果为 True,则更新到 `self`
- 如果为 False,否则返回新的 ``P115Client`` 对象
:param show_warning: 是否显示提示信息
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 客户端实例
"""
if not app_id:
app_id = self.app_id
if not app_id:
app_id = 100195125
def gen_step():
resp = yield self.login_with_open(
app_id,
show_warning=show_warning,
async_=async_,
**request_kwargs,
)
check_response(resp)
data = resp["data"]
if replace is False:
inst: P115OpenClient = P115OpenClient(data["access_token"], data["refresh_token"])
else:
if replace is True:
inst = self
else:
inst = replace
inst.refresh_token = data["refresh_token"]
inst.access_token = data["access_token"]
inst.app_id = app_id
return inst
return run_gen_step(gen_step, async_)
@overload
@classmethod
def login_bind_app(
cls,
/,
uid: str,
app: str = "alipaymini",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> Self:
...
@overload
@classmethod
def login_bind_app(
cls,
/,
uid: str,
app: str = "alipaymini",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, Self]:
...
[docs]
@classmethod
def login_bind_app(
cls,
/,
uid: str,
app: str = "alipaymini",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> Self | Coroutine[Any, Any, Self]:
"""获取绑定到某个设备的 cookies
.. hint::
同一个设备可以有多个 cookies 同时在线
其实只要你不主动去执行检查,这些 cookies 可以同时生效,只是看起来像“黑户”
:param uid: 登录二维码的 uid
:param app: 待绑定的设备名称
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 新的实例
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def gen_step():
resp = yield cls.login_qrcode_scan_result(
uid,
app=app,
async_=async_,
**request_kwargs,
)
check_response(resp)
cookies = resp["data"]["cookie"]
return cls(cookies)
return run_gen_step(gen_step, async_)
########## Activity API ##########
@overload
def act_xys_adopt(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_adopt(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_adopt(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""采纳助愿
POST https://act.115.com/api/1.0/{app}/1.0/act2024xys/adopt
:payload:
- did: str 💡 许愿的 id
- aid: int | str 💡 助愿的 id
- to_cid: int = <default> 💡 助愿中的分享链接转存到你的网盘中目录的 id
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/adopt", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def act_xys_aid_desire(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_aid_desire(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_aid_desire(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""创建助愿(如果提供 file_ids,则会创建一个分享链接)
POST https://act.115.com/api/1.0/{app}/1.0/act2024xys/aid_desire
:payload:
- id: str 💡 许愿 id
- content: str 💡 助愿文本,不少于 5 个字,不超过 500 个字
- images: int | str = <default> 💡 图片文件在你的网盘的 id,多个用逗号 "," 隔开
- file_ids: int | str = <default> 💡 文件在你的网盘的 id,多个用逗号 "," 隔开
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/aid_desire", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def act_xys_aid_desire_del(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_aid_desire_del(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_aid_desire_del(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除助愿
POST https://act.115.com/api/1.0/{app}/1.0/act2024xys/del_aid_desire
:payload:
- ids: int | str 💡 助愿的 id,多个用逗号 "," 隔开
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/del_aid_desire", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"ids": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def act_xys_desire_aid_list(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_desire_aid_list(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_desire_aid_list(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取许愿的助愿列表
GET https://act.115.com/api/1.0/{app}/1.0/act2024xys/desire_aid_list
:payload:
- id: str 💡 许愿的 id
- start: int = 0 💡 开始索引
- page: int = 1 💡 第几页
- limit: int = 10 💡 分页大小
- sort: int | str = <default> 💡 排序
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/desire_aid_list", base_url=base_url)
if isinstance(payload, str):
payload = {"id": payload}
payload = {"start": 0, "page": 1, "limit": 10, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def act_xys_get_act_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_get_act_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_get_act_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取许愿树活动的信息
GET https://act.115.com/api/1.0/{app}/1.0/act2024xys/get_act_info
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/get_act_info", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def act_xys_get_desire_info(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_get_desire_info(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_get_desire_info(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取的许愿信息
GET https://act.115.com/api/1.0/{app}/1.0/act2024xys/get_desire_info
:payload:
- id: str 💡 许愿的 id
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/get_desire_info", base_url=base_url)
if isinstance(payload, str):
payload = {"id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def act_xys_home_list(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_home_list(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_home_list(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""首页的许愿树(随机刷新 15 条)
GET https://act.115.com/api/1.0/{app}/1.0/act2024xys/home_list
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/home_list", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def act_xys_my_aid_desire(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_my_aid_desire(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_my_aid_desire(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""我的助愿列表
GET https://act.115.com/api/1.0/{app}/1.0/act2024xys/my_aid_desire
:payload:
- type: 0 | 1 | 2 = 0 💡 类型
- 0: 全部
- 1: 进行中
- 2: 已实现
- start: int = 0 💡 开始索引
- page: int = 1 💡 第几页
- limit: int = 10 💡 分页大小
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/my_aid_desire", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"type": payload}
payload = {"type": 0, "start": 0, "page": 1, "limit": 10, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def act_xys_my_desire(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_my_desire(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_my_desire(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""我的许愿列表
GET https://act.115.com/api/1.0/{app}/1.0/act2024xys/my_desire
:payload:
- type: 0 | 1 | 2 = 0 💡 类型
- 0: 全部
- 1: 进行中
- 2: 已实现
- start: int = 0 💡 开始索引
- page: int = 1 💡 第几页
- limit: int = 10 💡 分页大小
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/my_desire", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"type": payload}
payload = {"type": 0, "start": 0, "page": 1, "limit": 10, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def act_xys_wish(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_wish(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_wish(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""创建许愿
POST https://act.115.com/api/1.0/{app}/1.0/act2024xys/wish
:payload:
- content: str 💡 许愿文本,不少于 5 个字,不超过 500 个字
- rewardSpace: int = 5 💡 奖励容量,单位是 GB
- images: int | str = <default> 💡 图片文件在你的网盘的 id,多个用逗号 "," 隔开
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/wish", base_url=base_url)
if isinstance(payload, str):
payload = {"content": payload}
payload.setdefault("rewardSpace", 5)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def act_xys_wish_del(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def act_xys_wish_del(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def act_xys_wish_del(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://act.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除许愿
POST https://act.115.com/api/1.0/{app}/1.0/act2024xys/del_wish
:payload:
- ids: str 💡 许愿的 id,多个用逗号 "," 隔开
"""
api = complete_url(f"/api/1.0/{app}/1.0/act2024xys/del_wish", base_url=base_url)
if isinstance(payload, str):
payload = {"ids": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Captcha System API ##########
@overload
def captcha_all(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> bytes:
...
@overload
def captcha_all(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, bytes]:
...
[docs]
def captcha_all(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> bytes | Coroutine[Any, Any, bytes]:
"""返回一张包含 10 个汉字的图片,包含验证码中 4 个汉字(有相应的编号,从 0 到 9,计数按照从左到右,从上到下的顺序)
GET https://captchaapi.115.com/?ct=index&ac=code&t=all
"""
api = complete_url(base_url=base_url, query={"ct": "index", "ac": "code", "t": "all"})
request_kwargs.setdefault("parse", False)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def captcha_code(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> bytes:
...
@overload
def captcha_code(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, bytes]:
...
[docs]
def captcha_code(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> bytes | Coroutine[Any, Any, bytes]:
"""更新验证码,并获取图片数据(含 4 个汉字)
GET https://captchaapi.115.com/?ct=index&ac=code
"""
api = complete_url(base_url=base_url, query={"ct": "index", "ac": "code"})
request_kwargs.setdefault("parse", False)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def captcha_sign(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def captcha_sign(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def captcha_sign(
self,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取验证码的签名字符串
GET https://captchaapi.115.com/?ac=code&t=sign
"""
api = complete_url(base_url=base_url, query={"ac": "code", "t": "sign"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def captcha_single(
self,
payload: dict | int = 0,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> bytes:
...
@overload
def captcha_single(
self,
payload: dict | int = 0,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, bytes]:
...
[docs]
def captcha_single(
self,
payload: dict | int = 0,
/,
base_url: str | Callable[[], str] = "https://captchaapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> bytes | Coroutine[Any, Any, bytes]:
"""10 个汉字单独的图片,包含验证码中 4 个汉字,编号从 0 到 9
GET https://captchaapi.115.com/?ct=index&ac=code&t=single
:payload:
- id: int = 0
"""
api = complete_url(base_url=base_url, query={"ct": "index", "ac": "code", "t": "single"})
if not isinstance(payload, dict):
payload = {"id": payload}
request_kwargs.setdefault("parse", False)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def captcha_verify(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def captcha_verify(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def captcha_verify(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""提交验证码
POST https://webapi.115.com/user/captcha
:payload:
- code: int | str 💡 从 0 到 9 中选取 4 个数字的一种排列
- sign: str = <default> 💡 来自 `captcha_sign` 接口的响应
- ac: str = "security_code" 💡 默认就行,不要自行决定
- type: str = "web" 💡 默认就行,不要自行决定
- ctype: str = "web" 💡 需要和 type 相同
- client: str = "web" 💡 需要和 type 相同
"""
api = complete_url("/user/captcha", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"code": payload}
payload = {"ac": "security_code", "type": "web", "ctype": "web", "client": "web", **payload}
def gen_step():
if "sign" not in payload:
resp = yield self.captcha_sign(async_=async_)
payload["sign"] = resp["sign"]
return self.request(
url=api,
method="POST",
data=payload,
async_=async_,
**request_kwargs,
)
return run_gen_step(gen_step, async_)
########## Cloud Download API ##########
@overload
def _clouddownload_web_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def _clouddownload_web_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def _clouddownload_web_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
api = complete_url("/web/", base_url=base_url)
if action:
payload["ac"] = action
if method.upper() == "POST":
request_kwargs["data"] = payload
else:
request_kwargs["params"] = payload
return self.request(
url=api,
method=method,
async_=async_,
**request_kwargs,
)
@overload
def _clouddownload_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def _clouddownload_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def _clouddownload_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
api = complete_url("/", base_url=base_url)
if action:
payload["ac"] = action
if method.upper() == "POST":
request_kwargs["data"] = payload
request_kwargs.setdefault("ecdh_encrypt", True)
else:
request_kwargs["params"] = payload
return self.request(
url=api,
method=method,
async_=async_,
**request_kwargs,
)
@overload
def _clouddownload_lixianssp_request(
self,
payload: dict = {},
/,
action: str = "",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def _clouddownload_lixianssp_request(
self,
payload: dict = {},
/,
action: str = "",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def _clouddownload_lixianssp_request(
self,
payload: dict = {},
/,
action: str = "",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
api = complete_url("/lixianssp/", base_url=base_url)
request_kwargs["method"] = "POST"
for k, v in payload.items():
payload[k] = str(v)
if action:
payload["ac"] = action
app_ver = payload.setdefault("app_ver", _app_version)
request_kwargs["headers"] = {
**(request_kwargs.get("headers") or {}),
"user-agent": f"Mozilla/5.0 115disk/{app_ver} 115Browser/{app_ver} 115wangpan_android/{app_ver}",
}
request_kwargs["ecdh_encrypt"] = False
def parse(_, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if data := json.get("data"):
try:
json["data"] = json_loads(rsa_decrypt(data))
except Exception:
pass
return json
request_kwargs.setdefault("parse", parse)
return self.request(
url=api,
data={"data": rsa_encrypt(dumps(payload)).decode("ascii")},
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def clouddownload_request(
self,
payload: dict = {},
/,
action: str = "",
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
match type:
case "web":
call: Callable = self._clouddownload_web_request
case "ssp":
call = self._clouddownload_lixianssp_request
case _:
call = self._clouddownload_request
return call(
payload,
action,
method=method,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_downpath(
self,
payload: dict | int = 10,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_downpath(
self,
payload: dict | int = 10,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_downpath(
self,
payload: dict | int = 10,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前默认的云下载到的目录信息(可能有多个)
GET https://webapi.115.com/offine/downpath
:payload:
- limit: int = 1150
"""
api = complete_url("/offine/downpath", base_url=base_url)
if isinstance(payload, int):
payload = {"limit": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_downpath_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_downpath_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_downpath_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置默认选择的云下载到的目录信息
POST https://webapi.115.com/offine/downpath
:payload:
- file_id: int | str 💡 目录 id
"""
api = complete_url("/offine/downpath", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def clouddownload_get_id(
self,
payload: dict | int = 1,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_get_id(
self,
payload: dict | int = 1,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_get_id(
self,
payload: dict | int = 1,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取和云下载有关的目录 id
GET https://clouddownload.115.com/?ac=get_id
.. note::
调用此接口后,如果相关目录不存在,则会自动创建。
响应数据里,"cid" 对应的是上传的种子文件的保存目录,"dest_cid" 是云下载的目录
:payload:
- torrent: int = 1
"""
if isinstance(payload, int):
payload = {"torrent": payload}
return self.clouddownload_request(
payload,
"get_id",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_quota_info(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_quota_info(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_quota_info(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前云下载配额信息(简略)
GET https://clouddownload.115.com/?ac=get_quota_info
"""
return self.clouddownload_request(
ac="get_quota_info",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_quota_package_array(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_quota_package_array(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_quota_package_array(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前云下载配额信息(详细)
GET https://clouddownload.115.com/?ac=get_quota_package_array
"""
return self.clouddownload_request(
ac="get_quota_package_array",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_quota_package_info(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_quota_package_info(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_quota_package_info(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前云下载配额信息(详细)
GET https://clouddownload.115.com/?ac=get_quota_package_info
"""
return self.clouddownload_request(
ac="get_quota_package_info",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_sign(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_sign(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_sign(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 sign 和 time 字段(各个添加任务的接口需要),以及其它信息
GET https://115.com/?ct=clouddownload&ac=space
"""
api = complete_url(base_url=base_url, query={"ct": "clouddownload", "ac": "space"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def clouddownload_sign_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_sign_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_sign_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 sign 和 time 字段(各个添加任务的接口需要)
GET https://proapi.115.com/{app}/files/offlinesign
"""
api = complete_url("/files/offlinesign", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def clouddownload_task(
self,
payload: str | dict,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task(
self,
payload: str | dict,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task(
self,
payload: str | dict,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取云下载任务信息
GET https://clouddownload.115.com/?ac=get_user_task
:payload:
- info_hash: str
"""
if isinstance(payload, str):
payload = {"info_hash": payload}
return self.clouddownload_request(
payload,
"get_user_task",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_task_add_bt(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_add_bt(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_add_bt(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加一个种子作为云下载任务
POST https://clouddownload.115.com/lixianssp/?ac=add_task_bt
.. note::
`client.clouddownload_task_add_bt(info_hash)` 相当于 `client.clouddownload_task_add_url(f"magnet:?xt=urn:btih:{info_hash}")`
但此接口的优势是允许选择要下载的文件
:payload:
- info_hash: str 💡 种子文件的 info_hash
- wanted: str = <default> 💡 选择文件进行下载(是数字索引,从 0 开始计数,用 "," 分隔)
- savepath: str = <default> 💡 保存到 `wp_path_id` 对应目录下的相对路径
- wp_path_id: int | str = <default> 💡 保存到目录的 id
"""
if isinstance(payload, str):
payload = {"info_hash": payload}
return self.clouddownload_request(
payload,
"add_task_bt",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_task_add_url(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_add_url(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_add_url(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加一个云下载任务
POST https://clouddownload.115.com/lixianssp/?ac=add_task_url
:payload:
- url: str 💡 链接,支持HTTP、HTTPS、FTP、磁力链和电驴链接
- savepath: str = <default> 💡 保存到目录下的相对路径
- wp_path_id: int | str = <default> 💡 保存到目录的 id
"""
if isinstance(payload, str):
payload = {"url": payload}
return self.clouddownload_request(
payload,
"add_task_url",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_task_add_urls(
self,
payload: str | Iterable[str] | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_add_urls(
self,
payload: str | Iterable[str] | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_add_urls(
self,
payload: str | Iterable[str] | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "ssp",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加一组云下载任务
POST https://clouddownload.115.com/lixianssp/?ac=add_task_urls
:payload:
- url: str 💡 链接,支持HTTP、HTTPS、FTP、磁力链和电驴链接
- url[0]: str 💡 链接,支持HTTP、HTTPS、FTP、磁力链和电驴链接
- url[1]: str
- ...
- savepath: str = <default> 💡 保存到目录下的相对路径
- wp_path_id: int | str = <default> 💡 保存到目录的 id
"""
if isinstance(payload, str):
payload = payload.strip("\n").split("\n")
if not isinstance(payload, dict):
payload = {f"url[{i}]": url for i, url in enumerate(payload) if url}
if not payload:
raise ValueError("no `url` specified")
return self.clouddownload_request(
payload,
"add_task_urls",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_task_clear(
self,
payload: int | dict = 0,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_clear(
self,
payload: int | dict = 0,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_clear(
self,
payload: int | dict = 0,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""清空云下载任务列表
POST https://clouddownload.115.com/?ac=task_clear
:payload:
- flag: int = 0 💡 标识,用于对应某种情况
- 0: 已完成
- 1: 全部
- 2: 已失败
- 3: 进行中
- 4: 已完成+删除源文件
- 5: 全部+删除源文件
"""
if isinstance(payload, int):
payload = {"flag": payload}
return self.clouddownload_request(
payload,
"task_clear",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_task_cnt(
self,
payload: dict | int = 0,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_cnt(
self,
payload: dict | int = 0,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_cnt(
self,
payload: dict | int = 0,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前正在运行的云下载任务数
GET https://clouddownload.115.com/?ac=get_task_cnt
:payload:
- flag: int = 0
"""
if isinstance(payload, int):
payload = {"flag": payload}
return self.clouddownload_request(
payload,
"get_task_cnt",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_task_count(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_count(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_count(
self,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前各种类型任务的计数
GET https://clouddownload.115.com/?ac=task_count
"""
return self.clouddownload_request(
action="task_count",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_task_del(
self,
payload: str | Iterable[str] | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_del(
self,
payload: str | Iterable[str] | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_del(
self,
payload: str | Iterable[str] | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除一组云下载任务(无论是否已经完成)
POST https://clouddownload.115.com/?ac=task_del
:payload:
- hash[0]: str
- hash[1]: str
- ...
- flag: 0 | 1 = <default> 💡 是否删除源文件
"""
if isinstance(payload, str):
payload = {"hash[0]": payload}
elif not isinstance(payload, dict):
payload = {f"hash[{i}]": hash for i, hash in enumerate(payload)}
if not payload:
raise ValueError("no `hash` (info_hash) specified")
return self.clouddownload_request(
payload,
"task_del",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_task_list(
self,
payload: int | dict = 1,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_list(
self,
payload: int | dict = 1,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_list(
self,
payload: int | dict = 1,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前的云下载任务列表
GET https://clouddownload.115.com/?ac=task_lists
:payload:
- page: int = 1
- page_size: int = 30
- stat: int = <default> 💡 已知:9:已失败 11:已完成 12:进行中
"""
if isinstance(payload, int):
payload = {"page": payload}
payload.setdefault("page_size", 30)
return self.clouddownload_request(
payload,
"task_lists",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_task_pause(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_pause(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_pause(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""暂停云下载任务
POST https://clouddownload.115.com/?ac=pause_task
:payload:
- info_hash: str 💡 待重试任务的 info_hash
"""
if isinstance(payload, str):
payload = {"info_hash": payload}
return self.clouddownload_request(
payload,
"pause_task",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_task_restart(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_restart(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_restart(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""重试云下载任务
POST https://clouddownload.115.com/?ac=restart
.. tip::
经常会遇到批量添加的磁力链接,因为空间不足而失败,此接口可以用于重试
.. code:: python
from p115client import P115Client
from p115client.tool import *
client = P115Client.from_path()
while True:
resp = client.clouddownload_task_list({"stat": 9})
tasks = [task for task in resp["tasks"] if task["status"] == 2]
if not tasks:
break
for task in tasks:
print(client.clouddownload_task_restart(task["info_hash"]))
:payload:
- info_hash: str 💡 待重试任务的 info_hash
"""
if isinstance(payload, str):
payload = {"info_hash": payload}
return self.clouddownload_request(
payload,
"restart",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def clouddownload_task_resume(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_task_resume(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_task_resume(
self,
payload: str | dict,
/,
method: str = "POST",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""恢复(被暂停的)云下载任务
POST https://clouddownload.115.com/?ac=resume_task
:payload:
- info_hash: str 💡 待重试任务的 info_hash
"""
if isinstance(payload, str):
payload = {"info_hash": payload}
return self.clouddownload_request(
payload,
"resume_task",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload # type: ignore
def clouddownload_torrent(
self,
payload: str | dict,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def clouddownload_torrent(
self,
payload: str | dict,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def clouddownload_torrent(
self,
payload: str | dict,
/,
method: str = "GET",
type: Literal["", "web", "ssp"] = "web",
base_url: str | Callable[[], str] = "https://clouddownload.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""查看种子的文件列表等信息
GET https://clouddownload.115.com/?ac=torrent
:payload:
- sha1: str
"""
if isinstance(payload, str):
payload = {"sha1": payload}
return self.clouddownload_request(
payload,
"torrent",
method=method,
type=type,
base_url=base_url,
async_=async_,
**request_kwargs,
)
########## Diary API ##########
@overload
def diary_add(
self,
payload: str | dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_add(
self,
payload: str | dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_add(
self,
payload: str | dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建日记
POST https://life.115.com/api/1.0/{app}/1.0/diary/add
:payload:
- form[content]: str 💡 内容
- form[subject]: int | str = <default> 💡 标题
- form[user_time]: int | float = <default> 💡 时间戳,单位是秒
- form[weather]: int = <default> 💡 天气
- form[mood]: int = <default> 💡 心情
- form[moods]: int | str = <default> 💡 心情,多个用逗号 "," 隔开
- form[tags][]: str = <default> 💡 标签
- ...
- form[tags][0]: str = <default> 💡 标签
- ...
- form[index_image] = <default> 💡 封面图片链接
- form[address]: str = <default> 💡 地点
- form[location]: str = <default> 💡 地名
- form[longitude]: float | str = <default> 💡 经度
- form[latitude]: float | str = <default> 💡 纬度
- form[mid]: str = <default> 💡 位置编码
- form[maps]: list[dict] = <default> 💡 多个地图位置
- form[maps][0][address]: str = <default>
- form[maps][0][location]: str = <default>
- form[maps][0][latitude]: float | str = <default>
- form[maps][0][longitude] float | str = <default>
- form[maps][0][mid]: str = <default>
- ...
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/add", base_url)
now = int(time())
if isinstance(payload, str):
payload = {"form[content]": payload, "form[user_time]": now}
elif isinstance(payload, dict):
payload = dict(expand_payload(payload, prefix="form", enum_seq=True))
payload.setdefault("form[user_time]", now)
elif isinstance(payload, list):
payload = [("form[user_time]", now), *expand_payload(payload, prefix="form")]
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def diary_del(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_del(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_del(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除日记
POST https://life.115.com/api/1.0/{app}/1.0/diary/delete
:payload:
- diary_id: int | str 💡 日记 id
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/delete", base_url)
if isinstance(payload, (int, str)):
payload = {"diary_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def diary_detail(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_detail(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_detail(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取日记详情
GET https://life.115.com/api/1.0/{app}/1.0/diary/detail
:payload:
- diary_id: int | str 💡 日记 id
- format: str = html
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/detail", base_url)
if isinstance(payload, (int, str)):
payload = {"diary_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def diary_detail2(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_detail2(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_detail2(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取日记详情
GET https://life.115.com/api/1.0/{app}/1.0/life/diarydetail
:payload:
- diary_id: int | str 💡 日记 id
- format: str = html
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/diarydetail", base_url)
if isinstance(payload, (int, str)):
payload = {"diary_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def diary_edit(
self,
payload: dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_edit(
self,
payload: dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_edit(
self,
payload: dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改日记
POST https://life.115.com/api/1.0/{app}/1.0/diary/edit
:payload:
- form[diary_id]: str 💡 日记 id
- form[content]: str = <default> 💡 内容
- form[subject]: int | str = <default> 💡 标题
- form[user_time]: int | float = <default> 💡 时间戳,单位是秒
- form[weather]: int = <default> 💡 天气
- form[mood]: int = <default> 💡 心情
- form[moods]: int | str = <default> 💡 心情,多个用逗号 "," 隔开
- form[tags][]: str = <default> 💡 标签
- ...
- form[tags][0]: str = <default> 💡 标签
- ...
- form[index_image] = <default> 💡 封面图片链接
- form[address]: str = <default> 💡 地点
- form[location]: str = <default> 💡 地名
- form[longitude]: float | str = <default> 💡 经度
- form[latitude]: float | str = <default> 💡 纬度
- form[mid]: str = <default> 💡 位置编码
- form[maps]: list[dict] = <default> 💡 多个地图位置
- form[maps][0][address]: str = <default>
- form[maps][0][location]: str = <default>
- form[maps][0][latitude]: float | str = <default>
- form[maps][0][longitude] float | str = <default>
- form[maps][0][mid]: str = <default>
- ...
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/edit", base_url)
if isinstance(payload, dict):
payload = dict(expand_payload(payload, prefix="form", enum_seq=True))
elif isinstance(payload, list):
payload = list(expand_payload(payload, prefix="form"))
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def diary_get_config(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_get_config(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_get_config(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取日记可选项(例如天气、心情等)的取值集合
GET https://life.115.com/api/1.0/{app}/1.0/diary/get_diary_config
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/get_diary_config", base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def diary_get_latest_tags(
self,
payload: int | str | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_get_latest_tags(
self,
payload: int | str | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def diary_get_tag_color(
self,
payload: str | list | tuple | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_get_tag_color(
self,
payload: str | list | tuple | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_get_tag_color(
self,
payload: str | list | tuple | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取标签的颜色
POST https://life.115.com/api/1.0/{app}/1.0/diary/gettagcolor
:payload:
- tags: str 💡 标签文本
- tags[]: str
- ...
- tags[0]: str
- tags[1]: str
- ...
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/gettagcolor", base_url)
if not isinstance(payload, dict):
if isinstance(payload, (list, tuple)):
payload = [t if isinstance(t, (list, tuple)) else ("tags[]", str(t)) for t in payload]
else:
payload = {"tags": str(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def diary_list(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_list(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_list(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取日记列表,此接口是对 `life_glist` 的封装
:payload:
- start: int = 0 💡 开始索引,从 0 开始
- limit: int = <default> 💡 分页大小
- only_public: 0 | 1 = <default>
- msg_note: 0 | 1 = <default>
- option: 0 | 1 = <default>
"""
if isinstance(payload, int):
payload = {"start": payload}
else:
payload = dict(payload)
payload.setdefault("type", 5)
return self.life_glist(payload, app=app, base_url=base_url, async_=async_, **request_kwargs)
@overload
def diary_search(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_search(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_search(
self,
payload: str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""搜索日记
GET https://life.115.com/api/1.0/{app}/1.0/diary/search
:payload:
- q: str 💡 关键词
- start: int = 0 💡 开始索引,从 0 开始
- limit: int = <default> 💡 分页大小
- display_list: 0 | 1 = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/search", base_url)
if isinstance(payload, str):
payload = {"q": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def diary_settag(
self,
payload: dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_settag(
self,
payload: dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_settag(
self,
payload: dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置日记标签
POST https://life.115.com/api/1.0/{app}/1.0/diary/settag
:payload:
- diary_id: int | str 💡 日记 id
- tags: str
- tags[]: str
- ...
- tags[0]: str
- tags[1]: str
- ...
"""
api = complete_url(f"/api/1.0/{app}/1.0/diary/settag", base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def diary_settop(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def diary_settop(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def diary_settop(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""切换日记的置顶状态,此接口是对 `life_set_top` 的封装
.. attention::
这个接口会自动切换日记的置顶状态,但不支持手动指定是否置顶,只是在置顶和不置顶间来回切换。
:payload:
- relation_id: int | str 💡 日记 id
"""
if isinstance(payload, (int, str)):
payload = {"relation_id": payload}
payload.setdefault("type", 5)
return self.life_set_top(payload, app=app, base_url=base_url, async_=async_, **request_kwargs)
########## Download API ##########
@overload
def download_files_app(
self,
payload: str | dict,
/,
app: str = "chrome",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_files_app(
self,
payload: str | dict,
/,
app: str = "chrome",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_files_app(
self,
payload: str | dict,
/,
app: str = "chrome",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取待下载的文件列表
GET https://proapi.115.com/app/chrome/downfiles
.. caution::
不允许直接从根目录获取,因为根目录没有 ``pickcode``
.. tip::
如果 ``app`` 不是 "chrome",那么会多一个字段 "sha1",虽依然没有文件名,但有 "fs"(文件大小),搭配 "sha1" 可以用于检测文件重复
:payload:
- pickcode: str 💡 提取码
- page: int = 1 💡 第几页
- per_page: int = 5000 💡 每页大小,目前最大为 5000
"""
if app in ("", "web", "desktop", "chrome"):
api = complete_url(f"/app/{version}/chrome/downfiles", base_url)
else:
if app not in ("windows", "mac", "linux", "os_windows", "os_mac", "os_linux"):
app = "os_windows"
api = complete_url("/ufile/downfiles", base_url, app=app, version=version)
request_kwargs.setdefault("ecdh_encrypt", True)
if isinstance(payload, str):
payload = {"pickcode": payload}
payload = {"page": 1, "per_page": 5000, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def download_folders_app(
self,
payload: str | dict,
/,
app: str = "chrome",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_folders_app(
self,
payload: str | dict,
/,
app: str = "chrome",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_folders_app(
self,
payload: str | dict,
/,
app: str = "chrome",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取待下载的目录列表
GET https://proapi.115.com/app/chrome/downfolders
.. caution::
不允许直接从根目录获取,因为根目录没有 ``pickcode``
:payload:
- pickcode: str 💡 提取码
- page: int = 1 💡 第几页
- per_page: int = 5000 💡 每页大小,目前最大为 5000
"""
if app in ("", "web", "desktop", "chrome"):
api = complete_url(f"/app/{version}/chrome/downfolders", base_url)
else:
if app not in ("windows", "mac", "linux", "os_windows", "os_mac", "os_linux"):
app = "os_windows"
api = complete_url("/ufile/downfolders", base_url, app=app, version=version)
request_kwargs.setdefault("ecdh_encrypt", True)
if isinstance(payload, str):
payload = {"pickcode": payload}
payload = {"page": 1, "per_page": 5000, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def download_downfolder_app(
self,
payload: str | dict,
/,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_downfolder_app(
self,
payload: str | dict,
/,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_downfolder_app(
self,
payload: str | dict,
/,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取待下载的文件列表
GET https://proapi.115.com/{app}/folder/downfolder
.. caution::
一次性拉完,当文件过多时,会报错
:payload:
- pickcode: str 💡 提取码
- share_id: int | str 💡 共享 id
"""
if app in ("", "web", "desktop", "chrome"):
api = complete_url(f"/app/chrome/downfolder", base_url)
else:
api = complete_url("/folder/downfolder", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def download_url(
self,
pickcode: int | str,
/,
strict: bool = True,
user_agent: None | str = None,
app: str = "os_windows",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115URL:
...
@overload
def download_url(
self,
pickcode: int | str,
/,
strict: bool = True,
user_agent: None | str = None,
app: str = "os_windows",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115URL]:
...
[docs]
def download_url(
self,
pickcode: int | str,
/,
strict: bool = True,
user_agent: None | str = None,
app: str = "os_windows",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115URL | Coroutine[Any, Any, P115URL]:
"""获取文件的下载链接
.. note::
获取的直链中,部分查询参数的解释:
- ``t``: 过期时间戳
- ``u``: 用户 id
- ``c``: 允许同时打开次数,如果为 0,则是无限次数
- ``f``: 请求时要求携带请求头
- 如果为空,则无要求
- 如果为 1,则需要 user-agent(和请求直链时的一致)
- 如果为 3,则需要 user-agent(和请求直链时的一致) 和 Cookie(由请求直链时的响应所返回的 Set-Cookie 响应头)
:param pickcode: 提取码
:param strict: 如果为 True,当目标是目录时,会抛出 IsADirectoryError 异常
:param user_agent: 如果不为 None,则作为请求头 "user-agent" 的值
:param app: 使用此设备的接口
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 下载链接
"""
if app == "open":
return self.download_url_open(
pickcode,
strict=strict,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
pickcode = self.to_pickcode(pickcode)
def gen_step():
if app in ("web2", "video"):
resp = yield self.download_url_web2(
pickcode,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["pickcode"] = pickcode
resp["is_download"] = True
check_response(resp)
url = resp["url"]
return P115URL(
url,
id=self.to_id(pickcode),
pickcode=pickcode,
name=unquote(urlsplit(url).path.rsplit("/", 1)[-1]),
is_dir=False,
headers=resp["headers"],
)
elif app in ("web", "desktop"):
resp = yield self.download_url_web(
pickcode,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["pickcode"] = pickcode
resp["is_download"] = True
try:
check_response(resp)
except IsADirectoryError:
if strict:
raise
return P115URL(
resp.get("file_url", ""),
id=int(resp["file_id"]),
pickcode=pickcode,
name=resp["file_name"],
size=int(resp["file_size"]),
is_dir=not resp["state"],
headers=resp["headers"],
)
else:
resp = yield self.download_url_app(
pickcode,
user_agent=user_agent,
app=app,
async_=async_,
**request_kwargs,
)
resp["pickcode"] = pickcode
resp["is_download"] = True
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
check_response(resp)
if "url" in data:
url = data["url"]
return P115URL(
url,
pickcode=pickcode,
name=unquote(urlsplit(url).path.rsplit("/", 1)[-1]),
is_dir=False,
headers=resp["headers"],
)
for fid, info in data.items():
url = info["url"]
if strict and not url:
throw(
errno.EISDIR,
f"{fid} is a directory, with response {resp}",
)
return P115URL(
url["url"] if url else "",
id=int(fid),
pickcode=info["pick_code"],
name=info["file_name"],
size=int(info["file_size"]),
sha1=info["sha1"],
is_dir=not url,
headers=resp["headers"],
)
throw(
errno.ENOENT,
f"no such pickcode: {pickcode!r}, with response {resp}",
)
return run_gen_step(gen_step, async_)
@overload
def download_urls(
self,
pickcodes: int | str | Iterable[int | str],
/,
strict: bool = True,
user_agent: None | str = None,
app: str = "os_windows",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict[int, P115URL]:
...
@overload
def download_urls(
self,
pickcodes: int | str | Iterable[int | str],
/,
strict: bool = True,
user_agent: None | str = None,
app: str = "os_windows",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict[int, P115URL]]:
...
[docs]
def download_urls(
self,
pickcodes: int | str | Iterable[int | str],
/,
strict: bool = True,
user_agent: None | str = None,
app: str = "os_windows",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict[int, P115URL] | Coroutine[Any, Any, dict[int, P115URL]]:
"""批量获取文件的下载链接
.. note::
获取的直链中,部分查询参数的解释:
- ``t``: 过期时间戳
- ``u``: 用户 id
- ``c``: 允许同时打开次数,如果为 0,则是无限次数
- ``f``: 请求时要求携带请求头
- 如果为空,则无要求
- 如果为 1,则需要 user-agent(和请求直链时的一致)
- 如果为 3,则需要 user-agent(和请求直链时的一致) 和 Cookie(由请求直链时的响应所返回的 Set-Cookie 响应头)
:param pickcodes: 提取码,多个用逗号 "," 隔开
:param strict: 如果为 True,当目标是目录时,会直接忽略
:param user_agent: 如果不为 None,则作为请求头 "user-agent" 的值
:param app: 使用此设备的接口,要么是 "open",要么是 "chrome"(或者其他任何值)
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 一批下载链接
"""
if app == "open":
return self.download_urls_open(
pickcodes,
strict=strict,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
if isinstance(pickcodes, (int, str)):
pickcodes = self.to_pickcode(pickcodes)
else:
pickcodes = ",".join(map(self.to_pickcode, pickcodes))
def gen_step():
resp = yield self.download_url_app(
pickcodes,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["pickcode"] = pickcodes
resp["is_download"] = True
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
urls: dict[int, P115URL] = {}
if resp.get("errno") != 50003:
check_response(resp)
for fid, info in data.items():
url = info["url"]
if strict and not url:
continue
fid = int(fid)
urls[fid] = P115URL(
url["url"] if url else "",
id=fid,
pickcode=info["pick_code"],
name=info["file_name"],
size=int(info["file_size"]),
sha1=info["sha1"],
is_dir=not url,
headers=resp["headers"],
)
return urls
return run_gen_step(gen_step, async_)
@overload
def download_url_app(
self,
payload: str | dict,
/,
user_agent: None | str = None,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_url_app(
self,
payload: str | dict,
/,
user_agent: None | str = None,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_url_app(
self,
payload: str | dict,
/,
user_agent: None | str = None,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件的下载链接
POST https://proapi.115.com/app/chrome/downurl
.. note::
`app` 为 "chrome" 时,支持一次获取多个提取码对应的下载链接,但是每多一个提取码,大概多耗时 50 ms,猜测服务端也是逐个从某个服务获取下载链接的。
如果 `app` 为 "chrome",则仅支持 `aid=1` 的提取码获取下载链接(以前是不限制 aid 的,这样甚至可以获取已经删除的文件的下载链接);否则,还支持 `aid=12` 的下载链接。
.. attention::
尽量不要尝试对已经删除的文件获取下载链接,不仅会失败,还容易触发风控
:payload:
- pickcode: str 💡 如果 `app` 为 "chrome",则可以接受多个,多个用逗号 "," 隔开
- pick_code: str 💡 如果不用 ``pickcode``,那就用 ``pick_code``
- share_id: int | str = <default> 💡 共享 id
- user_id: int = <default>
"""
if app in ("", "chrome"):
api = complete_url("/app/chrome/downurl", base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
elif "pickcode" not in payload:
payload["pickcode"] = payload["pick_code"]
elif app in ("os_windows", "os_mac", "os_linux", "windows", "mac", "linux"):
if not app.startswith("os_"):
app = "os_" + app
api = complete_url("/2.0/ufile/downurl", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
elif "pickcode" not in payload:
payload["pickcode"] = payload["pick_code"]
# NOTE: 提取码不可是 "f" 开头,但允许由任何特征值构造(由 0-9 和 a-z 这 36 个字符,构成的 4 位字符串,例如 "0000"),但限定必须是自己网盘的文件(不能获取别人的文件)
payload["pickcode"] = ",".join(map(normalize_pickcode, payload["pickcode"].split(",")))
request_kwargs.setdefault("ecdh_encrypt", True)
else:
api = complete_url("/2.0/ufile/download", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pick_code": payload}
elif "pick_code" not in payload:
payload["pick_code"] = payload["pickcode"]
payload.setdefault("user_id", self.user_id)
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers") or ())
if user_agent is None:
headers.setdefault("user-agent", "")
else:
headers["user-agent"] = user_agent
def parse(_, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if json["state"] and (data := json.get("data")):
json["data"] = json_loads(rsa_decrypt(data))
json["headers"] = headers
return json
request_kwargs.setdefault("parse", parse)
request_kwargs["data"] = {"data": rsa_encrypt(dumps(payload)).decode("ascii")}
return self.request(
url=api,
method="POST",
async_=async_,
**request_kwargs,
)
@overload
def download_url_web(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_url_web(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_url_web(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件的下载链接(网页版接口)
GET https://webapi.115.com/files/download
.. note::
最大允许下载 200 MB 的文件,即使文件违规,或者 `aid=12`,也可以正常下载
:payload:
- pickcode: str
- dl: int = 0 💡 如果不为 0,则需要从响应中提取 "file_url_302" 字段,得到一个链接(只能被访问一次,但无需 cookies),然后访问此链接,才能从中获得最终的下载链接
"""
api = complete_url("/files/download", base_url=base_url)
if not isinstance(payload, dict):
payload = {"pickcode": self.to_pickcode(payload)}
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers") or ())
if user_agent is None:
headers.setdefault("user-agent", "")
else:
headers["user-agent"] = user_agent
def parse(resp, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if "Set-Cookie" in resp.headers:
if isinstance(resp.headers, Mapping):
match = CRE_SET_COOKIE.search(resp.headers["Set-Cookie"])
if match is not None:
headers["cookie"] = match[0]
else:
for k, v in reversed(resp.headers.items()):
if k == "Set-Cookie" and CRE_SET_COOKIE.match(v) is not None:
headers["cookie"] = v
break
json["headers"] = headers
return json
request_kwargs.setdefault("parse", parse)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def download_url_web2(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def download_url_web2(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def download_url_web2(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件的下载链接(网页版接口)
GET https://115.com/?ct=download&ac=video
.. note::
最大允许下载 200 MB 的文件,即使文件已被删除,也可以正常下载
:payload:
- pickcode: str
"""
api = complete_url(base_url=base_url, query={"ct": "download", "ac": "video"})
if not isinstance(payload, dict):
payload = {"pickcode": self.to_pickcode(payload)}
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers") or ())
if user_agent is None:
headers.setdefault("user-agent", "")
else:
headers["user-agent"] = user_agent
def parse(resp, _: bytes, /) -> dict:
if resp.status != 302:
return {
"state": False,
"errno": 31003,
"message": "文件不存在、已删除、超过200MB或者是目录",
"response": {"status": resp.status, "headers": dict(resp.headers)},
}
json = {"state": True, "url": resp.headers["location"]}
if "Set-Cookie" in resp.headers:
if isinstance(resp.headers, Mapping):
match = CRE_SET_COOKIE.search(resp.headers["Set-Cookie"])
if match is not None:
headers["cookie"] = match[0]
else:
for k, v in reversed(resp.headers.items()):
if k == "Set-Cookie" and CRE_SET_COOKIE.match(v) is not None:
headers["cookie"] = v
break
json["headers"] = headers
return json
request_kwargs.setdefault("parse", parse)
request_kwargs.setdefault("follow_redirects", False)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
########## Extraction API ##########
@overload
def extract_add_file(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_add_file(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_add_file_app(
self,
payload: list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_add_file_app(
self,
payload: list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_download_url(
self,
/,
pickcode: str,
path: str,
user_agent: None | str = None,
app: str = "android",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115URL:
...
@overload
def extract_download_url(
self,
/,
pickcode: str,
path: str,
user_agent: None | str = None,
app: str = "android",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115URL]:
...
@overload
def extract_download_url_app(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
user_agent: None | str = None,
app: str = "android",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_download_url_app(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
user_agent: None | str = None,
app: str = "android",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_download_url_web(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_download_url_web(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_file(
self,
/,
pickcode: str,
files: str | Iterable[str] = "",
dirs: str | Iterable[str] = "",
dirname: str = "",
to_pid: int | str = 0,
app: str = "web",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_file(
self,
/,
pickcode: str,
files: str | Iterable[str] = "",
dirs: str | Iterable[str] = "",
dirname: str = "",
to_pid: int | str = 0,
app: str = "web",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_folders(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_folders(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_folders_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_folders_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_folders_post(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_folders_post(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_folders_post_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_folders_post_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_list(
self,
/,
pickcode: str,
path: str = "",
next_marker: str = "",
page_count: int = 999,
app: str = "web",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_list(
self,
/,
pickcode: str,
path: str = "",
next_marker: str = "",
page_count: int = 999,
app: str = "web",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_progress(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_progress(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_progress_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_progress_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_push(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_push(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_push_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_push_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_push_progress(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_push_progress(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def extract_push_progress_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def extract_push_progress_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
########## File System API ##########
@overload
def fs_batch_edit(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_batch_edit(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_batch_edit(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量设置文件或目录(显示时长等)
POST https://webapi.115.com/files/batch_edit
:payload:
- show_play_long[{fid}]: 0 | 1 = 1 💡 设置或取消显示时长
"""
api = complete_url("/files/batch_edit", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_batch_edit_app(
self,
payload: list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_batch_edit_app(
self,
payload: list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_batch_edit_app(
self,
payload: list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量设置文件或目录(显示时长等)
POST https://proapi.115.com/{app}/files/batch_edit
:payload:
- show_play_long[{fid}]: 0 | 1 = 1 💡 设置或取消显示时长
"""
api = complete_url("/files/batch_edit", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_category_get(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_category_get(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_category_get(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""显示属性,可获取文件或目录的统计信息(提示:但得不到根目录的统计信息,所以 cid 为 0 时无意义)
GET https://webapi.115.com/category/get
.. caution::
尝试获取目录的信息时,会去计算目录中文件和目录的数量、总文件大小 等信息,可能会消耗大量时间,但短时间内再次查询同一目录,耗时可能会大大减小
:payload:
- cid: int | str
- fid: int | str 💡 ``cid`` 和 ``fid`` 至少需要提供一个
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- status: 0 | 1 = <default> 💡 如果为 1,那么文件已被删除,会返回错误
"""
api = complete_url("/category/get", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload.setdefault("aid", 1)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_category_get_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_category_get_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_category_get_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""显示属性,可获取文件或目录的统计信息(提示:但得不到根目录的统计信息,所以 cid 为 0 时无意义)
GET https://proapi.115.com/{app}/2.0/category/get
.. caution::
尝试获取目录的信息时,会去计算目录中文件和目录的数量、总文件大小 等信息,可能会消耗大量时间,但短时间内再次查询同一目录,耗时可能会大大减小
:payload:
- cid: int | str
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
"""
api = complete_url("/2.0/category/get", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload.setdefault("aid", 1)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
fs_info = fs_category_get # type: ignore
fs_info_app = fs_category_get_app
@overload
def fs_category_shortcut(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_category_shortcut(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_category_shortcut(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""快捷入口列表(罗列所有的快捷入口)
GET https://webapi.115.com/category/shortcut
:payload:
- offset: int = 0
- limit: int = 1150
"""
api = complete_url("/category/shortcut", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_category_shortcut_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
set: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_category_shortcut_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
set: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_category_shortcut_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
set: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""把一个目录设置或取消为快捷入口(快捷入口需要是目录)
POST https://webapi.115.com/category/shortcut
:payload:
- file_id: int | str 目录 id,多个用逗号 "," 隔开
- op: "add" | "delete" | "top" = "add" 操作代码
- "add": 添加
- "delete": 删除
- "top": 置顶
"""
api = complete_url("/category/shortcut", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload, "op": ("delete", "add")[set]}
elif not isinstance(payload, dict):
payload = {"file_id": ",".join(map(str, payload)), "op": ("delete", "add")[set]}
else:
payload = {"op": ("delete", "add")[set], **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_copy(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_copy(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_copy(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""复制文件或目录
POST https://webapi.115.com/files/copy
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- fid: int | str 💡 文件或目录 id,只接受单个 id
- fid[]: int | str
- ...
- fid[0]: int | str
- fid[1]: int | str
- ...
- pid: int | str = 0 💡 目标目录 id
"""
api = complete_url("/files/copy", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"fid": payload}
elif not isinstance(payload, dict):
payload = {f"fid[{i}]": fid for i, fid in enumerate(payload)}
payload.setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_copy_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_copy_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_copy_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""复制文件或目录
POST https://proapi.115.com/{app}/files/copy
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- fid: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- pid: int | str = 0 💡 目标目录 id
"""
api = complete_url("/files/copy", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"fid": payload}
elif not isinstance(payload, dict):
payload = {"fid": ",".join(map(str, payload))}
cast(dict, payload).setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_cover_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
fid_cover: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_cover_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
fid_cover: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_cover_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
fid_cover: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改封面,可以设置目录的封面,此接口是对 `fs_edit` 的封装
"""
return self._fs_edit_set(
payload,
"fid_cover",
default=fid_cover,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_cover_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
fid_cover: int | str,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_cover_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
fid_cover: int | str,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_cover_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
fid_cover: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改封面,可以设置目录的封面,此接口是对 `fs_files_update_app` 的封装
"""
return self._fs_edit_set_app(
payload,
"fid_cover",
default=fid_cover,
app=app,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_delete(
self,
payload: int | str | dict | Iterable[int | str],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_delete(
self,
payload: int | str | dict | Iterable[int | str],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_delete(
self,
payload: int | str | dict | Iterable[int | str],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除文件或目录
POST https://webapi.115.com/rb/delete
.. caution::
⚠️ 请不要并发执行,但不限制文件数
.. caution::
删除和(从回收站)还原是互斥的,同时最多只允许执行一个操作
:payload:
- fid: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- fid[]: int | str
- ...
- fid[0]: int | str
- fid[1]: int | str
- ...
- ignore_warn: 0 | 1 = <default>
- from: int = <default>
- pid: int = <default>
"""
api = complete_url("/rb/delete", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"fid": payload}
elif not isinstance(payload, dict):
payload = {f"fid[{i}]": fid for i, fid in enumerate(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_delete_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_delete_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_delete_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除文件或目录
POST https://proapi.115.com/{app}/rb/delete
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
.. caution::
删除和(从回收站)还原是互斥的,同时最多只允许执行一个操作
.. caution::
有超过 5 万个文件和文件夹时,不能直接执行删除。如果删除的只是文件,那么在接口响应时,涉及的文件,已经删除完毕;但如果是目录,那么接口响应时,后台可能还在执行,而删除是不可并发的,因此下一个删除任务执行失败时,只需要反复重试即可
.. note::
此接口还能删除 `aid=12` 下的文件,且不会经过回收站(`aid=7`),而是彻底删除(`aid=120`)
.. code:: python
from pathlib import Path
from itertools import batched
from p115client import P115Client
client = P115Client(Path("~/115-cookies.txt").expanduser())
while True:
fids = [info["fid"] for info in client.fs_files({"aid": 12, "limit": 1150, "show_dir": 0})["data"]]
if not fids:
break
client.fs_delete_app(fids)
:payload:
- file_ids: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- user_id: int | str = <default> 💡 用户 id
"""
api = complete_url("/rb/delete", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload, "user_id": self.user_id}
elif isinstance(payload, dict):
payload = dict(payload, user_id=self.user_id)
else:
payload = {"file_ids": ",".join(map(str, payload)), "user_id": self.user_id}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_desc(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_desc(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_desc(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件或目录的备注
GET https://webapi.115.com/files/desc
:payload:
- file_id: int | str
- field: str = <default> 💡 可取示例值:"pass"
- compat: 0 | 1 = 1
- new_html: 0 | 1 = <default>
"""
api = complete_url("/files/desc", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload = {"compat": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_desc_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_desc_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_desc_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件或目录的备注
GET https://proapi.115.com/{app}/files/desc
:payload:
- file_id: int | str
- field: str = <default> 💡 可取示例值:"pass"
- compat: 0 | 1 = 1
- new_html: 0 | 1 = <default>
"""
api = complete_url("/files/desc", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload = {"compat": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_desc_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
desc: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_desc_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
desc: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_desc_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
desc: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置备注,最多允许 65535 个字节 (64 KB 以内),此接口是对 `fs_edit` 的封装
.. hint::
修改文件备注会更新文件的更新时间,即使什么也没改或者改为空字符串
"""
return self._fs_edit_set(
payload,
"file_desc",
default=desc,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_desc_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
desc: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_desc_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
desc: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_desc_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
desc: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置备注,最多允许 65535 个字节 (64 KB 以内),此接口是对 `fs_files_update_app` 的封装
.. hint::
修改文件备注会更新文件的更新时间,即使什么也没改或者改为空字符串
"""
return self._fs_edit_set_app(
payload,
"file_desc",
desc,
app=app,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_dir_getid(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_dir_getid(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_dir_getid(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""由路径获取对应的 id(但只能获取目录,不能获取文件)
GET https://webapi.115.com/files/getid
:payload:
- path: str
"""
if isinstance(payload, str):
payload = {"path": payload}
if callable(base_url):
base_url = base_url()
if "://f.115.com" in base_url or "://n.115.com" in base_url:
api = complete_url("/files/getid", base_url=base_url, query=payload)
return self.request(url=api, async_=async_, **request_kwargs)
api = complete_url("/files/getid", base_url=base_url)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_dir_getid2(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_dir_getid2(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_dir_getid2(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""由路径获取对应的 id(但只能获取目录,不能获取文件)
GET https://webapi.115.com/files/get_path_id
:payload:
- path: str
- parent_id: int = 0
- is_create: 0 | 1 = 0 💡 当目录不存在时,是否创建
"""
if isinstance(payload, str):
payload = {"path": payload}
if callable(base_url):
base_url = base_url()
if "://f.115.com" in base_url or "://n.115.com" in base_url:
api = complete_url("/files/get_path_id", base_url=base_url, query=payload)
return self.request(url=api, async_=async_, **request_kwargs)
api = complete_url("/files/get_path_id", base_url=base_url)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_dir_getid_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_dir_getid_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_dir_getid_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""由路径获取对应的 id(但只能获取目录,不能获取文件)
GET https://proapi.115.com/{app}/files/getid
:payload:
- path: str
"""
api = complete_url("/files/getid", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"path": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_document(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_document(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_document(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文档的信息和下载链接
GET https://webapi.115.com/files/document
.. note::
即使文件格式不正确或者是一个目录,也可返回一些信息(包括 parent_id)
:payload:
- pickcode: str
"""
api = complete_url("/files/document", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_document_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_document_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_document_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文档的信息和下载链接
GET https://proapi.115.com/{app}/files/document
.. note::
即使文件格式不正确或者是一个目录,也可返回一些信息(包括 parent_id)
:payload:
- pickcode: str
"""
api = complete_url("/files/document", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_edit(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_edit(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_edit(
self,
payload: list | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置文件或目录(备注、标签、封面等)
POST https://webapi.115.com/files/edit
:payload:
- fid: int | str
- fid[]: int | str
- ...
- file_desc: str = <default> 💡 可以用 html
- file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
- fid_cover: int | str = <default> 💡 封面图片的文件 id,多个用逗号 "," 隔开,如果要删除,值设为 0 即可
- show_play_long: 0 | 1 = <default> 💡 文件名称显示时长
- ...
"""
api = complete_url("/files/edit", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def _fs_edit_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
attr: str,
default: Any = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def _fs_edit_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
attr: str,
default: Any = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def _fs_edit_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
attr: str,
default: Any = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量设置文件或目录(备注、标签、封面等),此接口是对 `fs_edit` 的封装
"""
if isinstance(payload, (int, str)):
payload = [("fid", payload), (attr, default)]
elif isinstance(payload, list):
if not any(a[0] == attr for a in payload):
payload.append((attr, default))
elif isinstance(payload, dict):
payload.setdefault(attr, default)
else:
payload = [("fid[]", fid) for fid in payload]
payload.append((attr, default))
return self.fs_edit(payload, base_url=base_url, async_=async_, **request_kwargs)
@overload
def _fs_edit_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
attr: str,
default: Any = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def _fs_edit_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
attr: str,
default: Any = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
def _fs_edit_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
attr: str,
default: Any = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量设置文件或目录(备注、标签、封面等),此接口是对 `fs_files_update_app` 的封装
"""
if isinstance(payload, (int, str)):
payload = [("file_id", payload), (attr, default)]
elif isinstance(payload, list):
if not any(a[0] == attr for a in payload):
payload.append((attr, default))
elif isinstance(payload, dict):
payload.setdefault(attr, default)
else:
payload = [(f"file_id[{i}]", fid) for i, fid in enumerate(payload)]
payload.append((attr, default))
return self.fs_files_update_app(
payload,
async_=async_,
app=app,
base_url=base_url,
**request_kwargs,
)
@overload
def fs_export_dir(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_export_dir(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_export_dir(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""导出目录树
POST https://webapi.115.com/files/export_dir
.. caution::
【导出目录树】任务不可并发、不可中止,空目录不会被导出,输出的文件不会产生 life 操作事件
:payload:
- file_ids: int | str 💡 多个用逗号 "," 隔开
- target: str = "U_1_0" 💡 导出目录树到这个目录
- layer_limit: int = <default> 💡 层级深度,自然数
- not_suffix: str = <default>
"""
api = complete_url("/files/export_dir", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
payload.setdefault("target", "U_1_0")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_export_dir_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_export_dir_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_export_dir_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""导出目录树
POST https://proapi.115.com/{app}/2.0/ufile/export_dir
.. caution::
【导出目录树】任务不可并发、不可中止,空目录不会被导出,输出的文件不会产生 life 操作事件
:payload:
- file_ids: int | str 💡 多个用逗号 "," 隔开
- target: str = "U_1_0" 💡 导出目录树到这个目录
- layer_limit: int = <default> 💡 层级深度,自然数
"""
api = complete_url("/2.0/ufile/export_dir", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
payload.setdefault("target", "U_1_0")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_export_dir_status(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_export_dir_status(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_export_dir_status(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取导出目录树的完成情况
GET https://webapi.115.com/files/export_dir
:payload:
- export_id: int | str = 0 💡 任务 id
"""
api = complete_url("/files/export_dir", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"export_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_export_dir_status_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_export_dir_status_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_export_dir_status_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取导出目录树的完成情况
GET https://proapi.115.com/{app}/2.0/ufile/export_dir
:payload:
- export_id: int | str = 0 💡 任务 id
"""
api = complete_url("/2.0/ufile/export_dir", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"export_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_file(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_file(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_file(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件或目录的基本信息
GET https://webapi.115.com/files/get_info
.. caution::
仅当文件的 aid 是 1(网盘文件)、12(瞬间文件) 或 120(永久删除文件) 时,才能用此接口获取信息,否则请用 `client.fs_file_skim` 或 `client.fs_supervision` 获取信息(只能获取比较简略的信息)。
特别的,文件被移入回收站后,就不能用此接口获取信息了,除非将其还原或永久删除。
:payload:
- file_id: int | str 💡 文件或目录的 id,不能为 0,只能传 1 个 id,如果有多个只采用第一个
"""
api = complete_url("/files/get_info", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_file_skim(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
method: str = "GET",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_file_skim(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
method: str = "GET",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_file_skim(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
method: str = "GET",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件或目录的简略信息
GET https://webapi.115.com/files/file
.. note::
如果需要查询的 id 特别多,请指定 `method="POST"`
:payload:
- file_id: int | str 💡 文件或目录的 id,不能为 0,多个用逗号 "," 隔开
"""
api = complete_url("/files/file", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif not isinstance(payload, dict):
payload = {"file_id": ",".join(map(str, payload))}
if method.upper() == "POST":
request_kwargs["data"] = payload
else:
request_kwargs["params"] = payload
return self.request(url=api, method=method, async_=async_, **request_kwargs)
@overload
def fs_files(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的文件列表和基本信息
GET https://webapi.115.com/files
.. attention::
此接口被风控,此域名下的大量接口都会被风控,但重新登录可能恢复(多次登录必定不能恢复,即使更换设备)
.. hint::
指定如下条件中任一,且 cur = 0 (默认),即可遍历搜索所在目录树
1. cid=0 且 star=1
2. suffix 为非空的字符串
3. type 为正整数
4. show_dir=0 且 cur=0(或不指定 cur)
.. hint::
如果不指定或者指定的 cid 不存在,则会视为 cid=0 进行处理
当指定 ``natsort=1`` 时,如果里面的数量较少时,可仅统计某个目录内的文件或目录总数,而不返回具体的文件信息
.. hint::
当一个 cookies 被另一个更新的登录所失效,并不意味着这个 cookies 就直接不可用了。
如果你使用的是 `proapi` 下的接口,则会让你重新登录。但是 `webapi`、`aps` 等之下的接口,却依然可以正常使用。具体哪些失效,哪些还正常,请自行试验总结。这就意味着可以设计一种同一设备多 cookies 做池的分流策略。
.. hint::
对于普通的文件系统,我们只允许任何一个目录中不可有相同的名字,但是 115 网盘中却可能有重复:
- 目录和文件同名:文件和目录同名在 115 中不算是一个冲突
- 相同的目录名:转存可以导致同一目录下有多个相同名字的目录
- 相同的文件名:转存、云下载和上传等,可以导致同一目录下有多个相同名字的文件
.. hint::
如果文件或目录被置顶,会在整个文件列表的最前面
在根目录下且 ``fc_mix=0`` 且是特殊名字 ("最近接收", "手机相册", "云下载", "我的时光记录")(即 ``sys_dir``),会在整个文件列表的最前面但在置顶之后,这时可从返回信息的 "sys_count" 字段知道数目
.. note::
当 ``type=1`` 时,``suffix_type`` 的取值的含义:
- (不填): 全部
- 1: 文字(word,即 doc 和 docx 等)
- 2: 表格(excel,即 xls 和 xlsx 等)
- 3: 演示(ppt,即 ppt 和 pptx 等)
- 4: pdf
- 5: txt
- 6: xmind
- 7: 其它
.. caution::
``fields`` 字段并不以返回的文件列表所有的字段名为准,而是要用其对应的完整名字,例如
- "file_id": 对应 "cid"
- "parent_id": 对应 "pid"
- "area_id": 对应 "aid"
- "file_name": 对应 "n"
如果其中有一个是 "cid",则可能只返回这一个字段,而不管你又指定了其它多少字段。
由于这个参数的行为太过古怪且难猜,所以不建议使用,如果非要使用,建议换用 ``P115client.fs_files_app()``。
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,目前最大值是 1,150,以前是没限制的
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数,好像也可以写成 ``countfolders``
- cur: 0 | 1 = <default> 💡 是否只搜索当前目录
- custom_order: 0 | 1 = <default> 💡 启用自定义排序,如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 1
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- fields: str = <default> 💡 筛选字段(⚠️ 不建议使用),多个用逗号 "," 隔开,如果存在字段无效,则文件列表为空(但有计数 "count"、"file_count" 和 "folder_count")
- hidden: 0 | 1 = <default>
- is_q: 0 | 1 = <default> 💡 如果为 1,只显示文件
- is_share: 0 | 1 = <default>
- last_utime: int | str = <default>
- min_size: int = 0 💡 最小的文件大小
- max_size: int = 0 💡 最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting) 💡 natural sorting
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- oof_token: str = <default>
- qid: int | str = <default>
- r_all: 0 | 1 = <default>
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- scid: int | str = <default>
- show_dir: 0 | 1 = 1 💡 是否显示目录,好像也可以写成 showdir
- snap: 0 | 1 = <default>
- source: str = <default>
- sys_dir: int | str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- suffix_type: int = <default>
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-11: 大概相当于 1
- 12: 文档+图片+视频,相当于 1、2、4
- 13: ???,音频
- 14: ???,文档
- 15: 图片+视频,相当于 2、4
- 16: 字幕
- 17~98: 大概相当于 1
- 99: 所有文件
- >=100: 大概相当于 1
"""
api = complete_url("/files", base_url=base_url)
if payload is None:
return self.request(url=api, async_=async_, **request_kwargs)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {
"aid": 1, "count_folders": 1, "limit": 32, "offset": 0,
"record_open_time": 1, "show_dir": 1, "cid": 0, **payload,
}
if payload.keys() & frozenset(("asc", "fc_mix", "o")):
payload["custom_order"] = 2
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
version: str = "2.0",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的文件列表和基本信息
GET https://proapi.115.com/{app}/{version}/ufile/files
.. hint::
如果要遍历获取所有文件,需要指定 show_dir=0 且 cur=0(或不指定 cur),这个接口并没有 type=99 时获取所有文件的意义
.. note::
一旦此接口被风控,那么同一域名下,所有路径尾部是 /files 的接口都被风控,但是如果你没有携带任何参数,竟然可以避免风控(至少可以用来获得一下文件总数,以及最近的 20 个创建的文件)。
另外,proapi 之下,指定接口的版本为 2.0,可能会被服务器后台专门的处理,而其它版本(乃至于不指定版本),也往往被视为等同的,由此可以分为两类:2.0 版本和其它版本。
.. attention::
此接口存在一些潜在的问题。假如我上传了一个扩展名特别长的文件,越出了这个接口的能力范围,就会直接报错,例如:
.. code:: python
client.upload_file_sample(b"", filename="a."+"a"*300)
# NOTE: 因而下面的请求将不会成功
client.fs_files_app()
.. caution::
这个接口有些问题:
1. 当 custom_order=1 时,如果设定 limit=1 可能会报错
2. fc_mix 无论怎么设置,都和 fc_mix=0 的效果相同(即目录总是置顶),设置为 custom_order=2 也没用
.. hint::
置顶无效,但可以知道是否置顶了。
在根目录下且 fc_mix=0 且是特殊名字 ("最近接收", "手机相册", "云下载", "我的时光记录"),会在整个文件列表的最前面,这时可从返回信息的 "sys_count" 字段知道数目
.. tip::
这个接口的 ``fields`` 参数可以筛选字段,例如,当你只需要文件 id 时,只需要指定 ``client.fs_files_app({"fields": "fid"})``
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,并没有上限,请预估返回的字节数自行调整规模,7,000 是大约安全了,如果用 ``fields`` 筛选了字段,可以增到 10,000,如果报 500 响应,则要适当减小些(或者多次尝试,偶有成功)
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数
- cur: 0 | 1 = <default> 💡 是否只显示当前目录
- custom_order: 0 | 1 | 2 = <default> 💡 是否使用记忆排序。如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 2
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- fields: str = <default> 💡 筛选字段,多个用逗号 "," 隔开,如果所有字段都无效,则返回全部
- for: str = <default> 💡 文件格式,例如 "doc"
- is_q: 0 | 1 = <default>
- is_share: 0 | 1 = <default>
- min_size: int = 0 💡 最小的文件大小
- max_size: int = 0 💡 最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_etime": 事件时间(无效,效果相当于 "user_utime")
- "user_utime": 修改时间
- "user_ptime": 创建时间(无效,效果相当于 "user_utime")
- "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
- r_all: 0 | 1 = <default>
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- scid: int | str = <default>
- show_dir: 0 | 1 = 1 💡 是否显示目录
- snap: 0 | 1 = <default>
- source: str = <default>
- sys_dir: int | str = <default> 💡 系统目录编号,0:最近接收 1:手机相册 2:云下载 3:我的时光记录 4,10,20,21,22,30,40,50,60,70:(未知)
- star: 0 | 1 = <default> 💡 是否星标文件
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-11: 大概相当于 1
- 12: 文档+图片+视频,相当于 1、2、4
- 13: ???,音频
- 14: 大概相当于 1
- 15: 图片+视频,相当于 2、4
- >= 16: 相当于 8
"""
api = complete_url("/ufile/files", base_url=base_url, app=app, version=version)
if payload is None:
return self.request(url=api, async_=async_, **request_kwargs)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {
"aid": 1, "count_folders": 1, "limit": 32, "offset": 0,
"record_open_time": 1, "show_dir": 1, "cid": 0, **payload,
}
if payload.keys() & frozenset(("asc", "fc_mix", "o")):
payload["custom_order"] = 2
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_app2(
self,
payload: None| int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_app2(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_app2(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的文件列表和基本信息
GET https://proapi.115.com/{app}/files
.. hint::x
如果要遍历获取所有文件,需要指定 show_dir=0 且 cur=0(或不指定 cur),这个接口并没有 type=99 时获取所有文件的意义
.. caution::
这个接口有些问题:
1. 当 custom_order=1 时,如果设定 limit=1 可能会报错
2. fc_mix 无论怎么设置,都和 fc_mix=0 的效果相同(即目录总是置顶),设置为 custom_order=2 也没用
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,最大值不一定,看数据量,7,000 应该总是安全的,10,000 有可能报错,但有时也可以 20,000 而成功
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数
- cur: 0 | 1 = <default> 💡 是否只搜索当前目录
- custom_order: 0 | 1 | 2 = <default> 💡 启用自定义排序,如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 2
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- for: str = <default> 💡 文件格式,例如 "doc"
- hide_data: str = <default> 💡 是否返回文件数据
- is_q: 0 | 1 = <default>
- is_share: 0 | 1 = <default>
- min_size: int = 0 💡 (⚠️ 似乎不可用)最小的文件大小
- max_size: int = 0 💡 (⚠️ 似乎不可用)最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_etime": 事件时间(无效,效果相当于 "user_utime")
- "user_utime": 修改时间
- "user_ptime": 创建时间(无效,效果相当于 "user_utime")
- "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
- r_all: 0 | 1 = <default>
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- scid: int | str = <default>
- show_dir: 0 | 1 = 1 💡 是否显示目录
- snap: 0 | 1 = <default>
- source: str = <default>
- sys_dir: int | str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-11: 大概相当于 1
- 12: 文档+图片+视频,相当于 1、2、4
- 13: ???,音频
- 14: 大概相当于 1
- 15: 图片+视频,相当于 2、4
- >= 16: 相当于 8
"""
api = complete_url("/files", base_url=base_url, app=app, force_app=("wechatmini", "alipaymini"))
if payload is None:
return self.request(url=api, async_=async_, **request_kwargs)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {
"aid": 1, "count_folders": 1, "limit": 32, "offset": 0,
"record_open_time": 1, "show_dir": 1, "cid": 0, **payload,
}
if payload.keys() & frozenset(("asc", "fc_mix", "o")):
payload["custom_order"] = 2
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_aps(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_aps(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_aps(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的文件列表和基本信息
GET https://aps.115.com/natsort/files.php
.. tip::
这个接口的响应速度极快,在文件数特别多时,是 ``P115Client.fs_files`` 的数倍甚至数十倍的速度,更无论 ``P115Client.fs_files_app`` 了
.. caution::
这个函数最多获取任何一种排序条件下的前 ``1200 + n`` 条数据(``n >= 0`` 是一个潜在的不定限制)。
`o` 参数无效,效果只等于 "file_name",而 ``fc_mix`` 和 ``asc`` 可用。
当 ``offset >= 1200 + n``,则相当于 ``offset=0&fc_mix=1``,即从头开始,且置顶项不会置顶
.. hint::
从技术上来讲最多可分别获取 ``(1200 + n) * 2`` 个文件和目录,即你可以通过改变顺序(asc 取 0 或者 1),来最多获取两倍于数量上限的不同条目,然后通过指定 ``show_dir=0&cur=1`` 和 ``show_dir=1&nf=1`` 来分别只获取文件或目录。
不过对于文件,如果利用 ``type``、 ``suffix``、``min_size``、``max_size`` 等参数进行筛选,则可以获得更多,甚至可以是全部。
注意:如果有置顶的条目,置顶条目总是出现,因此可能会使获取到的不同条目总数变少。
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,最大值是 1,200
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数
- cur: 0 | 1 = <default> 💡 是否只搜索当前目录
- custom_order: 0 | 1 = <default> 💡 启用自定义排序,如果指定了 "asc"、"fc_mix" 中其一,则此参数会被自动设置为 1
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- is_asc: 0 | 1 = <default>
- min_size: int = 0 💡 最小的文件大小
- max_size: int = 0 💡 最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default>
- order: str = <default>
- r_all: 0 | 1 = <default>
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- scid: int | str = <default>
- show_dir: 0 | 1 = 1 💡 是否显示目录
- snap: 0 | 1 = <default>
- source: str = <default>
- sys_dir: int | str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-12: 大概相当于 1
- 13: ???,音频
- 14-15: 大概相当于 1
- 16: 字幕
- 17~98: 大概相当于 1
- 99: 所有文件
- >=100: 大概相当于 1
"""
api = complete_url("/natsort/files.php", base_url=base_url)
if payload is None:
return self.request(url=api, async_=async_, **request_kwargs)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {
"aid": 1, "count_folders": 1, "limit": 32, "offset": 0,
"record_open_time": 1, "show_dir": 1, "cid": 0, **payload,
}
if payload.keys() & frozenset(("asc", "fc_mix")):
payload["custom_order"] = 2
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_blank_document(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_blank_document(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_blank_document(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建空白 office 文件
POST https://webapi.115.com/files/blank_document
:payload:
- file_name: str 💡 文件名,不含后缀
- pid: int | str = 0 💡 目录 id,对应 parent_id
- type: 1 | 2 | 3 = 1 💡 1:Word文档(.docx) 2:Excel表格(.xlsx) 3:PPT文稿(.pptx)
"""
api = complete_url("/files/blank_document", base_url=base_url)
if isinstance(payload, str):
payload = {"file_name": payload}
payload = {"pid": 0, "type": 1, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_files_cover(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_cover(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_cover(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""查看是否有封面
GET https://webapi.115.com/files/cover
:payload:
- file_id: int | str 💡 文件或目录 id
- folder_as_file: 0 | 1 = <default>
"""
api = complete_url("/files/cover", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_cover_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_cover_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_cover_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""是否生成封面
POST https://webapi.115.com/files/cover
:payload:
- file_id: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- show: 0 | 1 = 1
"""
api = complete_url("/files/cover", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload.setdefault("show", 1)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_files_image(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_image(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_image(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的图片列表和基本信息
GET https://webapi.115.com/files/imglist
.. danger::
这个函数大概是有 bug 的,不推荐使用,请用 ``fs_files_media`` 代替
.. attention::
只能获取直属于 `cid` 所在目录的图片,不会遍历整个目录树
:payload:
- cid: int | str 💡 目录 id
- file_id: int | str 💡 不能是 0,可以是任何一个有效的 id(必须在自己网盘中,哪怕已经被删除,此必需参数只为应付检查)
- limit: int = 32 💡 最多返回数量
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- is_asc: 0 | 1 = <default> 💡 是否升序排列
- next: 0 | 1 = <default>
- order: str = <default> 💡 用某字段排序
- 文件名:"file_name"
- 文件大小:"file_size"
- 文件种类:"file_type"
- 修改时间:"user_utime"
- 创建时间:"user_ptime"
- 上一次打开时间:"user_otime"
"""
api = complete_url("/files/imglist", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {"limit": 32, "offset": 0, "cid": 0, **payload}
if cid := payload.get("cid"):
payload.setdefault("file_id", cid)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_image_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_image_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_image_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的图片列表和基本信息
GET https://proapi.115.com/{app}/files/imglist
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 一页大小,建议控制在 <= 9000,不然会报错
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- cur: 0 | 1 = 1 💡 只罗列当前目录
- o: str = <default> 💡 用某字段排序
- 文件名:"file_name"
- 文件大小:"file_size"
- 文件种类:"file_type"
- 修改时间:"user_utime"
- 创建时间:"user_ptime"
- 上一次打开时间:"user_otime"
"""
api = complete_url("/files/imglist", base_url=base_url, app=app)
if payload is None:
return self.request(url=api, async_=async_, **request_kwargs)
if isinstance(payload, (int, str)):
payload = {"cid": payload}
payload = {"limit": 32, "offset": 0, "aid": 1, "cid": 0, "cur": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_media(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_media(
self,
payload: None | int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def fs_files_media_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_media_app(
self,
payload: None | int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def fs_files_recent_docs(
self,
payload: int | dict = 1000,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_recent_docs(
self,
payload: int | dict = 1000,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_recent_docs(
self,
payload: int | dict = 1000,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取最近上传的文档
POST https://webapi.115.com/files/recent
.. todo::
这个接口可能支持其它参数,但目前暂未搞清楚
:payload:
- limit: int
"""
api = complete_url("/files/recent", base_url=base_url)
if isinstance(payload, int):
payload = {"limit": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_files_recent_docs_app(
self,
payload: int | dict = 1000,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_recent_docs_app(
self,
payload: int | dict = 1000,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_recent_docs_app(
self,
payload: int | dict = 1000,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取最近上传的文档
POST https://proapi.115.com/{app}/2.0/ufile/recent
.. todo::
这个接口可能支持其它参数,但目前暂未搞清楚
:payload:
- limit: int
"""
api = complete_url("/2.0/ufile/recent", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"limit": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_files_second_type(
self,
payload: Literal[1, 2, 3, 4, 5, 6, 7] | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_second_type(
self,
payload: Literal[1, 2, 3, 4, 5, 6, 7] | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_second_type(
self,
payload: Literal[1, 2, 3, 4, 5, 6, 7] | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中某个文件类型的扩展名的(去重)列表
GET https://webapi.115.com/files/get_second_type
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- type: int = 1 💡 文件类型
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
"""
api = complete_url("/files/get_second_type", base_url=base_url)
if isinstance(payload, int):
payload = {"type": payload}
payload = {"cid": 0, "type": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_second_type_app(
self,
payload: Literal[1, 2, 3, 4, 5, 6, 7] | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_second_type_app(
self,
payload: Literal[1, 2, 3, 4, 5, 6, 7] | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_second_type_app(
self,
payload: Literal[1, 2, 3, 4, 5, 6, 7] | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中某个文件类型的扩展名的(去重)列表
GET https://proapi.115.com/{app}/2.0/ufile/get_second_type
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- type: int = 1 💡 文件类型
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
"""
api = complete_url("/2.0/ufile/get_second_type", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"type": payload}
payload = {"cid": 0, "type": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_files_update_app(
self,
payload: int | str | tuple[int | str] | list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_files_update_app(
self,
payload: int | str | tuple[int | str] | list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_files_update_app(
self,
payload: int | str | tuple[int | str] | list | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置(若干个)文件或目录(名字、备注、标签等)
POST https://proapi.115.com/{app}/files/update
:payload:
- file_id: int | str
- file_id[]: int | str
- ...
- file_id[0]: int | str
- file_id[1]: int | str
- ...
- file_desc: str = <default> 💡 可以用 html
- file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
- file_name: str = <default> 💡 文件或目录名
- fid_cover: int | str = <default> 💡 封面图片的文件 id,多个用逗号 "," 隔开,如果要删除,值设为 0 即可
- show_play_long: 0 | 1 = <default> 💡 文件名称显示时长
- ...
"""
api = complete_url("/files/update", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif isinstance(payload, tuple):
payload = {f"file_id[i]": p for i, p in enumerate(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_folder_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录列表(不含文件)
GET https://proapi.115.com/{app}/folder/update
:payload:
- p_id: int
- offset: int = 0
- limit: int = 1150
- user_id: int | str = <default> 💡 用户 id,不必是自己 🤪
- ...
"""
api = complete_url("/folder/update", base_url=base_url, app=app)
if not isinstance(payload, dict):
payload = {"p_id": payload}
payload = {"limit": 1150, "offset": 0, "p_id": 0, "user_id": self.user_id, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_folder_app2(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_app2(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_app2(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录列表(不含文件)
GET https://proapi.115.com/{app}/folder
:payload:
- p_id: int
- offset: int = 0
- limit: int = 1150
- user_id: int | str = <default> 💡 用户 id,不必是自己 🤪
- aid: int = 1
- ...
"""
api = complete_url("/folder", base_url=base_url, app=app)
if not isinstance(payload, dict):
payload = {"p_id": payload}
payload = {"aid": 1, "limit": 1150, "offset": 0, "p_id": 0, "user_id": self.user_id, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_folder_file(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_file(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_file(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录中的文件列表(不含目录)
GET https://webapi.115.com/folder/file
.. todo::
``limit`` 参数无效,即不能设定分页大小,此接口还需继续挖掘 ⛏️
:payload:
- cid: int | str
- offset: int = 0
- limit: int = 1150 💡 ⚠️ 无效,只能是默认获取 15 条
- user_id: int | str = <default> 💡 用户 id,不必是自己 🤪
- aid: int = 1
- ...
"""
api = complete_url("/folder/file", base_url=base_url)
if not isinstance(payload, dict):
payload = {"cid": payload}
payload = {"aid": 1, "limit": 1150, "offset": 0, "cid": 0, "user_id": self.user_id, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_folder_playlong(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_playlong(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_playlong(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取目录内文件总的播放时长
POST https://aps.115.com/getFolderPlaylong
:payload:
- folder_ids: int | str 💡 目录 id,多个用逗号 "," 隔开
"""
api = complete_url("/getFolderPlaylong", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"folder_ids": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_folder_playlong_set(
self,
/,
ids: int | str | Iterable[int | str],
is_set: Literal[0, 1] = 1,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_playlong_set(
self,
/,
ids: int | str | Iterable[int | str],
is_set: Literal[0, 1] = 1,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_playlong_set(
self,
/,
ids: int | str | Iterable[int | str],
is_set: Literal[0, 1] = 1,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""显示或取消目录内文件总的播放时长,此接口是对 `fs_batch_edit` 的封装
:param ids: 一个或多个文件或目录的 id
:param is_set: 是否显示时长
:return: 返回成功状态
"""
if isinstance(ids, (int, str)):
payload = {f"show_play_long[{ids}]": is_set}
else:
payload = {f"show_play_long[{id}]": is_set for id in ids}
return self.fs_batch_edit(payload, base_url=base_url, async_=async_, **request_kwargs)
@overload
def fs_folder_update_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_update_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_update_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置文件或目录,或者创建目录
POST https://proapi.115.com/{app}/folder/update
.. note::
如果提供了 `cid` 和 `name`,则表示对 `cid` 对应的文件或目录进行改名,否则创建目录
:payload:
- name: str 💡 名字
- pid: int | str = 0 💡 在此目录 id 下创建目录
- aid: int = 1 💡 area_id
- cid: int = <default> 💡 文件或目录的 id,优先级高于 `pid`
- user_id: int | str = <default> 💡 用户 id
- ...
"""
api = complete_url("/folder/update", base_url=base_url, app=app)
payload = {"aid": 1, "pid": 0, "user_id": self.user_id, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_folder_update_app2(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_folder_update_app2(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_folder_update_app2(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置文件或目录,或者创建目录
POST https://proapi.115.com/{app}/folder
.. note::
如果提供了 `cid` 和 `name`,则表示对 `cid` 对应的文件或目录进行改名,否则创建目录
:payload:
- name: str 💡 名字
- pid: int | str = 0 💡 在此目录 id 下创建目录
- aid: int = 1 💡 area_id
- cid: int = <default> 💡 文件或目录的 id,优先级高于 `pid`
- user_id: int | str = <default> 💡 用户 id
- ...
"""
api = complete_url("/folder", base_url=base_url, app=app)
payload = {"aid": 1, "pid": 0, "user_id": self.user_id, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_hide(
self,
payload: int | str | Iterable[int | str] | dict,
/,
hidden: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_hide(
self,
payload: int | str | Iterable[int | str] | dict,
/,
hidden: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_hide(
self,
payload: int | str | Iterable[int | str] | dict,
/,
hidden: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""隐藏或者取消隐藏某些文件或目录
POST https://webapi.115.com/files/hiddenfiles
:payload:
- fid[0]: int | str
- fid[1]: int | str
- ...
- hidden: 0 | 1 = 1
"""
api = complete_url("/files/hiddenfiles", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"fid[0]": payload}
elif not isinstance(payload, dict):
payload = {f"fid[{i}]": f for i, f in enumerate(payload)}
payload.setdefault("hidden", int(hidden))
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_hide_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
hidden: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_hide_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
hidden: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_hide_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
hidden: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""隐藏或者取消隐藏某些文件或目录
POST https://proapi.115.com/{app}/files/hiddenfiles
:payload:
- fid: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- hidden: 0 | 1 = 1
"""
api = complete_url("/files/hiddenfiles", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"fid": payload}
elif not isinstance(payload, dict):
payload = {"fid": ",".join(map(str, payload))}
cast(dict, payload).setdefault("hidden", int(hidden))
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_hidden_switch(
self,
payload: bool | int | str | dict = False,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_hidden_switch(
self,
payload: bool | int | str | dict = False,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_hidden_switch(
self,
payload: bool | int | str | dict = False,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""切换隐藏模式
POST https://115.com/?ct=hiddenfiles&ac=switching
.. tip::
开启隐藏模式时,需要提供安全密钥,关闭时则不需要
.. tip::
这个接口必须提供安全密钥。如果不提供,则默认使用 "000000",在不必要的情况下,完全可以把安全密钥设为这个值
.. note::
这个接口会返回一个 "token" 字段,可以提供给某些接口,作为通过安全密钥验证的凭证
:payload:
- safe_pwd: str = "000000" 💡 安全密钥
- show: 0 | 1 = <default> 💡 是否开启隐藏模式:0:关闭 1:开启
- valid_type: int = <default>
"""
api = complete_url(base_url=base_url, query={"ct": "hiddenfiles", "ac": "switching"})
if payload in (0, 1):
payload = {"show": int(cast(int, payload))}
elif isinstance(payload, (int, str)):
payload = {"show": 1, "safe_pwd": f"{payload:>06}"}
payload["safe_pwd"] = format(payload.get("safe_pwd") or "", ">06")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_hidden_switch_app(
self,
payload: bool | int | str | dict = False,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_hidden_switch_app(
self,
payload: bool | int | str | dict = False,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_hidden_switch_app(
self,
payload: bool | int | str | dict = False,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""切换隐藏模式
GET https://proapi.115.com/{app}/files/hiddenswitch
.. note::
可以在设置中的【账号安全/安全密钥】页面下,关闭【文件(隐藏模式/清空删除回收站)】的按钮,就不需要传安全密钥了
:payload:
- safe_pwd: str = "000000" 💡 安全密钥,值为实际安全密钥的 md5 哈希值
- show: 0 | 1 = <default> 💡 是否开启隐藏模式:0:关闭 1:开启
- token: str = <default> 💡 令牌,调用 `P115client.user_security_key_check()` 获得,可以不传
"""
api = complete_url("/files/hiddenswitch", base_url=base_url, app=app)
if payload in (0, 1):
payload = {"show": int(cast(int, payload))}
elif isinstance(payload, (int, str)):
payload = {"show": 1, "safe_pwd": payload}
payload["safe_pwd"] = md5_secret_password(payload.get("safe_pwd"))
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_history(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件的观看历史,主要用于视频
GET https://webapi.115.com/files/history
:payload:
- pick_code: str
- fetch: str = "one"
- category: int = 1 💡 类型:1:视频 3:音频
- share_id: int | str = <default>
"""
api = complete_url("/files/history", base_url=base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
payload = {"category": 1, "fetch": "one", **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
fs_video_history = fs_history
@overload
def fs_history_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取历史记录
GET https://proapi.115.com/{app}/history
:payload:
- pick_code: str
- fetch: str = "one"
- category: int = 1
- share_id: int | str = <default>
"""
api = complete_url("/history", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pick_code": payload}
payload = {"category": 1, "action": "get_one", **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
fs_video_history_app = fs_history_app
@overload
def fs_history_clean(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_clean(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_clean(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""清空历史记录
POST https://webapi.115.com/history/clean
:payload:
- type: int | str = 0 💡 类型(??表示还未搞清楚),多个用逗号 "," 隔开
- 全部: 0
- ??: 1(大概和接收有关)
- 云下载: 2
- 播放视频: 3
- 上传: 4
- ??: 5
- ??: 6(大概和名称冲突有关)
- 接收: 7
- 移动: 8
- with_file: 0 | 1 = 0 💡 是否同时删除文件
"""
api = complete_url("/history/clean", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"type": payload}
payload = {"with_file": 0, "type": 0, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_history_delete(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_delete(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_delete(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除历史记录
POST https://webapi.115.com/history/delete
:payload:
- id: int | str 💡 多个用逗号 "," 隔开
- type: int = 0
- with_file: 0 | 1 = 0
"""
api = complete_url("/history/delete", base_url=base_url)
if not isinstance(payload, dict):
payload = {"id": payload}
payload.setdefault("with_file", 0)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_history_delete_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_delete_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_delete_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除历史记录
POST https://proapi.115.com/{app}/history/delete
:payload:
- id: int | str 💡 多个用逗号 "," 隔开
- with_file: 0 | 1 = 0
"""
api = complete_url("/history/delete", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"id": payload}
payload.setdefault("with_file", 0)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_history_clean_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_clean_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_clean_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""清空历史记录
POST https://proapi.115.com/{app}/history/clean
:payload:
- type: int | str = 0 💡 类型(??表示还未搞清楚),多个用逗号 "," 隔开
- 全部: 0
- ??: 1(大概和接收有关)
- 云下载: 2
- 播放视频: 3
- 上传: 4
- ??: 5
- ??: 6(大概和名称冲突有关)
- 接收: 7
- 移动: 8
- with_file: 0 | 1 = 0
"""
api = complete_url("/history/clean", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"type": payload}
payload = {"with_file": 0, "type": 0, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_history_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""历史记录列表
GET https://webapi.115.com/history/list
:payload:
- offset: int = 0
- limit: int = 1150
- order: str = <default>
- played_end: 0 | 1 = <default> 💡 是否已经播放完
- search_value: str = ""
- type: int = <default> 💡 类型(??表示还未搞清楚),多个用逗号 "," 隔开
- 全部: 0
- ??: 1(大概和接收有关)
- 云下载: 2
- 播放视频: 3
- 上传: 4
- ??: 5
- ??: 6(大概和名称冲突有关)
- 接收: 7
- 移动: 8
"""
api = complete_url("/history/list", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_history_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""历史记录列表
GET https://proapi.115.com/{app}/history/list
:payload:
- offset: int = 0
- limit: int = 1150
- played_end: 0 | 1 = <default>
- type: int = <default> 💡 类型(??表示还未搞清楚),多个用逗号 "," 隔开
- 全部: 0
- ??: 1(大概和接收有关)
- 云下载: 2
- 播放视频: 3
- 上传: 4
- ??: 5
- ??: 6(大概和名称冲突有关)
- 接收: 7
- 移动: 8
"""
api = complete_url("/history/list", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_history_move_target_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_move_target_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_move_target_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""最近移动记录
GET https://webapi.115.com/history/move_target_list
.. tip::
使用这个方法,甚至可以随时获取近期有文件移入的目录,可以部分代替 115 生活的移动事件的使用
:payload:
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 1150 💡 分页大小,最大值不一定,看数据量,7,000 应该总是安全的,10,000 有可能报错,但有时也可以 20,000 而成功
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- custom_order: 0 | 1 | 2 = <default> 💡 是否使用记忆排序。如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 2
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- min_size: int = 0 💡 最小的文件大小
- max_size: int = 0 💡 最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_etime": 事件时间(无效,效果相当于 "user_utime")
- "user_utime": 修改时间
- "user_ptime": 创建时间(无效,效果相当于 "user_utime")
- "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
- qid: int = <default>
- search_value: str = <default> 💡 搜索文本
- show_dir: 0 | 1 = 1 💡 是否显示目录
- snap: 0 | 1 = <default>
- source: str = <default>
"""
api = complete_url("/history/move_target_list", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"cid": 0, "limit": 1150, "offset": 0, "aid": 1, "show_dir": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_history_receive_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_receive_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_receive_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""接收列表
GET https://webapi.115.com/history/receive_list
:payload:
- offset: int = 0
- limit: int = 1150
"""
api = complete_url("/history/receive_list", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_history_receive_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_receive_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_receive_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""接收列表
GET https://proapi.115.com/{app}/history/receive_list
:payload:
- offset: int = 0
- limit: int = 1150
"""
api = complete_url("/history/receive_list", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_history_rename_list(
self,
payload: int | str | Iterable[int | str] | dict,
/,
method: str = "POST",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_rename_list(
self,
payload: int | str | Iterable[int | str] | dict,
/,
method: str = "POST",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_rename_list(
self,
payload: int | str | Iterable[int | str] | dict,
/,
method: str = "POST",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""重命名历史记录
POST https://webapi.115.com/history/renamelist
:payload:
- file_ids: int | str 💡 文件 id,多个用逗号 "," 隔开
- offset: int = 0
- limit: int = <default>
- type: int = <default>
"""
api = complete_url("/history/renamelist", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_ids": str(payload)}
elif not isinstance(payload, dict):
payload = {"file_ids": ",".join(map(str, payload))}
return self.request(url=api, method=method, payload=payload, async_=async_, **request_kwargs)
@overload
def fs_history_rename_list_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
method: str = "POST",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_rename_list_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
method: str = "POST",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_rename_list_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
method: str = "POST",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""重命名历史记录
POST https://proapi.115.com/files/history/renamelist
:payload:
- file_ids: int | str 💡 文件 id,多个用逗号 "," 隔开
- offset: int = 0
- limit: int = <default>
- type: int = <default>
"""
api = complete_url("/files/history/renamelist", base_url=base_url, app=app)
if isinstance(payload, (int, )):
payload = {"file_ids": payload}
elif not isinstance(payload, dict):
payload = {"file_ids": ",".join(map(str, payload))}
return self.request(url=api, method=method, payload=payload, async_=async_, **request_kwargs)
@overload
def fs_history_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""更新文件的观看历史,主要用于视频和音频
POST https://webapi.115.com/files/history
:payload:
- pick_code: str 💡 文件的提取码
- op: str = "update" 💡 操作类型,具体有哪些还需要再研究
- category: int = 1 💡 类型:1:视频 3:音频
- definition: int = <default> 💡 视频清晰度
- share_id: int | str = <default>
- time: int = <default> 💡 播放时间点(用来向服务器同步播放进度)
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
- ...(其它未找全的参数)
"""
api = complete_url("/files/history", base_url=base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
payload = {"category": 1, "op": "update", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
fs_video_history_set = fs_history_set
@overload
def fs_history_set_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_history_set_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_history_set_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""更新文件的观看历史,主要用于视频
POST https://proapi.115.com/{app}/history
:payload:
- pick_code: str 💡 文件的提取码
- op: str = "update" 💡 操作类型,具体有哪些还需要再研究
- category: int = 1
- definition: int = <default> 💡 视频清晰度
- share_id: int | str = <default>
- time: int = <default> 💡 播放时间点(用来向服务器同步播放进度)
- watch_end: int = <default> 💡 视频是否播放播放完毕 0:未完毕 1:完毕
- ...(其它未找全的参数)
"""
api = complete_url("/files/hiddenswitch", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pick_code": payload}
payload = {"category": 1, "op": "update", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
fs_video_history_set_app = fs_history_set_app
@overload
def fs_image(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_image(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_image(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取图片的各种链接
GET https://webapi.115.com/files/image
:payload:
- pickcode: str 💡 同 ``pick_code``,优先级较高
- pick_code: str 💡 同 ``pickcode``
- share_id: int | str = <default>
"""
api = complete_url("/files/image", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_imagedata(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://imgjump.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_imagedata(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://imgjump.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_imagedata(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://imgjump.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取图片的分辨率等信息
POST https://imgjump.115.com/getimgdata_url
:payload:
- imgurl: str 💡 图片的访问链接
"""
api = complete_url("/getimgdata_url", base_url=base_url)
if isinstance(payload, str):
payload = {"imgurl": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_index_info(
self,
payload: Literal[0, 1] | bool | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_index_info(
self,
payload: Literal[0, 1] | bool | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_index_info(
self,
payload: Literal[0, 1] | bool | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前已用空间、可用空间、登录设备等信息
GET https://webapi.115.com/files/index_info
:payload:
- count_space_nums: 0 | 1 = 0 💡 是否获取明细:0:包含各种类型文件的数量统计 1:包含登录设备列表
"""
api = complete_url("/files/index_info", base_url=base_url)
if not isinstance(payload, dict):
payload = {"count_space_nums": int(payload)}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_label_add(
self,
payload: str | Iterable[str] | dict | list[tuple],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_add(
self,
payload: str | Iterable[str] | dict | list[tuple],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_add(
self,
payload: str | Iterable[str] | dict | list[tuple],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加标签(可以接受多个)
POST https://webapi.115.com/label/add_multi
:payload:
- name: str 💡 格式为 "{label_name}" 或 "{label_name}\x07{color}",例如 "tag\x07#FF0000"(中间有个 "\\x07")
- name[]: str
- ...
"""
api = complete_url("/label/add_multi", base_url=base_url)
if isinstance(payload, str):
payload = [("name[]", payload)]
elif not isinstance(payload, dict) or not isinstance(payload, list) and payload and not isinstance(payload[0], tuple):
payload = [("name[]", label) for label in payload if label]
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_add_app(
self,
payload: str | Iterable[str] | dict | list[tuple],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_add_app(
self,
payload: str | Iterable[str] | dict | list[tuple],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_add_app(
self,
payload: str | Iterable[str] | dict | list[tuple],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加标签(可以接受多个)
POST https://proapi.115.com/{app}/label/add_multi
:payload:
- name: str 💡 格式为 "{label_name}" 或 "{label_name}\x07{color}",例如 "tag\x07#FF0000"(中间有个 "\\x07")
- name[]: str
- ...
"""
api = complete_url("/label/add_multi", base_url=base_url, app=app)
if isinstance(payload, str):
payload = [("name[]", payload)]
elif not isinstance(payload, dict) or not isinstance(payload, list) and payload and not isinstance(payload[0], tuple):
payload = [("name[]", label) for label in payload if label]
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_del(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_del(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_del(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除标签
POST https://webapi.115.com/label/delete
:payload:
- id: int | str 💡 标签 id,多个用逗号 "," 隔开
"""
api = complete_url("/label/delete", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_del_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_del_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_del_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除标签
POST https://proapi.115.com/{app}/label/delete
:payload:
- id: int | str 💡 标签 id,多个用逗号 "," 隔开
"""
api = complete_url("/label/delete", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_edit(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_edit(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_edit(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""编辑标签
POST https://webapi.115.com/label/edit
:payload:
- id: int | str 💡 标签 id
- name: str = <default> 💡 标签名
- color: str = <default> 💡 标签颜色,支持 css 颜色语法
- sort: int = <default> 💡 序号
"""
api = complete_url("/label/edit", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_edit_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_edit_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_edit_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""编辑标签
POST https://proapi.115.com/{app}/label/edit
:payload:
- id: int | str 💡 标签 id
- name: str = <default> 💡 标签名
- color: str = <default> 💡 标签颜色,支持 css 颜色语法
- sort: int = <default> 💡 序号
"""
api = complete_url("/label/edit", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_files(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_files(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_files(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列标签的文件列表
GET https://webapi.115.com/label/files
:payload:
- label_id: int
- offset: 0
- limit: int = 1150
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- cid: int | str = 0 💡 目录 id
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数
- cur: 0 | 1 = <default> 💡 是否只显示当前目录
- custom_order: 0 | 1 | 2 = <default> 💡 是否使用记忆排序。如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 2
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- fields: str = <default>
- for: str = <default> 💡 文件格式,例如 "doc"
- is_q: 0 | 1 = <default>
- is_share: 0 | 1 = <default>
- min_size: int = 0 💡 最小的文件大小
- max_size: int = 0 💡 最大的文件大小(含),<= 0 表示不限,因此并不能借此仅筛选出空文件
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_etime": 事件时间(无效,效果相当于 "user_utime")
- "user_utime": 修改时间
- "user_ptime": 创建时间(无效,效果相当于 "user_utime")
- "user_otime": 上一次打开时间(无效,效果相当于 "user_utime")
- qid: int = <default>
- r_all: 0 | 1 = <default>
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- scid: int | str = <default>
- show_dir: 0 | 1 = 1 💡 是否显示目录
- snap: 0 | 1 = <default>
- source: str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-11: 大概相当于 1
- 12: 文档+图片+视频,相当于 1、2、4
- 13: ???,音频
- 14: ???,文档
- 15: 图片+视频,相当于 2、4
- 16: 字幕
- 17~98: 大概相当于 1
- 99: 所有文件
- >=100: 大概相当于 1
"""
api = complete_url("/label/files", base_url=base_url)
if not isinstance(payload, dict):
payload = {"label_id": payload}
payload = {"offset": 0, "limit": 1150, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_label_list(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_list(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_list(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列标签列表(如果要获取做了标签的文件列表,用 `fs_search` 接口)
GET https://webapi.115.com/label/list
:payload:
- offset: int = 0 💡 索引偏移,从 0 开始
- limit: int = 11500 💡 最多返回数量
- keyword: str = <default> 💡 搜索关键词
- sort: "name" | "update_time" | "create_time" = <default> 💡 排序字段
- 名称: "name"
- 添加时间: "create_time"
- 修改时间: "update_time"
- order: "asc" | "desc" = <default> 💡 排序顺序:"asc"(升序), "desc"(降序)
- count_file: 0 | 1 = <default>
"""
api = complete_url("/label/list", base_url=base_url)
if isinstance(payload, str):
payload = {"keyword": payload}
payload = {"offset": 0, "limit": 11500, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_label_list_app(
self,
payload: str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_list_app(
self,
payload: str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_list_app(
self,
payload: str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列标签列表(如果要获取做了标签的文件列表,用 `fs_search` 接口)
GET https://proapi.115.com/{app}/label/list
:payload:
- offset: int = 0 💡 索引偏移,从 0 开始
- limit: int = 11500 💡 最多返回数量
- keyword: str = <default> 💡 搜索关键词
- sort: "name" | "update_time" | "create_time" = <default> 💡 排序字段
- 名称: "name"
- 创建时间: "create_time"
- 更新时间: "update_time"
- order: "asc" | "desc" = <default> 💡 排序顺序:"asc"(升序), "desc"(降序)
"""
api = complete_url("/label/list", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"keyword": payload}
payload = {"offset": 0, "limit": 11500, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_label_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
label: int | str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
label: int | str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
label: int | str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置标签,此接口是对 `fs_edit` 的封装
.. attention::
这个接口会把标签列表进行替换,而不是追加
.. hint::
为单个文件或目录,设置一个不存在的标签 id,比如 1,会清空标签,但可产生事件(批量设置时无事件,可能是 bug)
.. code:: python
client.fs_label_set(id, 1)
"""
return self._fs_edit_set(
payload,
"file_label",
default=label,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_label_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
label: int | str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
label: int | str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
label: int | str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置标签,此接口是对 `fs_files_update_app` 的封装
.. attention::
这个接口会把标签列表进行替换,而不是追加
"""
return self._fs_edit_set_app(
payload,
"file_label",
default=label,
app=app,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_label_batch(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_batch(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_batch(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量设置标签
POST https://webapi.115.com/files/batch_label
:payload:
- action: "add" | "remove" | "reset" | "replace" 💡 操作名
- "add": 添加
- "remove": 移除
- "reset": 重设
- "replace": 替换
- file_ids: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
- file_label[{file_label}]: int | str = <default> 💡 action 为 replace 时使用此参数,file_label[{原标签id}]: {目标标签id},例如 file_label[123]: 456,就是把 id 是 123 的标签替换为 id 是 456 的标签
"""
api = complete_url("/files/batch_label", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_label_batch_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_label_batch_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_label_batch_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量设置标签
POST https://proapi.115.com/{app}/files/batch_label
:payload:
- action: "add" | "remove" | "reset" | "replace" 💡 操作名
- "add": 添加
- "remove": 移除
- "reset": 重设
- "replace": 替换
- file_ids: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- file_label: int | str = <default> 💡 标签 id,多个用逗号 "," 隔开
- file_label[{file_label}]: int | str = <default> 💡 action 为 replace 时使用此参数,file_label[{原标签id}]: {目标标签id},例如 file_label[123]: 456,就是把 id 是 123 的标签替换为 id 是 456 的标签
"""
api = complete_url("/files/batch_label", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_makedirs(
self,
payload: str | dict,
/,
pid: int | str = 0,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_makedirs(
self,
payload: str | dict,
/,
pid: int | str = 0,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_makedirs(
self,
payload: str | dict,
/,
pid: int | str = 0,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建目录(会尝试创建所有的中间节点)
.. note::
0. 此接口是对 ``fs_dir_getid2`` 的封装
1. 目录层级最多 25 级(不算文件节点的话)
2. 名字不能包含 3 个字符之一 "<>,如果包含,则会被替换为 _
3. 单个目录内最多 5 万个文件(用网页上传、创建 Office 文档此限制)
.. attention::
这个方法并不产生 115 生活的操作事件
:payload:
- path: str
- parent_id: int | str = 0
"""
if isinstance(payload, str):
payload = {"path": payload}
payload.setdefault("parent_id", pid)
payload["is_create"] = 1
return self.fs_dir_getid2(payload, async_=async_, **request_kwargs)
@overload
def fs_makedirs_app(
self,
payload: str | dict,
/,
pid: int | str = 0,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_makedirs_app(
self,
payload: str | dict,
/,
pid: int | str = 0,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_makedirs_app(
self,
payload: str | dict,
/,
pid: int | str = 0,
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建目录(会尝试创建所有的中间节点)
POST https://proapi.115.com/app/chrome/add_path
.. note::
1. 目录层级最多 25 级(不算文件节点的话)
2. 名字不能包含 3 个字符之一 "<>,如果包含,则会被替换为 _
3. 单个目录内最多 5 万个文件(用网页上传、创建 Office 文档此限制)
.. attention::
这个方法并不产生 115 生活的操作事件
:payload:
- path: str
- parent_id: int | str = 0
"""
if app in ("", "chrome"):
api = complete_url("/app/chrome/add_path", base_url)
else:
api = complete_url("/2.0/ufile/add_path", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"path": payload}
payload.setdefault("parent_id", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_mkdir(
self,
payload: str | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_mkdir(
self,
payload: str | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_mkdir(
self,
payload: str | dict,
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建目录
POST https://webapi.115.com/files/add
.. note::
1. 目录层级最多 25 级(不算文件节点的话)
2. 名字不能包含 3 个字符之一 "<>,但是文件可以通过上传来突破此限制
3. 单个目录内最多 5 万个文件(用网页上传、创建 Office 文档此限制)
:payload:
- cname: str 💡 新建目录名称,限制 255 个字符
- pid: int | str = 0 💡 上级目录的 id
"""
api = complete_url("/files/add", base_url=base_url)
if isinstance(payload, str):
payload = {"cname": payload}
payload.setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_mkdir_app(
self,
payload: dict | str,
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_mkdir_app(
self,
payload: dict | str,
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_mkdir_app(
self,
payload: dict | str,
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建目录,此接口是对 `fs_folder_update_app` 的封装
:payload:
- name: str 💡 新建目录名称,限制 255 个字符
- pid: int | str = 0 💡 上级目录的 id
"""
if isinstance(payload, str):
payload = {"name": payload}
payload.setdefault("pid", pid)
return self.fs_folder_update_app(
payload,
app=app,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_move(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_move(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_move(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""移动文件或目录
POST https://webapi.115.com/files/move
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
.. caution::
你可以把文件或目录移动到其它目录 id 下,即使是不存在的 id
因此,我定义了一个概念,悬空节点,此节点的 aid=1,但它有一个祖先节点,要么不存在,要么 aid != 1
你可以用 ``P115Client.tool_space()`` 方法,使用【校验空间】功能,把所有悬空节点找出来,放到根目录下的【修复文件】目录,此接口一天只能用一次
:payload:
- fid: int | str 💡 文件或目录 id,只接受单个 id
- fid[]: int | str
- ...
- fid[0]: int | str
- fid[1]: int | str
- ...
- pid: int | str = 0 💡 目标目录 id
- move_proid: str = <default> 💡 任务 id
- conflict_policy: str = <default>
"""
api = complete_url("/files/move", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"fid": payload}
elif not isinstance(payload, dict):
payload = {f"fid[{i}]": fid for i, fid in enumerate(payload)}
payload.setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_move_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_move_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_move_app(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""移动文件或目录
POST https://proapi.115.com/{app}/files/move
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- ids: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- to_cid: int | str 💡 目标目录 id
- user_id: int | str = <default> 💡 用户 id
"""
api = complete_url("/files/move", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"ids": payload}
elif not isinstance(payload, dict):
payload = {"ids": ",".join(map(str, payload))}
payload = {"to_cid": pid, "user_id": self.user_id, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_move_check_conflict(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_move_check_conflict(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_move_check_conflict(
self,
payload: int | str | dict | Iterable[int | str],
/,
pid: int | str = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""检查移动文件或目录时,是否有冲突
POST https://webapi.115.com/files/move_check_conflict
:payload:
- move_fids: int | str 💡 文件或目录 id,只接受单个 id
- move_fids[]: int | str
- ...
- move_fids[0]: int | str
- move_fids[1]: int | str
- ...
- pid: int | str = 0 💡 目标目录 id
"""
api = complete_url("/files/move_check_conflict", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"move_fids": payload}
elif not isinstance(payload, dict):
payload = {f"move_fids[{i}]": fid for i, fid in enumerate(payload)}
payload.setdefault("pid", pid)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_move_progress(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_move_progress(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_move_progress(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""移动任务的进度
GET https://webapi.115.com/files/move_progress
:payload:
- move_proid: str = <default> 💡 任务 id
"""
api = complete_url("/files/move_progress", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"move_proid": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取音乐信息(其实只有一个下载链接)
GET https://webapi.115.com/files/music
:payload:
- pickcode: str 💡 提取码
- topic_id: int = <default>
- music_id: int = <default>
- download: int = <default>
- platform: str = <default> 💡 如果取值为 "weixin",则会多一个字段 "audio_url"
- share_id: int | str = 0
"""
api = complete_url("/files/music", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
request_kwargs.setdefault("follow_redirects", False)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取音乐信息
GET https://proapi.115.com/{app}/music/musicplay
.. note::
即使文件格式不正确或者过大(超过 200 MB),也可返回一些信息(包括 parent_id),但如果是目录则信息匮乏(但由此也可判定一个目录)
:payload:
- pickcode: str 💡 提取码
- music_id: int = <default>
- topic_id: int = <default>
"""
api = complete_url("/music/musicplay", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_file_exist(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_file_exist(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_file_exist(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""检查音乐文件是否存在
GET https://webapi.115.com/files/music_file_exist
:payload:
- pickcode: str 💡 提取码
- topic_id: int = <default>
- music_id: int = <default>
- download: int = <default>
"""
api = complete_url("/files/music_file_exist", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_fond_list(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_fond_list(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_fond_list(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列星标听单
GET https://webapi.115.com/files/music_fond_list
"""
api = complete_url("/files/music_fond_list", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def fs_music_fond_list_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_fond_list_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_fond_list_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列星标听单
GET https://proapi.115.com/{app}/music/music_fond_list
"""
api = complete_url("/music/music_fond_list", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def fs_music_fond_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_fond_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_fond_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""给听单加减星标
POST https://webapi.115.com/files/music_topic_fond
:payload:
- topic_id: int
- fond: 0 | 1 = 1
"""
api = complete_url("/files/music_topic_fond", base_url=base_url)
if isinstance(payload, int):
payload = {"topic_id": payload}
payload.setdefault("fond", 1)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_music_include_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_include_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_include_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""包含音乐的目录列表(专属文件)
GET https://webapi.115.com/files/include_music_list
:payload:
- asc: 0 | 1 = 0
- limit: int = 1150
- offset: int = 0
- order: str = "user_etime"
"""
api = complete_url("/files/include_music_list", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"asc": 0, "limit": 1150, "order": "user_etime", **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_include_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_include_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_include_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""包含音乐的目录列表(专属文件)
GET https://proapi.115.com/{app}/music/include_music_list
:payload:
- asc: 0 | 1 = 0
- limit: int = 1150
- offset: int = 0
- order: str = "user_etime"
"""
api = complete_url("/music/include_music_list", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"asc": 0, "limit": 1150, "order": "user_etime", **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取音乐封面等信息
GET https://webapi.115.com/files/music_info
:payload:
- pickcode: str 💡 提取码
"""
api = complete_url("/files/music_info", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取音乐封面等信息
GET https://proapi.115.com/{app}/music/musicdetail
:payload:
- pickcode: str 💡 提取码
"""
api = complete_url("/music/musicdetail", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_list(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_list(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_list(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列听单中的文件
GET https://webapi.115.com/files/music_list
:payload:
- topic_id: int = 1 💡 听单 id。-1:星标 1:最近听过 2:最近接收
- start: int = 0
- limit: int = 1150
"""
api = complete_url("/files/music_list", base_url=base_url)
if isinstance(payload, int):
payload = {"topic_id": payload}
payload = {"start": 0, "limit": 1150, "topic_id": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_list_app(
self,
payload: int | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_list_app(
self,
payload: int | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_list_app(
self,
payload: int | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列听单中的文件
GET https://proapi.115.com/{app}/music/music_list
:payload:
- topic_id: int = 1 💡 听单 id。-1:星标 1:最近听过 2:最近接收
- start: int = 0
- limit: int = 1150
"""
api = complete_url("/music/music_list", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"topic_id": payload}
payload = {"start": 0, "limit": 1150, "topic_id": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_new(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_new(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_new(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列听单或听单中的文件
GET https://webapi.115.com/files/musicnew
:payload:
- topic_id: int = 1 💡 听单 id。-1:星标 1:最近听过 2:最近接收
- type: 0 | 1 = 0 💡 类型:0:文件 1:目录
- start: int = 0
- limit: int = 1150
"""
api = complete_url("/files/musicnew", base_url=base_url)
if isinstance(payload, int):
payload = {"topic_id": payload}
payload = {"start": 0, "limit": 1150, "type": 0, "topic_id": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_new_app(
self,
payload: int | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_new_app(
self,
payload: int | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_new_app(
self,
payload: int | dict = 1,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列听单或听单中的文件
GET https://proapi.115.com/{app}/music/musicnew
:payload:
- topic_id: int = 1 💡 听单 id。-1:星标 1:最近听过 2:最近接收
- type: 0 | 1 = 0 💡 类型:0:文件 1:目录
"""
api = complete_url("/music/musicnew", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"topic_id": payload}
payload = {"type": 0, "topic_id": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""从听单添加或移除音乐,或者给音乐加减星标
POST https://webapi.115.com/files/music
.. caution::
文件超过500MB,暂不支持添加到我听
:payload:
- file_id: int 💡 文件 id,多个用逗号 "," 隔开(op 为 "add" 和 "delete" 时需要)
- music_id: int = 1 💡 音乐 id(op 为 "fond" 时需要)
- topic_id: int = 1 💡 听单 id,默认为【最近听过】
- op: str = "add" 💡 操作类型:"add": 添加到听单, "delete": 从听单删除, "fond": 设置星标
- fond: 0 | 1 = 1 💡 是否星标(op 为 "fond" 时需要),这个星标和 music_id 有关,和 file_id 无关
"""
api = complete_url("/files/music", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif not isinstance(payload, dict):
payload = {"file_id": ",".join(map(str, payload))}
payload = {"op": "add", "fond": 1, "music_id": 1, "topic_id": 1, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_music_status(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_status(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_status(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""音乐状态
GET https://webapi.115.com/files/music_status
:payload:
- pickcode: str 💡 提取码
"""
api = complete_url("/files/music_status", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_topic_listnew(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_topic_listnew(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_topic_listnew(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列听单
GET https://webapi.115.com/files/music_topic_listnew
.. caution::
似乎查询参数并没有效果
:payload:
- fond: 0 | 1 = 0 💡 是否星标
- start: int = 0 💡 开始索引
- limit: int = 1150 💡 最多返回数量
- hidden: 0 | 1 = 0
"""
api = complete_url("/files/music_topic_listnew", base_url=base_url)
if isinstance(payload, int):
payload = {"start": payload}
payload = {"fond": 0, "hidden": 0, "limit": 1150, "start": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_topic_listnew_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_topic_listnew_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_topic_listnew_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列听单
GET https://proapi.115.com/{app}/music/musiclistnew
.. caution::
似乎查询参数并没有效果
:payload:
- fond: 0 | 1 = 0 💡 是否星标
- start: int = 0 💡 开始索引
- limit: int = 1150 💡 最多返回数量
"""
api = complete_url("/music/musiclistnew", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"start": payload}
payload = {"fond": 0, "limit": 1150, "start": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_music_topic_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_music_topic_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_music_topic_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改听单
POST https://webapi.115.com/files/music_topic
:payload:
- op: "edit" | "delete" | "add" 💡 操作类型:"edit":改名 "delete":删除 "add":添加
- topic_id: int = <default> 💡 听单 id(op 不为 "add" 时需要)
- topic_name: str = <default> 💡 听单名字(op 为 "add" 和 "edit" 时需要)
"""
api = complete_url("/files/music_topic", base_url=base_url)
if isinstance(payload, str):
payload = {"op": "add", "topic_name": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_office_check_complete(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_office_check_complete(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_office_check_complete(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""检查 office 保存任务完成情况
POST https://webapi.115.com/files/office?act=check_complete
:payload:
- office_id: int | str
"""
api = complete_url("/files/office", base_url=base_url, query={"act": "check_complete"})
if isinstance(payload, (int, str)):
payload = {"office_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_office_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_office_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_office_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""初始化 office
POST https://webapi.115.com/files/office?act=office_init
:payload:
- pickcode: str
"""
api = complete_url("/files/office", base_url=base_url, query={"act": "office_init"})
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_office_save(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_office_save(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_office_save(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""保存 office,即提交一个 office 的保存任务
POST https://webapi.115.com/files/office?act=save
.. todo::
临时数据需要经过 AES-128 加密,并用 WPS 的(类似如下)接口提交,以后等需要用到了再进行破解
https://kmon.kdocs.cn/api/relay/store/57QKSwAPhwr/log
.. caution::
用 office 来编辑文件,可以在 id 不变的情况下,修改文件数据,打破了文件一旦上传完成,内容就不会改变的固有认知
:payload:
- office_id: int | str
"""
api = complete_url("/files/office", base_url=base_url, query={"act": "save"})
if isinstance(payload, (int, str)):
payload = {"office_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_order_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_order_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_order_set(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置某个目录内文件的默认排序
POST https://webapi.115.com/files/order
:payload:
- user_order: str 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- file_id: int | str = 0 💡 目录 id,对应 parent_id
- user_asc: 0 | 1 = <default> 💡 是否升序排列
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- module: str = <default> 💡 "label_search" 表示用于搜索的排序
"""
api = complete_url("/files/order", base_url=base_url)
if isinstance(payload, str):
payload = {"user_order": payload}
payload = {"file_id": 0, "user_asc": 1, "user_order": "user_ptime", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_order_set_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_order_set_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_order_set_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置某个目录内文件的默认排序
POST https://proapi.115.com/{app}/2.0/ufile/order
.. error::
这个接口暂时并不能正常工作,应该是参数构造有问题,暂时请用 ``P115Client.fs_order_set()``
:payload:
- user_order: str 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- file_id: int | str = 0 💡 目录 id,对应 parent_id
- user_asc: 0 | 1 = <default> 💡 是否升序排列
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- module: str = <default> 💡 "label_search" 表示用于搜索的排序
"""
api = complete_url("/2.0/ufile/order", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"user_order": payload}
payload = {"file_id": 0, "user_asc": 1, "user_order": "user_ptime", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_preview(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_preview(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_preview(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""文档预览
POST https://webapi.115.com/files/preview
:payload:
- pickcode: str 💡 提取码
- share_id: int | str = <default>
"""
api = complete_url("/files/preview", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_rename(
self,
payload: tuple[int | str, str] | dict | Iterable[tuple[int | str, str]],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_rename(
self,
payload: tuple[int | str, str] | dict | Iterable[tuple[int | str, str]],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_rename(
self,
payload: tuple[int | str, str] | dict | Iterable[tuple[int | str, str]],
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""重命名文件或目录
POST https://webapi.115.com/files/batch_rename
.. caution::
改名时,虽然不能修改扩展名,但是一定要带上扩展名(无论是啥),不然会把最后一个句点 . 及其之后文字截断
:payload:
- files_new_name[{file_id}]: str 💡 值为新的文件名(basename)
"""
api = complete_url("/files/batch_rename", base_url=base_url)
if isinstance(payload, tuple) and len(payload) == 2 and isinstance(payload[0], (int, str)):
payload = {f"files_new_name[{payload[0]}]": payload[1]}
elif not isinstance(payload, dict):
payload = {f"files_new_name[{fid}]": name for fid, name in payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_rename_app(
self,
payload: tuple[int | str, str] | dict | Iterable[tuple[int | str, str]],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_rename_app(
self,
payload: tuple[int | str, str] | dict | Iterable[tuple[int | str, str]],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_rename_app(
self,
payload: tuple[int | str, str] | dict | Iterable[tuple[int | str, str]],
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""重命名文件或目录
POST https://proapi.115.com/{app}/files/batch_rename
.. caution::
改名时,虽然不能修改扩展名,但是一定要带上扩展名(无论是啥),不然会把最后一个句点 . 及其之后文字截断
:payload:
- files_new_name[{file_id}]: str 💡 值为新的文件名(basename)
"""
api = complete_url("/files/batch_rename", base_url=base_url, app=app)
if isinstance(payload, tuple) and len(payload) == 2 and isinstance(payload[0], (int, str)):
payload = {f"files_new_name[{payload[0]}]": payload[1]}
elif not isinstance(payload, dict):
payload = {f"files_new_name[{fid}]": name for fid, name in payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_rename_set_names(
self,
payload: dict | list[tuple[str, str | int]],
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_rename_set_names(
self,
payload: dict | list[tuple[str, str | int]],
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_rename_set_names(
self,
payload: dict | list[tuple[str, str | int]],
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""提交模拟批量重命名请求(提示:较为复杂,自己抓包研究)
POST https://aps.115.com/rename/set_names.php
"""
api = complete_url("/rename/set_names.php", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_rename_reset_names(
self,
payload: dict | list[tuple[str, str | int]],
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_rename_reset_names(
self,
payload: dict | list[tuple[str, str | int]],
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_rename_reset_names(
self,
payload: dict | list[tuple[str, str | int]],
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取批量重命名的模拟结果(提示:较为复杂,自己抓包研究)
POST https://aps.115.com/rename/reset_names.php
"""
api = complete_url("/rename/reset_names.php", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_repeat_sha1(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_repeat_sha1(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_repeat_sha1(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""查找重复文件(罗列除此以外的 sha1 相同的文件)
GET https://webapi.115.com/files/get_repeat_sha
:payload:
- file_id: int | str
- offset: int = 0
- limit: int = 1150
- source: str = ""
"""
api = complete_url("/files/get_repeat_sha", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload = {"offset": 0, "limit": 1150, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_repeat_sha1_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_repeat_sha1_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_repeat_sha1_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""查找重复文件(罗列除此以外的 sha1 相同的文件)
GET https://proapi.115.com/{app}/2.0/ufile/get_repeat_sha
:payload:
- file_id: int | str
- offset: int = 0
- limit: int = 1150
- source: str = ""
"""
api = complete_url("/2.0/ufile/get_repeat_sha", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload = {"offset": 0, "limit": 1150, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_score_set(
self,
file_id: int | str | Iterable[int | str],
/,
score: int = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_score_set(
self,
file_id: int | str | Iterable[int | str],
/,
score: int = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_score_set(
self,
file_id: int | str | Iterable[int | str],
/,
score: int = 0,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""给文件或目录评分
POST https://webapi.115.com/files/score
:payload:
- file_id: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- score: int | str = 0 💡 任意整数,<= 0 时网页上不会显示星星,如果是字符串,会尝试解析为整数,失败的话得到 0
"""
api = complete_url("/files/score", base_url=base_url)
if not isinstance(file_id, (int, str)):
file_id = ",".join(map(str, file_id))
payload = {"file_id": file_id, "score": score}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_search(
self,
payload: str | dict = ".",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_search(
self,
payload: str | dict = ".",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_search(
self,
payload: str | dict = ".",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""搜索文件或目录
GET https://webapi.115.com/files/search
.. attention::
最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
这个接口实际上不支持在查询中直接设置排序,只能由 ``P115Client.fs_order_set()`` 设置
.. note::
搜索接口甚至可以把上级 id 关联错误的文件或目录都搜索出来。一般是因为把文件或目录移动到了一个不存在的 id 下,你可以用某些关键词把他们搜索出来,然后移动到一个存在的目录中,就可以恢复他们了,或者使用 ``P115Client.tool_space()`` 接口来批量恢复
.. important::
一般使用的话,要提供 "search_value" 或 "file_label",不然返回数据里面看不到任何一条数据,即使你指定了其它参数
下面指定的很多参数其实是一点效果都没有的,具体可以实际验证
:payload:
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- cid: int | str = 0 💡 目录 id,对应 parent_id
- count_folders: 0 | 1 = <default> 💡 是否统计目录数,这样就会增加 "folder_count" 和 "file_count" 字段作为统计
- date: str = <default> 💡 筛选日期,格式为 YYYY-MM-DD(或者 YYYY-MM 或者 YYYY 或者 时间戳(限定在当天)),最小单位是天,其次是月,具体可以看文件信息中的 "t" 字段的值
- distance: int | str = <default>
- fc: 0 | 1 = <default> 💡 只显示文件或目录。1:只显示目录 2:只显示文件
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- file_label: int | str = <default> 💡 标签 id
- gte_day: str 💡 搜索结果匹配的开始时间;格式:YYYY-MM-DD
- limit: int = 32 💡 一页大小,意思就是 page_size
- location: str = <default>
- lte_day: str 💡 搜索结果匹配的结束时间;格式:YYYY-MM-DD
- natsort: 0 | 1 = <default> 💡 是否执行自然排序(natural sorting)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录)
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- qid: int | str = <default>
- resp_ver: int | str = <default>
- search_value: str = "." 💡 搜索文本,可以是 sha1
- show_dir: 0 | 1 = 1 💡 是否显示目录
- source: str = <default> 💡 来源
- star: 0 | 1 = <default> 💡 是否打星标
- stdir: 0 | 1 = <default> 💡 筛选文件时,是否显示目录:1:展示 0:不展示
- suffix: str = <default> 💡 文件后缀(扩展名),优先级高于 `type`
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 99: 所有文件
"""
api = complete_url("/files/search", base_url=base_url)
if isinstance(payload, str):
payload = {"search_value": payload}
payload = {
"aid": 1, "cid": 0, "limit": 32, "offset": 0,
"show_dir": 1, "search_value": ".", **payload,
}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_search_app(
self,
payload: str | dict = ".",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_search_app(
self,
payload: str | dict = ".",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_search_app(
self,
payload: str | dict = ".",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""搜索文件或目录
GET https://proapi.115.com/{app}/2.0/ufile/search
.. attention::
最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
:payload:
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- cid: int | str = 0 💡 目录 id。cid=-1 时,表示不返回列表任何内容
- count_folders: 0 | 1 = <default>
- date: str = <default> 💡 筛选日期,格式为 YYYY-MM-DD(或者 YYYY-MM 或者 YYYY 或者 时间戳(限定在当天)),最小单位是天,其次是月,具体可以看文件信息中的 "t" 字段的值
- fc: 0 | 1 = <default> 💡 只显示文件或目录。1:只显示目录 2:只显示文件
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- file_label: int | str = <default> 💡 标签 id
- gte_day: str 💡 搜索结果匹配的开始时间;格式:YYYY-MM-DD
- limit: int = 32 💡 一页大小,意思就是 page_size
- lte_day: str 💡 搜索结果匹配的结束时间;格式:YYYY-MM-DD
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- search_value: str = "." 💡 搜索文本,可以是 sha1
- source: str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 99: 所有文件
- version: str = <default> 💡 版本号,比如 3.1
"""
api = complete_url("/2.0/ufile/search", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"search_value": payload}
payload = {
"aid": 1, "cid": 0, "limit": 32, "offset": 0,
"show_dir": 1, "search_value": ".", **payload,
}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_search_app2(
self,
payload: str | dict = ".",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_search_app2(
self,
payload: str | dict = ".",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_search_app2(
self,
payload: str | dict = ".",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""搜索文件或目录
GET https://proapi.115.com/{app}/files/search
.. attention::
最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
不过就算正确设置了 `limit` 和 `offset`,并且总数据量大于 `limit + offset`,可能也不足 `limit`,这应该是 bug,也就是说,就算数据总量足够你也取不到足量
它返回数据中的 `count` 字段的值表示总数据量(即使你只能取前 10,000 条),往往并不准确,最多能当作一个可参考的估计值
:payload:
- aid: int = 1 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- cid: int | str = 0 💡 目录 id。cid=-1 时,表示不返回列表任何内容
- count_folders: 0 | 1 = <default>
- date: str = <default> 💡 筛选日期,格式为 YYYY-MM-DD(或者 YYYY-MM 或者 YYYY 或者 时间戳(限定在当天)),最小单位是天,其次是月,具体可以看文件信息中的 "t" 字段的值
- fc: 0 | 1 = <default> 💡 只显示文件或目录。1:只显示目录 2:只显示文件
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- file_label: int | str = <default> 💡 标签 id
- gte_day: str 💡 搜索结果匹配的开始时间;格式:YYYY-MM-DD
- limit: int = 32 💡 一页大小,意思就是 page_size
- lte_day: str 💡 搜索结果匹配的结束时间;格式:YYYY-MM-DD
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- search_value: str = "." 💡 搜索文本,可以是 sha1
- source: str = <default>
- star: 0 | 1 = <default> 💡 是否星标文件
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 99: 所有文件
- version: str = <default> 💡 版本号,比如 3.1
"""
api = complete_url("/files/search", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"search_value": payload}
payload = {
"aid": 1, "cid": 0, "limit": 32, "offset": 0,
"show_dir": 1, "search_value": ".", **payload,
}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_shasearch(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_shasearch(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_shasearch(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""通过 sha1 搜索文件
GET https://webapi.115.com/files/shasearch
.. note::
这是个非常早期的接口,高级功能请直接使用 `client.fs_search`。这个方法最多只能获得一条记录,并且不支持指定搜索目录,而且当未搜索到时,返回的信息为 '{"state": false, "error": "文件错误"}'
:payload:
- sha1: str
"""
api = complete_url("/files/shasearch", base_url=base_url)
if isinstance(payload, str):
payload = {"sha1": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_show_play_long_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
show: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_show_play_long_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
show: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_show_play_long_set(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
show: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为目录设置显示时长,此接口是对 `fs_edit` 的封装
"""
return self._fs_edit_set(
payload,
"show_play_long",
default=int(show),
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_show_play_long_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
show: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_show_play_long_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
show: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_show_play_long_set_app(
self,
payload: int | str | Iterable[int | str] | list[tuple] | dict,
/,
show: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为目录设置显示时长,此接口是对 `fs_files_update_app` 的封装
"""
return self._fs_edit_set_app(
payload,
"show_play_long",
default=int(show),
app=app,
base_url=base_url,
async_=async_,
**request_kwargs,
)
@overload
def fs_space_report(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_space_report(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_space_report(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取数据报告(截至月末数据,分组聚合)
GET https://webapi.115.com/user/report
:payload:
- month: str 💡 年月,格式为 YYYYMM
"""
api = complete_url("/user/report", base_url=base_url)
if not payload:
now = datetime.now()
year, month = now.year, now.month
if month == 1:
ym = f"{year-1}12"
else:
ym = f"{year}{month-1:02d}"
payload = {"month": ym}
elif isinstance(payload, str):
payload = {"month": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_space_summury(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_space_summury(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_space_summury(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取数据报告(当前数据,分组聚合,但并不精确)
POST https://webapi.115.com/user/space_summury
"""
api = complete_url("/user/space_summury", base_url=base_url)
return self.request(url=api, method="POST", async_=async_, **request_kwargs)
@overload
def fs_star_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_star_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_star_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置或取消星标
POST https://webapi.115.com/files/star
.. note::
如果其中任何一个 id 目前已经被删除,则会直接返回错误信息
:payload:
- file_id: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- star: 0 | 1 = 1
"""
api = complete_url("/files/star", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload, "star": int(star)}
elif not isinstance(payload, dict):
payload = {"file_id": ",".join(map(str, payload)), "star": int(star)}
else:
payload = {"star": int(star), **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_star_set_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_star_set_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_star_set_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
star: bool = True,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""为文件或目录设置或取消星标
POST https://proapi.115.com/{app}/files/star
.. note::
如果其中任何一个 id 目前已经被删除,则会直接返回错误信息
:payload:
- ids: int | str 💡 文件或目录 id,多个用逗号 "," 隔开
- star: 0 | 1 = 1
- user_id: int | str = <default> 💡 用户 id
"""
api = complete_url("/files/star", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"ids": payload, "star": int(star)}
elif not isinstance(payload, dict):
payload = {"ids": ",".join(map(str, payload)), "star": int(star)}
else:
payload = {"star": int(star), **payload}
payload["user_id"] = self.user_id
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_storage_info(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_storage_info(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_storage_info(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取使用空间的统计数据(最简略,如需更详细,请用 `fs.user_space_info()`)
GET https://115.com/index.php?ct=ajax&ac=get_storage_info
"""
api = complete_url("/index.php", base_url=base_url, query={"ct": "ajax", "ac": "get_storage_info"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def fs_supervision(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_supervision(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_supervision(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""通过 pickcode 获取文件信息
POST https://webapi.115.com/files/supervision
:payload:
- pickcode: str
- preview_type: str = "file" 💡 file:文件 doc:文档 video:视频 music:音乐 pic:图片
- module: int = 10
- share_id: int | str = <default>
"""
api = complete_url("/files/supervision", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
payload = {"preview_type": "file", "module": 10, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_supervision_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_supervision_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_supervision_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""通过 pickcode 获取文件信息
POST https://proapi.115.com/{app}/files/supervision
:payload:
- pickcode: str
- preview_type: str = "file" 💡 file:文件 doc:文档 video:视频 music:音乐 pic:图片
- module: int = 10
- share_id: int | str = <default>
"""
api = complete_url("/files/supervision", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
payload = {"preview_type": "file", "module": 10, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_sys_dir(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_sys_dir(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_sys_dir(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取系统目录(在根目录下,使用 `fs_files` 接口罗列时,数目体现在返回值的 `sys_count` 字段)
GET https://proapi.115.com/{app}/files/getpackage
:payload:
- sys_dir: int 💡 0:最近接收 1:手机相册 2:云下载 3:我的时光记录 4,10,20,21,22,30,40,50,60,70:(未知)
"""
api = complete_url("/files/getpackage", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"sys_dir": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_top_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
top: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_top_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
top: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_top_set(
self,
payload: int | str | Iterable[int | str] | dict,
/,
top: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""文件或目录置顶
POST https://webapi.115.com/files/top
:payload:
- file_id: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- top: 0 | 1 = 1
"""
api = complete_url("/files/top", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload, "top": int(top)}
elif not isinstance(payload, dict):
payload = {"file_id": ",".join(map(str, payload)), "top": int(top)}
else:
payload = {"top": int(top), **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频信息和 m3u8 链接列表
GET https://webapi.115.com/files/video
.. caution::
`local` 在有些视频上不起作用,无论如何,都相当于 `local=0`,可能是因为文件超过 200 MB
但如果 `local=1` 有效,则返回仅可得到下载链接,key 为 "download_url"
.. note::
如果返回信息中有 "queue_url",则可用于查询转码状态
如果视频从未被转码过,则会自动推送转码
:payload:
- pickcode: str 💡 提取码
- share_id: int | str = <default> 💡 共享 id
- local: 0 | 1 = <default> 💡 是否本地,如果为 1,则不包括 m3u8
"""
api = complete_url("/files/video", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_video_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频信息和 m3u8 链接列表
POST https://proapi.115.com/{app}/2.0/video/play
:payload:
- pickcode: str 💡 提取码
- share_id: int | str = <default> 💡 共享 id
- local: 0 | 1 = 0 💡 是否本地,如果为 1,则不包括 m3u8
- user_id: int | str = <default> 💡 用户 id
"""
api = complete_url("/2.0/video/play", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload, "user_id": self.user_id}
else:
payload = dict(payload, user_id=self.user_id)
def parse(_, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if json["state"] or json.get("errno") == 409:
json["data"] = json_loads(rsa_decrypt(json["data"]))
return json
request_kwargs.setdefault("parse", parse)
request_kwargs["data"] = {"data": rsa_encrypt(dumps(payload)).decode("ascii")}
return self.request(
url=api,
method="POST",
async_=async_,
**request_kwargs,
)
@overload
def fs_video_add_caption_map(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_add_caption_map(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_add_caption_map(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""关联字幕
POST https://webapi.115.com/movies/add_caption_map
:payload:
- file_ids: int | str 💡 多个用逗号 "," 隔开
- type: int = 2
"""
api = complete_url("/movies/add_caption_map", base_url=base_url)
if not isinstance(payload, dict):
payload = {"file_ids": payload}
payload.setdefault("type", 2)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_def_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_def_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_def_set(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""切换视频清晰度
POST https://webapi.115.com/files/video_def
:payload:
- definition: str
"""
api = complete_url("/files/video_def", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"definition": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_is_transcode(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_is_transcode(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_is_transcode(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频的转码进度
POST https://webapi.115.com/files/is_transcoded
:payload:
- pick_code: str
"""
api = complete_url("/files/is_transcoded", base_url=base_url)
if isinstance(payload, str):
payload = {"pick_code": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_m3u8(
self,
/,
pickcode: str,
definition: int = 0,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> bytes:
...
@overload
def fs_video_m3u8(
self,
/,
pickcode: str,
definition: int = 0,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, bytes]:
...
[docs]
def fs_video_m3u8(
self,
/,
pickcode: str,
definition: int = 0,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> bytes | Coroutine[Any, Any, bytes]:
"""获取视频的 m3u8 文件列表,此接口必须使用 web 的 cookies
GET https://115.com/api/video/m3u8/{pickcode}.m3u8?definition={definition}
.. attention::
这个接口只支持 web 的 cookies,其它设备会返回空数据,而且获取得到的 m3u8 里的链接,也是 m3u8,会绑定前一次请求时的 user-agent
:param pickcode: 视频文件的 pickcode
:param definition: 画质,默认列出所有画质。但可进行筛选,常用的为:
- 0: 各种分辨率(默认)
- 1: SD 标清(约为 480p)
- 3: HD 超清(约为 720p)
- 4: UD 1080P(约为 1080p)
- 5: BD 4K
- 100: 原画(尺寸和原始的相同)
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口返回值
"""
api = complete_url(f"/api/video/m3u8/{pickcode}.m3u8", base_url=base_url, query={"definition": definition})
request_kwargs.setdefault("parse", False)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def fs_video_multitrack_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_multitrack_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_multitrack_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置视频音轨
POST https://webapi.115.com/movies/user_multitrack
:payload:
- pick_code: str
- track_id: str
- user_id: int = <default>
"""
api = complete_url("/movies/user_multitrack", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_push(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_push(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_push(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""提交视频转码
POST https://115.com/?ct=play&ac=push
:payload:
- pickcode: str
- sha1: str = <default>
- op: str = "vip_push" 💡 提交视频加速转码方式
- "vip_push": 根据;vip 等级加速
- "pay_push": 枫叶加速
"""
api = complete_url(base_url=base_url, query={"ct": "play", "ac": "push"})
if isinstance(payload, str):
payload = {"pickcode": payload}
payload.setdefault("op", "vip_push")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_push_batch(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_push_batch(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_push_batch(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""提交视频转码
POST https://115.com/?ctl=play&ac=batch_push
:payload:
- file_ids: int | str 💡 多个用逗号 "," 隔开
- folder_ids: int | str 💡 多个用逗号 "," 隔开
"""
api = complete_url(base_url=base_url, query={"ctl": "play", "ac": "batch_push"})
if not isinstance(payload, dict):
payload = {"file_ids": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_push_batch2(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_push_batch2(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_push_batch2(
self,
payload: dict | int | str,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""提交视频转码
POST https://webapi.115.com/movies/batch_push
:payload:
- file_ids: int | str 💡 多个用逗号 "," 隔开
- folder_ids: int | str 💡 多个用逗号 "," 隔开
"""
api = complete_url("/movies/batch_push", base_url=base_url)
if not isinstance(payload, dict):
payload = {"file_ids": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_subtitle(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_subtitle(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_subtitle(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频字幕
GET https://webapi.115.com/movies/subtitle
:payload:
- pickcode: str
"""
api = complete_url("/movies/subtitle", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_video_subtitle_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_subtitle_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_subtitle_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频字幕
GET https://proapi.115.com/{app}/2.0/video/subtitle
:payload:
- pickcode: str
"""
api = complete_url("/2.0/video/subtitle", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"pickcode": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def fs_video_subtitle_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_subtitle_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_subtitle_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置视频字幕
POST https://webapi.115.com/movies/user_subtitle
:payload:
- pick_code: str
- sid: str 💡 字幕的 id,通过 ``P115Client.fs_video_subtitle()`` 接口获得
- action: str = "set" 💡 操作:"set":设置 "delete":删除
"""
api = complete_url("/movies/user_subtitle", base_url=base_url)
payload.setdefault("action", "set")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def fs_video_transcode(
self,
payload: dict | str,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://transcode.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def fs_video_transcode(
self,
payload: dict | str,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://transcode.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def fs_video_transcode(
self,
payload: dict | str,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://transcode.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取视频的转码进度
GET https://transcode.115.com/api/1.0/{app}/1.0/trans_code/check_transcode_job
:payload:
- sha1: str
- priority: int = 100 💡 优先级
"""
api = complete_url(f"/api/1.0/{app}/1.0/trans_code/check_transcode_job", base_url=base_url)
if isinstance(payload, str):
payload = {"sha1": payload}
payload.setdefault("priority", 100)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
########## Life API ##########
@overload
def life_batch_delete(
self,
payload: Iterable[dict] | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_batch_delete(
self,
payload: Iterable[dict] | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_batch_delete(
self,
payload: Iterable[dict] | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量删除操作历史:批量删除 115 生活事件列表
POST https://life.115.com/api/1.0/{app}/1.0/life/life_batch_delete
:payload:
- delete_data: str 💡 JSON array,每条数据格式为 {"relation_id": str, "behavior_type": str}
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/life_batch_delete", base_url=base_url)
if not isinstance(payload, dict):
payload = {"delete_data": (b"[%s]" % b",".join(map(dumps, payload))).decode("utf-8")}
return self.request(
url=api,
method="POST",
data=payload,
async_=async_,
**request_kwargs,
)
@overload
def life_behavior_detail(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_behavior_detail(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_behavior_detail(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 `P115Client.life_list` 操作记录明细
GET https://webapi.115.com/behavior/detail
.. attention::
这个接口最多能拉取前 10_000 条数据,且响应速度也较差,请优先使用 ``P115Client.life_behavior_detail_app()``。
但如果指定 ``type`` 或 ``date``,响应可能会快得多,但当涉及的总量较多时,速度依然会很慢,哪怕你只拉取其中的几条。
.. caution::
缺乏下面这些事件:
- 从回收站还原文件或目录(但相应的删除事件会消失)
:payload:
- type: str = "" 💡 操作类型,若不指定则是全部
- "upload_image_file": 1 💡 上传图片
- "upload_file": 2 💡 上传文件或目录(不包括图片)
- "star_image": 3 💡 给图片设置星标
- "star_file": 4 💡 给文件或目录设置星标(不包括图片)
- "move_image_file": 5 💡 移动图片
- "move_file": 6 💡 移动文件或目录(不包括图片)
- "browse_image": 7 💡 浏览图片
- "browse_video": 8 💡 浏览视频
- "browse_audio": 9 💡 浏览音频
- "browse_document": 10 💡 浏览文档
- "receive_files": 14 💡 接收文件
- "new_folder": 17 💡 新增目录
- "copy_folder": 18 💡 复制目录
- "folder_label": 19 💡 目录设置标签
- "folder_rename": 20 💡 目录改名
- "delete_file": 22 💡 删除文件或目录
- "copy_file": 23 💡 复制文件
- "file_rename": 24 💡 文件改名
- limit: int = 1_000 💡 最大值为 1_000
- offset: int = 0
- date: str = <default> 💡 日期,格式为 'YYYY-MM-DD',指定拉取这一天的数据
"""
api = complete_url("/behavior/detail", base_url=base_url)
if isinstance(payload, str):
payload = {"type": payload}
payload = {"limit": 1_000, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_behavior_detail_app(
self,
payload: str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_behavior_detail_app(
self,
payload: str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_behavior_detail_app(
self,
payload: str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 `P115Client.life_list` 操作记录明细
GET https://proapi.115.com/{app}/behavior/detail
.. caution::
缺乏下面这些事件:
- 从回收站还原文件或目录(但相应的删除事件会消失)
.. caution::
此接口有被风控的风险,两次调用之间相隔 5 秒以上为宜,但即使风控,恢复起来也比较快
:payload:
- type: str = "" 💡 操作类型
- "upload_image_file": 1 💡 上传图片
- "upload_file": 2 💡 上传文件或目录(不包括图片)
- "star_image": 3 💡 给图片设置星标
- "star_file": 4 💡 给文件或目录设置星标(不包括图片)
- "move_image_file": 5 💡 移动图片
- "move_file": 6 💡 移动文件或目录(不包括图片)
- "browse_image": 7 💡 浏览图片
- "browse_video": 8 💡 浏览视频
- "browse_audio": 9 💡 浏览音频
- "browse_document": 10 💡 浏览文档
- "receive_files": 14 💡 接收文件
- "new_folder": 17 💡 新增目录
- "copy_folder": 18 💡 复制目录
- "folder_label": 19 💡 目录设置标签
- "folder_rename": 20 💡 目录改名
- "delete_file": 22 💡 删除文件或目录
- "copy_file": 23 💡 复制文件
- "file_rename": 24 💡 文件改名
- limit: int = 1_000 💡 最大值为 1_000
- offset: int = 0
- date: str = <default> 💡 日期,格式为 'YYYY-MM-DD',指定拉取这一天的数据
"""
api = complete_url("/behavior/detail", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"type": payload}
payload = {"limit": 1_000, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_behavior_doc_post_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_behavior_doc_post_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_behavior_doc_post_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""推送事件:浏览文档 "browse_document"
POST https://proapi.115.com/{app}/files/doc_behavior
:payload:
- file_id: int | str
- file_id[0]: int | str
- file_id[1]: int | str
- ...
"""
api = complete_url("/files/doc_behavior", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif not isinstance(payload, dict):
payload = {f"file_id[{i}]": fid for i, fid in enumerate(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_behavior_img_post_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_behavior_img_post_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_behavior_img_post_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""推送事件:浏览图片 "browse_image"
POST https://proapi.115.com/{app}/files/img_behavior
:payload:
- file_id: int | str
- file_id[0]: int | str
- file_id[1]: int | str
- ...
"""
api = complete_url("/files/img_behavior", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
elif not isinstance(payload, dict):
payload = {f"file_id[{i}]": fid for i, fid in enumerate(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_calendar_getoption(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_calendar_getoption(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_calendar_getoption(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 115 生活的开关设置
GET https://life.115.com/api/1.0/{app}/1.0/calendar/getoption
.. hint::
app 可以是任意字符串,服务器并不做检查。其他可用 app="web" 的接口可能皆是如此
"""
api = complete_url(f"/api/1.0/{app}/1.0/calendar/getoption", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def life_calendar_getoption2(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_calendar_getoption2(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_calendar_getoption2(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 115 生活的开关设置
GET https://life.115.com/api/1.0/{app}/1.0/calendar/recent_operations_getoption
"""
api = complete_url(f"/api/1.0/{app}/1.0/calendar/recent_operations_getoption", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def life_calendar_setoption(
self,
payload: Literal[0, 1] | dict = 1,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_calendar_setoption(
self,
payload: Literal[0, 1] | dict = 1,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_calendar_setoption(
self,
payload: Literal[0, 1] | dict = 1,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置 115 生活的开关选项
POST https://life.115.com/api/1.0/{app}/1.0/calendar/setoption
:payload:
- locus: 0 | 1 = 1 💡 开启或关闭最近记录
- open_life: 0 | 1 = 1 💡 显示或关闭
- birthday: 0 | 1 = <default>
- holiday: 0 | 1 = <default>
- lunar: 0 | 1 = <default>
- view: 0 | 1 = <default>
- diary: 0 | 1 = <default>
- del_notice_item: 0 | 1 = <default>
- first_week: 0 | 1 = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/calendar/setoption", base_url=base_url)
if isinstance(payload, dict):
payload = {"locus": 1, "open_life": 1, **payload}
else:
payload = {"locus": 1, "open_life": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_calendar_setoption2(
self,
payload: Literal[0, 1] | dict = 1,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_calendar_setoption2(
self,
payload: Literal[0, 1] | dict = 1,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_calendar_setoption2(
self,
payload: Literal[0, 1] | dict = 1,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置 115 生活的开关选项
POST https://life.115.com/api/1.0/{app}/1.0/calendar/recent_operations_setoption
:payload:
- locus: 0 | 1 = 1 💡 开启或关闭最近记录
- open_life: 0 | 1 = 1 💡 显示或关闭
- birthday: 0 | 1 = <default>
- holiday: 0 | 1 = <default>
- lunar: 0 | 1 = <default>
- view: 0 | 1 = <default>
- diary: 0 | 1 = <default>
- del_notice_item: 0 | 1 = <default>
- first_week: 0 | 1 = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/calendar/recent_operations_setoption", base_url=base_url)
if isinstance(payload, dict):
payload = {"locus": 1, "open_life": 1, **payload}
else:
payload = {"locus": 1, "open_life": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_cdlist(
self,
payload: int | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_cdlist(
self,
payload: int | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_cdlist(
self,
payload: int | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取节假日等
GET https://life.115.com/api/1.0/{app}/1.0/life/cdlist
:payload:
- start_time: int = <default> 💡 开始时间戳,单位是秒,默认为当年第一天零点
- end_time: int = <default> 💡 开始时间戳,单位是秒,默认为次年第一天零点前一秒
- holiday: 0 | 1 = <default> 💡 是否显示节假日
- only_public: 0 | 1 = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/cdlist", base_url)
if isinstance(payload, int):
payload = {"end_time": payload}
else:
payload = dict(payload)
if "start_time" not in payload:
this_year = date.today().year
payload["start_time"] = int(datetime(this_year, 1, 1).timestamp())
if "end_time" not in payload:
this_year = datetime.fromtimestamp(int(payload["start_time"])).year
payload["end_time"] = int(datetime(this_year + 1, 1, 1).timestamp()) - 1
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_get_pic_url(
self,
payload: str | tuple[str, ...] | dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_get_pic_url(
self,
payload: str | tuple[str, ...] | dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_get_pic_url(
self,
payload: str | tuple[str, ...] | dict | list,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量获取图片的预览图链接
POST https://life.115.com/api/1.0/{app}/1.0/imgload/get_pic_url
.. hint::
这个接口获取的链接似乎长久有效,而且支持任何文件(只要有人上传过),但限制文件大小在 50 MB 以内
.. tip::
在获得的链接最后加上一个 ``&i=1``,就可以获取原始尺寸(但不一定是原图)
:payload:
- rs: str 💡 图片的 sha1 (必须大写)或者 f"{oss_bucket}_{oss_object}"(由 `upload_file_image` 接口的响应获得),后者跳转次数更少、响应更快
- rs[]: str
- ...
- rs[0]: str
- rs[1]: str
- ...
- module: int = <default>
- file_names[]: str = <default>
- ...
- type[]: int = <default>
- ...
"""
api = complete_url(f"/api/1.0/{app}/1.0/imgload/get_pic_url", base_url=base_url)
if isinstance(payload, str):
payload = {"rs": payload}
elif isinstance(payload, tuple):
payload = [("rs[]", s) for s in payload]
return self.request(
url=api,
method="POST",
data=payload,
async_=async_,
**request_kwargs,
)
@overload
def life_get_pic_url2(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_get_pic_url2(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_get_pic_url2(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量获取图片的预览图链接
POST https://q.115.com/ajax_request/get_pic_url
.. hint::
这个接口获取的链接似乎长久有效,而且支持任何文件(只要有人上传过),但限制文件大小在 50 MB 以内
.. tip::
在获得的链接最后加上一个 ``&i=1``,就可以获取原始尺寸(但不一定是原图)
:payload:
- rs: str 💡 图片的 sha1 (必须大写)或者 f"{oss_bucket}_{oss_object}"(由 `upload_file_image` 接口的响应获得),后者跳转次数更少、响应更快
- rs[]: str
- ...
- rs[0]: str
- rs[1]: str
- ...
- module: int = <default>
- file_names[]: str = <default>
- ...
- type[]: int = <default>
- ...
"""
api = complete_url("/ajax_request/get_pic_url", base_url=base_url)
if isinstance(payload, str):
payload = {"rs": payload}
elif isinstance(payload, tuple):
payload = [("rs[]", s) for s in payload]
return self.request(
url=api,
method="POST",
data=payload,
async_=async_,
**request_kwargs,
)
@overload
def life_clear_history(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_clear_history(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_clear_history(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""清空操作历史:清空 115 生活事件列表
POST https://life.115.com/api/1.0/{app}/1.0/life/life_clear_history
:payload:
- tab_type: 0 | 1 = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/life_clear_history", base_url=base_url)
if isinstance(payload, int):
payload = {"tab_type": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_glist(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_glist(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_glist(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取备忘(记录/笔记/记事)、日记或日程的列表
GET https://life.115.com/api/1.0/{app}/1.0/life/glist
.. note::
返回数据列表中,每一条都有个 `"type"` 字段,这个和请求参数里面的 `"type"` 含义并不同
- 2: 备忘
- 3: 日程
- 4: 瞬间
- 5: 日记
:payload:
- start: int = 0 💡 开始索引,从 0 开始
- limit: int = <default> 💡 分页大小
- type: int = 8 💡 分类:1,6:瞬间 2:日记+日程 3:备忘 4,7:瞬间+备忘 5:日记 8:所有(日记+备忘+日程)
- only_public: 0 | 1 = <default>
- msg_note: 0 | 1 = <default>
- option: 0 | 1 = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/glist", base_url)
if isinstance(payload, int):
payload = {"start": payload}
else:
payload = dict(payload)
payload.setdefault("type", 8)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_has_data(
self,
payload: int | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_has_data(
self,
payload: int | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_has_data(
self,
payload: int | dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取有数据的那几天零点的时间戳
GET https://life.115.com/api/1.0/{app}/1.0/life/life_has_data
:payload:
- end_time: int = <default>
- show_note_cal: 0 | 1 = <default>
- start_time: int = <default>
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/life_has_data", base_url=base_url)
if isinstance(payload, int):
payload = {"start_time": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_list(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_list(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_list(
self,
payload: int | str | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列登录和增删改操作记录(最新几条)
GET https://life.115.com/api/1.0/{app}/1.0/life/life_list
.. note::
为了实现分页拉取,需要指定 last_data 参数。只要上次返回的数据不为空,就会有这个值,直接使用即可
.. attention::
此接口正在被 `P115Client.life_recent_operations` 取代
.. hint::
引用:https://cdnres.115.com/life/m_r/web/static_v11.0/homepage/lifetime.js
- 'upload_file' => '上传文件' 💡 上传文件(非图片) 文件类
- 'upload_image_file' => '上传图片' 💡 上传文件(图片) 文件类
- 'backup_album' => '备份相册' 💡 备份相册 文件类
- 'sync_communication' => '同步通讯录' 💡 同步通讯录 文件类
- 'receive_files' => '接收文件' 💡 接收文件 文件类
- 'star_file' => '星标文件' 💡 星标文件 文件类
- 'radar_sharing' => '雷达分享' 💡 雷达分享 文件类
- 'file_search' => '文件搜索' 💡 文件搜索 文件类
- 'move_file' => '移动文件' 💡 移动文件(非图片) 文件类
- 'move_image_file' => '移动图片' 💡 移动文件(图片) 文件类
- 'browse_document' => '浏览文档' 💡 浏览文档 信息预览类
- 'browse_video' => '浏览视频' 💡 浏览视频 信息预览类
- 'browse_audio' => '浏览音频' 💡 浏览音频 信息预览类
- 'browse_image' => '浏览图片' 💡 浏览图片 信息预览类
- 'publish_record' => '发布记录' 💡 发布记录 信息发布类
- 'publish_calendar' => '发布日程' 💡 发布日程 信息发布类
- 'publish_home' => '发布传说' 💡 发布传说 信息发布类
- 'account_security' => '账号安全' 💡 账号安全 账号安全类
一些筛选条件::
- 全部:type=0
- 上传文件:type=1&file_behavior_type=1
- 浏览文件:type=1&file_behavior_type=2
- 星标文件:type=1&file_behavior_type=3
- 移动文件:type=1&file_behavior_type=4
- 目录:type=1&file_behavior_type=5
- 备份:type=1&file_behavior_type=6
- 删除文件:type=1&file_behavior_type=7
- 账号安全:type=2
- 通讯录:type=3
- 其他:type=99
一些类型分类::
.. code:: python
{
'file':['upload_file', 'upload_image_file', 'backup_album', 'sync_communication',
'receive_files', 'star_file', 'radar_sharing', 'file_search', 'move_file',
'move_image_file', 'star_image', 'del_photo_image', 'del_similar_image',
'generate_smart_albums', 'new_person_albums', 'del_person_albums',
'generate_photo_story', 'share_photo', 'folder_rename', 'folder_label',
'new_folder', 'copy_folder', 'delete_file'],
'review':['browse_video', 'browse_document', 'browse_audio', 'browse_image'],
'edit':['publish_record', 'publish_calendar', 'publish_home'],
'safe':['account_security'],
'cloud':[],
'share': ['share_contact']
}
:payload:
- start: int = 0
- limit: int = 1_000
- check_num: int = <default> 💡 选中记录数
- del_data: str = <default> 💡 JSON array,删除时传给接口数据
- end_time: int = <default> 💡 结束时间戳
- file_behavior_type: int | str = <default> 💡 筛选类型,有多个则用逗号 ',' 隔开
- 💡 0: 所有
- 💡 1: 上传
- 💡 2: 浏览
- 💡 3: 星标
- 💡 4: 移动
- 💡 5: 标签
- 💡 6: <UNKNOWN>
- 💡 7: 删除
- isPullData: 'true' | 'false' = <default> 💡 是否下拉加载数据
- isShow: 0 | 1 = <default> 💡 是否显示
- last_data: str = <default> 💡 JSON object, e.g. '{"last_time":1700000000,"last_count":1,"total_count":200}'
- mode: str = <default> 💡 操作模式
- 💡 "show" 展示列表模式
- 💡 "select": 批量操作模式
- selectedRecords: str = <default> 💡 JSON array,选中记录 id 数组
- show_note_cal: 0 | 1 = <default>
- show_type: int = 0 💡 筛选类型,有多个则用逗号 ',' 隔开
- 💡 0: 所有
- 💡 1: 增、删、改、移动、上传、接收、设置标签等文件系统操作
- 💡 2: 浏览文件
- 💡 3: <UNKNOWN>
- 💡 4: account_security
- start_time: int = <default> 💡 开始时间戳
- tab_type: int = <default>
- total_count: int = <default> 💡 列表所有项数
- type: int = <default> 💡 类型
"""
if app in ("", "desktop", "chrome", "aps"):
app = "web"
api = complete_url(f"/api/1.0/{app}/1.0/life/life_list", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"start": payload}
payload = {"limit": 1_000, "show_type": 0, "start": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_recent_browse(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_recent_browse(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_recent_browse(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取最近浏览记录
GET https://life.115.com/api/1.0/{app}/1.0/life/recent_browse
:payload:
- start: int = 0
- limit: int = 1000
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/recent_browse", base_url=base_url)
if isinstance(payload, int):
payload = {"start": payload}
payload.setdefault("limit", 1000)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_recent_operation_items(
self,
payload: str | dict = "browse_document",
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_recent_operation_items(
self,
payload: str | dict = "browse_document",
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_recent_operation_items(
self,
payload: str | dict = "browse_document",
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取最近操作记录详细
GET https://life.115.com/api/1.0/{app}/1.0/life/recent_operation_items
.. caution::
这个接口目前必须传 ``behavior_type`` 和 ``date``,而且支持的 ``behavior_type`` 仅限浏览、移动、复制、重命名
.. caution::
谨慎,此接口明显是半成品,几乎和 `P115Client.life_behavior_detail` 一样慢,但能力却似乎远远不如
:payload:
- behavior_type: str 💡 操作类型(尾部带🚫的表示暂不可用)
- "upload_image_file": 1 💡 上传图片🚫
- "upload_file": 2 💡 上传文件或目录(不包括图片)🚫
- "star_image": 3 💡 给图片设置星标🚫
- "star_file": 4 💡 给文件或目录设置星标(不包括图片)🚫
- "move_image_file": 5 💡 移动图片
- "move_file": 6 💡 移动文件或目录(不包括图片)
- "browse_image": 7 💡 浏览图片
- "browse_video": 8 💡 浏览视频
- "browse_audio": 9 💡 浏览音频
- "browse_document": 10 💡 浏览文档
- "receive_files": 14 💡 接收文件🚫
- "new_folder": 17 💡 新增目录🚫
- "copy_folder": 18 💡 复制目录
- "folder_label": 19 💡 目录设置标签🚫
- "folder_rename": 20 💡 目录改名
- "delete_file": 22 💡 删除文件或目录🚫
- "copy_file": 23 💡 复制文件
- "file_rename": 24 💡 文件改名
- date: str 💡 日期,格式为 'YYYY-MM-DD',指定拉取这一天的数据
- start: int = 0
- limit: int = 100 💡 最大值为 100
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/recent_operation_items", base_url=base_url)
if isinstance(payload, str):
payload = {"behavior_type": payload}
payload = {"limit": 100, "date": str(date.today()), **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_recent_operations(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_recent_operations(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_recent_operations(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取最近操作记录
GET https://life.115.com/api/1.0/{app}/1.0/life/recent_operations
:payload:
- start: int = 0
- limit: int = 1_000
- start_time: int = <default>
- end_time: int = <default>
- last_data: str = <default> 💡 需要经过 JSON序列化,格式为:{"last_time": int, "last_count": int, "total_count": int}
- operation_type: 0 | 1 | 2 | 3 = 0 💡 操作类型
- 0: 全部
- 1: 浏览
- 2: 移动复制
- 3: 重命名
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/recent_operations", base_url=base_url)
if isinstance(payload, int):
payload = {"start": payload}
payload.setdefault("limit", 1_000)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def life_recent_operations_clear(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_recent_operations_clear(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_recent_operations_clear(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""清空最近操作记录
GET https://life.115.com/api/1.0/{app}/1.0/life/recent_operations_clear
:payload:
- tab_type: int = 0
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/recent_operations_clear", base_url=base_url)
if isinstance(payload, int):
payload = {"tab_type": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_recent_operations_del(
self,
payload: Iterable[dict] | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_recent_operations_del(
self,
payload: Iterable[dict] | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_recent_operations_del(
self,
payload: Iterable[dict] | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量删除最近操作记录
GET https://life.115.com/api/1.0/{app}/1.0/life/recent_operations_del
:payload:
- delete_data: str 💡 JSON array,每条数据格式为 {"relation_id": str, "behavior_type": str}
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/recent_operations_del", base_url=base_url)
if not isinstance(payload, dict):
payload = {"delete_data": (b"[%s]" % b",".join(map(dumps, payload))).decode("utf-8")}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def life_set_top(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def life_set_top(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def life_set_top(
self,
payload: dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://life.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""切换备忘(记录/笔记/记事)、日记或日程的置顶状态
GET https://life.115.com/api/1.0/{app}/1.0/life/set_top
.. attention::
这个接口会自动切换记录的置顶状态,但不支持手动指定是否置顶,只是在置顶和不置顶间来回切换。
:payload:
- relation_id: int | str 💡 备忘、日程或日记的 id
- type: int 💡 分类:2:备忘 3:日程 4:瞬间 5:日记
"""
api = complete_url(f"/api/1.0/{app}/1.0/life/set_top", base_url)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
########## Login API ##########
@overload
def login_app(
self,
/,
async_: Literal[False] = False,
**request_kwargs,
) -> None | str:
...
@overload
def login_app(
self,
/,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, None | str]:
...
[docs]
def login_app(
self,
/,
async_: Literal[False, True] = False,
**request_kwargs,
) -> None | str | Coroutine[Any, Any, None | str]:
"""获取当前的登录设备名,如果为 None,说明未能获得
"""
def gen_step():
ssoent = self.login_ssoent
if not ssoent:
return None
if ssoent in SSOENT_TO_APP:
return SSOENT_TO_APP[ssoent]
device = yield self.login_device(async_=async_, **request_kwargs)
if device is None:
return None
return device["icon"]
return run_gen_step(gen_step, async_)
@overload
def login_open_auth_detail(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_open_auth_detail(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_open_auth_detail(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取某个开放应用的授权信息
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/user/getAppAuthDetail
:payload:
- auth_id: int | str 💡 授权 id
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/getAppAuthDetail", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"auth_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def login_open_auth_list(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_open_auth_list(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_open_auth_list(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取所有授权的开放应用的列表
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/user/getAppAuthList
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/getAppAuthList", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def login_open_deauth(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_open_deauth(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_open_deauth(
self,
payload: int | str | dict,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""取消某个开放应用的授权
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/user/deauthApp
:payload:
- auth_id: int | str 💡 授权 id
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/deauthApp", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"auth_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def login_check_sso(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_check_sso(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_check_sso(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""检查当前 cookies 的登录状态信息,并且自最近一次登录的 60 秒后,使当前设备下除最近一次登录外的所有 cookies 失效
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/check/sso
"""
api = complete_url(f"/app/1.0/{app}/1.0/check/sso", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def login_device(
self,
/,
async_: Literal[False] = False,
**request_kwargs,
) -> None | dict:
...
@overload
def login_device(
self,
/,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, None | dict]:
...
[docs]
def login_device(
self,
/,
async_: Literal[False, True] = False,
**request_kwargs,
) -> None | dict | Coroutine[Any, Any, None | dict]:
"""获取当前的登录设备的信息,如果为 None,也不代表当前的 cookies 被下线,只能说明有更晚的登录到同一设备
"""
def parse(_, content: bytes, /) -> None | dict:
login_devices = json_maybe_decrypt_loads(content)
if not login_devices["state"]:
return None
return next(filter(cast(Callable, itemgetter("is_current")), login_devices["data"]["list"]), None)
request_kwargs.setdefault("parse", parse)
return self.login_devices(async_=async_, **request_kwargs)
@overload
def login_devices(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_devices(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_devices(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取所有的已登录设备的信息,不过当前的 cookies 必须是登录状态(未退出或未失效)
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/login_log/login_devices
"""
api = complete_url(f"/app/1.0/{app}/1.0/login_log/login_devices", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def login_info(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_info(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_info(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取登录信息
GET https://proapi.115.com/{app}/2.0/login_info
"""
api = complete_url("/2.0/login_info", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def login_log(
self,
payload: dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_log(
self,
payload: dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_log(
self,
payload: dict = {},
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取登录信息日志列表
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/login_log/log
:payload:
- start: int = 0
- limit: int = 100
"""
api = complete_url(f"/app/1.0/{app}/1.0/login_log/log", base_url=base_url)
payload = {"start": 0, "limit": 100, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def login_online(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def login_online(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def login_online(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""当前登录的设备总数和最近登录的设备
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/login_log/login_online
"""
api = complete_url(f"/app/1.0/{app}/1.0/login_log/login_online", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def login_status(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> bool:
...
@overload
def login_status(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, bool]:
...
[docs]
def login_status(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> bool | Coroutine[Any, Any, bool]:
"""检查是否已登录
GET https://my.115.com/?ct=guide&ac=status
"""
api = complete_url(base_url=base_url, query={"ct": "guide", "ac": "status"})
def parse(_, content: bytes, /) -> bool:
try:
return json_maybe_decrypt_loads(content)["state"]
except:
return False
request_kwargs.setdefault("parse", parse)
return self.request(url=api, async_=async_, **request_kwargs)
@property
def login_ssoent(self, /) -> str:
"""获取当前的登录设备 ssoent,如果为空,说明未能获得(会直接获取 Cookies 中名为 UID 字段的值,所以即使能获取,也不能说明登录未失效)
"""
return self.cookies_str.UID.ssoent
########## Logout API ##########
@overload
def logout(
self,
/,
app: None | str = "android",
method: str = "POST",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> None:
...
@overload
def logout(
self,
/,
app: None | str = "android",
method: str = "POST",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, None]:
...
[docs]
def logout(
self,
/,
app: None | str = "android",
method: str = "POST",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> None | Coroutine[Any, Any, None]:
"""当前设备退出登录状态
POST https://qrcodeapi.115.com/app/1.0/{app}/1.0/logout/index
.. tip::
无论指定什么 ``app``,都是退出当前 cookies 所对应的登录设备
"""
api = complete_url(f"/app/1.0/{app}/1.0/logout/index", base_url=base_url)
return self.request(api, method=method, async_=async_, **request_kwargs)
@overload
def logout_by_app(
self,
/,
app: None | str = None,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> None:
...
@overload
def logout_by_app(
self,
/,
app: None | str = None,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, None]:
...
[docs]
def logout_by_app(
self,
/,
app: None | str = None,
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> None | Coroutine[Any, Any, None]:
"""退出登录状态(可以把某个客户端下线,所有已登录设备可从 `login_devices` 获取)
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/logout/logout
:param app: 退出登录的 app
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
def gen_step():
nonlocal app
if app is None:
app = yield self.login_app(async_=async_)
if app == "desktop":
app = "web"
api = complete_url(f"/app/1.0/{app}/1.0/logout/logout", base_url=base_url)
request_kwargs.setdefault("parse", lambda *a: None)
return self.request(url=api, async_=async_, **request_kwargs)
return run_gen_step(gen_step, async_)
@overload
def logout_by_ssoent(
self,
payload: None | str | dict = None,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def logout_by_ssoent(
self,
payload: None | str | dict = None,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def logout_by_ssoent(
self,
payload: None | str | dict = None,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""退出登录状态(可以把某个客户端下线,所有已登录设备可从 `login_devices` 获取)
POST https://qrcodeapi.115.com/app/1.0/{app}/1.0/logout/mange
:payload:
- ssoent: str
-----
:设备列表如下:
+-------+----------+------------+----------------------+
| No. | ssoent | app | description |
+=======+==========+============+======================+
| 01 | A1 | web | 115生活_网页端 |
+-------+----------+------------+----------------------+
| -- | A1 | desktop | 115浏览器 |
+-------+----------+------------+----------------------+
| -- | A2 | ? | 未知: android |
+-------+----------+------------+----------------------+
| -- | A3 | ? | 未知: ios |
+-------+----------+------------+----------------------+
| -- | A4 | ? | 未知: ipad |
+-------+----------+------------+----------------------+
| -- | B1 | ? | 未知: android |
+-------+----------+------------+----------------------+
| 02 | D1 | ios | 115生活_苹果端 |
+-------+----------+------------+----------------------+
| 03 | D2 | bios | 未知: ios |
+-------+----------+------------+----------------------+
| 04 | D3 | 115ios | 115_苹果端 |
+-------+----------+------------+----------------------+
| 05 | F1 | android | 115生活_安卓端 |
+-------+----------+------------+----------------------+
| 06 | F2 | bandroid | 未知: android |
+-------+----------+------------+----------------------+
| 07 | F3 | 115android | 115_安卓端 |
+-------+----------+------------+----------------------+
| 08 | H1 | ipad | 115生活_苹果平板端 |
+-------+----------+------------+----------------------+
| 09 | H2 | bipad | 未知: ipad |
+-------+----------+------------+----------------------+
| 10 | H3 | 115ipad | 115_苹果平板端 |
+-------+----------+------------+----------------------+
| 11 | I1 | tv | 115生活_安卓电视端 |
+-------+----------+------------+----------------------+
| 12 | I2 | apple_tv | 115生活_苹果电视端 |
+-------+----------+------------+----------------------+
| 13 | M1 | qandriod | 115管理_安卓端 |
+-------+----------+------------+----------------------+
| 14 | N1 | qios | 115管理_苹果端 |
+-------+----------+------------+----------------------+
| 15 | O1 | qipad | 115管理_苹果平板端 |
+-------+----------+------------+----------------------+
| 16 | P1 | os_windows | 115生活_Windows端 |
+-------+----------+------------+----------------------+
| 17 | P2 | os_mac | 115生活_macOS端 |
+-------+----------+------------+----------------------+
| 18 | P3 | os_linux | 115生活_Linux端 |
+-------+----------+------------+----------------------+
| 19 | R1 | wechatmini | 115生活_微信小程序端 |
+-------+----------+------------+----------------------+
| 20 | R2 | alipaymini | 115生活_支付宝小程序 |
+-------+----------+------------+----------------------+
| 21 | S1 | harmony | 115_鸿蒙端 |
+-------+----------+------------+----------------------+
"""
api = complete_url(f"/app/1.0/{app}/1.0/logout/mange", base_url=base_url)
if payload is None:
payload = {"ssoent": self.login_ssoent or ""}
elif isinstance(payload, str):
payload = {"ssoent": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Message API ##########
@overload
def msg_contacts_ls(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://pmsg.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def msg_contacts_ls(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://pmsg.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def msg_contacts_notice(
self,
/,
base_url: str | Callable[[], str] = "https://msg.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def msg_contacts_notice(
self,
/,
base_url: str | Callable[[], str] = "https://msg.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def msg_get_websocket_host(
self,
/,
base_url: str | Callable[[], str] = "https://msg.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def msg_get_websocket_host(
self,
/,
base_url: str | Callable[[], str] = "https://msg.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def msg_get_websocket_host(
self,
/,
base_url: str | Callable[[], str] = "https://msg.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 websocket 链接
GET https://msg.115.com/?ct=im&ac=get_websocket_host
.. note::
用返回数据构造链接,可由此监听 websocket 消息,但目前来看,最多也就实时获得一个【删除】事件
`wss://{server}/?uid={user_id}&session={session_id}&client_version=100&client_type=5&sequence_id=0&source=web&device_id=0000000000000000000000000000000000000000`
"""
api = complete_url(base_url=base_url, query={"ct": "im", "ac": "get_websocket_host"})
return self.request(url=api, async_=async_, **request_kwargs)
########## Multimedia API ##########
@overload
def multimedia_collection_listen(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_collection_listen(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_collection_listen_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_collection_listen_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_collection_watch(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_collection_watch(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_collection_watch_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_collection_watch_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_cover_auto(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_cover_auto(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_cover_check(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_cover_check(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_listen(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_listen(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_listen_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_listen_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_watch(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_watch(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_watch_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_watch_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_recent_listen(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_recent_listen(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_recent_listen_update(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_recent_listen_update(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_recent_watch(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_recent_watch(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_recent_watch_update(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_recent_watch_update(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_relate_file(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_relate_file(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_relate_file_update(
self,
payload: int | str | Iterable[int | str] | dict,
/,
channel_id: int | str = 1,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_relate_file_update(
self,
payload: int | str | Iterable[int | str] | dict,
/,
channel_id: int | str = 1,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_related(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_related(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_related_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_related_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_relation(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_relation(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_type(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_type(
self,
payload: int | dict = 1,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def multimedia_type_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def multimedia_type_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
########## Note API ##########
@overload
def note_bookmark_list(
self,
payload: int | str | dict = "",
/,
base_url: str | Callable[[], str] = "https://bookmark.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_bookmark_list(
self,
payload: int | str | dict = "",
/,
base_url: str | Callable[[], str] = "https://bookmark.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_bookmark_list(
self,
payload: int | str | dict = "",
/,
base_url: str | Callable[[], str] = "https://bookmark.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列书签(网址收藏夹)
GET https://bookmark.115.com/api/bookmark_list.php
.. note::
这个接口支持 GET 和 POST 请求方法
:payload:
- search_value: str = ""
- parent_id: int = 0
- limit: int = 1150
- offset: int = 0
"""
api = complete_url("/api/bookmark_list.php", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
elif isinstance(payload, str):
payload = {"search_value": payload}
payload.setdefault("limit", 1150)
if request_kwargs.get("method", "").upper() == "POST":
return self.request(url=api, data=payload, async_=async_, **request_kwargs)
else:
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_cate_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_cate_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_cate_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""添加记录分类
POST https://note.115.com/?ct=note&ac=addcate
:payload:
- cname: str 💡 最多允许 20 个字符
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "addcate"})
if isinstance(payload, str):
payload = {"cname": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_cate_del(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_cate_del(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_cate_del(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除记录分类
POST https://note.115.com/?ct=note&ac=delcate
:payload:
- cid: int 💡 分类 id
- action: str = <default>
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "delcate"})
if isinstance(payload, int):
payload = {"cid": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_cate_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_cate_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_cate_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""改名记录分类
POST https://note.115.com/?ct=note&ac=upcate
:payload:
- cid: int 💡 分类 id
- cname: str 💡 分类名,最多 20 个字符
"""
api = complete_url(base_url=base_url, query={"ct": "node", "ac": "upcate"})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_cate_list(
self,
payload: bool | dict = True,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_cate_list(
self,
payload: bool | dict = True,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_cate_list(
self,
payload: bool | dict = True,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取记录分类列表
GET https://note.115.com/?ct=note&ac=cate
:payload:
- has_picknews: 0 | 1 = 1 💡 是否显示 id 为负数的分类
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "cate"})
if isinstance(payload, bool):
payload = {"has_picknews": int(payload)}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_cate_list2(
self,
payload: bool | dict = True,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_cate_list2(
self,
payload: bool | dict = True,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_cate_list2(
self,
payload: bool | dict = True,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取记录分类列表
GET https://note.115.com/api/2.0/api.php?ac=get_category
:payload:
- has_picknews: 0 | 1 = 1 💡 是否显示 id 为负数的分类
- is_all: 0 | 1 = <default> 💡 是否显示全部
- has_msg: 0 | 1 = <default>
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "get_category"})
if isinstance(payload, bool):
payload = {"has_picknews": int(payload)}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_del(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_del(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_del(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除记录
POST https://note.115.com/?ct=note&ac=delete
:payload:
- nid: int | str 💡 记录 id,多个用逗号 "," 隔开
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "delete"})
if isinstance(payload, (int, str)):
payload = {"nid": payload}
elif not isinstance(payload, dict):
payload = {"nid": ",".join(map(str, payload))}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_del2(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_del2(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_del2(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除记录
POST https://note.115.com/api/2.0/api.php?ac=note_delete
:payload:
- nid: int | str 💡 记录 id,多个用逗号 "," 隔开
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "note_delete"})
if isinstance(payload, (int, str)):
payload = {"nid": payload}
elif not isinstance(payload, dict):
payload = {"nid": ",".join(map(str, payload))}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_detail(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_detail(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_detail(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取备忘(记录/笔记/记事)数据
GET https://note.115.com/?ct=note&ac=detail
:payload:
- nid: int 💡 记录 id
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "detail"})
if isinstance(payload, int):
payload = {"nid": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_detail2(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_detail2(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_detail2(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取备忘(记录/笔记/记事)数据
GET https://note.115.com/api/2.0/api.php?ac=note_detail
:payload:
- nid: int 💡 记录 id
- has_picknews: 0 | 1 = <default>
- is_html: 0 | 1 = <default>
- copy: 0 | 1 = <default>
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "note_detail"})
if isinstance(payload, int):
payload = {"nid": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_edit_attaches(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_edit_attaches(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_edit_attaches(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""给记录修改附件
POST https://note.115.com/?ct=note&ac=edit_attaches
.. attention::
每个附件的大小必须控制在 200 MB 以内,这也是网页版所允许的单次下载的最大文件
:payload:
- nid: int 💡 记录 id
- pickcodes: str 💡 附件的提取码,多个用逗号 "," 隔开
- op: "add" | "del" | "save" = "add" 💡 操作类型:"add":添加 "del":去除 "save":置换
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "edit_attaches"})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_fav_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_fav_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_fav_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取星标备忘(记录/笔记/记事)列表
GET https://note.115.com/?ct=note&ac=get_fav_note_list
:payload:
- start: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 最多返回数量
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "get_fav_note_list"})
if isinstance(payload, int):
payload = {"start": payload}
payload = {"limit": 1150, "start": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_fav_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_fav_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_fav_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""给记录添加或去除星标
POST https://note.115.com/?ct=note&ac=fav
:payload:
- note_id: int 💡 记录 id
- op: "add" | "del" = "add" 💡 操作类型:"add":添加 "del":去除
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "fav"})
if isinstance(payload, int):
payload = {"note_id": payload}
payload.setdefault("op", "add")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_get_pic_url(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_get_pic_url(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_get_pic_url(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量获取图片的预览图链接
POST https://note.115.com?ct=note&ac=get_pic_url
.. hint::
这个接口获取的链接似乎长久有效,而且支持任何文件(只要有人上传过),但限制文件大小在 50 MB 以内
:payload:
- rs: str 💡 图片的 sha1 (必须大写)或者 f"{oss_bucket}_{oss_object}"(由 `upload_file_image` 接口的响应获得),后者跳转次数更少、响应更快
- rs[]: str
- ...
- rs[0]: str
- rs[1]: str
- ...
- module: int = <default>
- file_names[]: str = <default>
- ...
- type[]: int = <default>
- ...
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "get_pic_url"})
if isinstance(payload, str):
payload = {"rs": payload}
elif isinstance(payload, tuple):
payload = [("rs[]", s) for s in payload]
return self.request(
url=api,
method="POST",
data=payload,
async_=async_,
**request_kwargs,
)
@overload
def note_get_pic_url2(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_get_pic_url2(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_get_pic_url2(
self,
payload: str | tuple[str, ...] | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量获取图片的预览图链接
POST https://note.115.com/api/2.0/api.php?ac=get_pic_url
.. hint::
这个接口获取的链接似乎长久有效,而且支持任何文件(只要有人上传过),但限制文件大小在 50 MB 以内
:payload:
- rs: str 💡 图片的 sha1 (必须大写)或者 f"{oss_bucket}_{oss_object}"(由 `upload_file_image` 接口的响应获得),后者跳转次数更少、响应更快
- rs[]: str
- ...
- rs[0]: str
- rs[1]: str
- ...
- module: int = <default>
- file_names[]: str = <default>
- ...
- type[]: int = <default>
- ...
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "get_pic_url"})
if isinstance(payload, str):
payload = {"rs": payload}
elif isinstance(payload, tuple):
payload = [("rs[]", s) for s in payload]
return self.request(
url=api,
method="POST",
data=payload,
async_=async_,
**request_kwargs,
)
@overload
def note_is_fav(
self,
payload: int | str | Iterable[int | str] |dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_is_fav(
self,
payload: int | str | Iterable[int | str] |dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_is_fav(
self,
payload: int | str | Iterable[int | str] |dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""检查记录是否被星标
.. note::
这个接口支持 GET 和 POST 请求方法
GET https://note.115.com/api/2.0/api.php?ac=is_fav
:payload:
- note_id: int | str 💡 多个用逗号 "," 隔开
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "is_fav"})
if isinstance(payload, (int, str)):
payload = {"note_id": payload}
elif not isinstance(payload, dict):
payload = {"note_id": ",".join(map(str, payload))}
if request_kwargs.get("method", "").upper() == "POST":
return self.request(url=api, data=payload, async_=async_, **request_kwargs)
else:
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取备忘(记录/笔记/记事)列表
GET https://note.115.com/?ct=note
:payload:
- ac: "" | "all" = "all" 💡 如果为 "all",则显示完整信息,如果为 "",则显示简要信息(只有标题,没有内容文本)
- start: int = 0 💡 开始索引,从 0 开始
- page_size: int = 1150 💡 分页大小,相当于 `limit`
- cid: int = 0 💡 分类 id:0:全部 -10:云收藏 -15:消息备忘
- has_picknews: 0 | 1 = 1 💡 是否显示 id 为负数的分类
- keyword: str = <default>
- recently: 0 | 1 = <default> 💡 是否为最近
"""
api = complete_url(base_url=base_url, query={"ct": "note"})
if isinstance(payload, int):
payload = {"start": payload}
payload = {"ac": "all", "cid": 0, "has_picknews": 1, "page_size": 1150, "start": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_list2(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_list2(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_list2(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取备忘(记录/笔记/记事)列表
GET https://note.115.com/api/2.0/api.php?ac=note_list
:payload:
- start: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 分页大小
- cid: int = 0 💡 分类 id:0:全部 -10:云收藏 -15:消息备忘
- only_public: 0 | 1 = <default>
- msg_note: 0 | 1 = <default>
- has_picknews: 0 | 1 = <default>
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "note_list"})
if isinstance(payload, int):
payload = {"start": payload}
payload = {"cid": 0, "has_picknews": 1, "page_size": 1150, "start": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_save(
self,
payload: str | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_save(
self,
payload: str | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_save(
self,
payload: str | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""保存备忘(记录/笔记/记事)
POST https://note.115.com/?ct=note&ac=save
:payload:
- nid: int = <default> 💡 记录 id,如果提供就是更新,否则就是新建
- content: str = <default> 💡 记录的文本,最多 50000 个字符
- title: str = <default> 💡 标题,最多 927 个字节,可以为空
- cid: int = 0 💡 分类 id
- is_html: 0 | 1 = 0 💡 是否 HTML,如果为 1,则会自动加上标签(例如 <p>),以使内容成为合法的 HTML
- pickcodes: str = <default> 💡 附件的提取码,多个用逗号 "," 隔开
- tags: str = <default> 💡 标签文本
- tags[]: str = <default> 💡 标签文本(多个用 "[]" 后缀)
- ...
- tags[0]: str = <default> 💡 标签文本(多个用 "[0]","[1]",... 后缀)
- tags[1]: str = <default> 💡 标签文本
- ...
- toc_ids: int | str = <default>
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "save"})
if isinstance(payload, str):
payload = {"content": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_save2(
self,
payload: str | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_save2(
self,
payload: str | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_save2(
self,
payload: str | dict | list,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""编辑备忘(记录/笔记/记事)
POST https://note.115.com/api/2.0/api.php?ac=note_edit
:payload:
- nid: int = <default> 💡 记录 id,如果提供就是更新,否则就是新建
- content: str = <default> 💡 记录的文本,最多 50000 个字符
- title: str = <default> 💡 标题,最多 927 个字节,可以为空
- cid: int = <default> 💡 分类 id
- is_html: 0 | 1 = <default> 💡 是否 HTML,如果为 1,则会自动加上标签(例如 <p>),以使内容成为合法的 HTML
- pickcodes: str = <default> 💡 附件的提取码,多个用逗号 "," 隔开
- tags: str = <default> 💡 标签文本
- tags[]: str = <default> 💡 标签文本(多个用 "[]" 后缀)
- ...
- tags[0]: str = <default> 💡 标签文本(多个用 "[0]","[1]",... 后缀)
- tags[1]: str = <default> 💡 标签文本
- ...
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "note_edit"})
if isinstance(payload, str):
payload = {"content": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_search(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_search(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_search(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""搜索备忘(记录/笔记/记事)
.. note::
这个接口支持 GET 和 POST 请求方法
GET https://note.115.com/api/2.0/api.php?ac=search
:payload:
- q: str 💡 搜索词
- start: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 最多返回数量
- count: int = <default>
- cid: int = 0 💡 分类 id
- has_picknews: 0 | 1 = 1 💡 是否显示 id 为负数的分类
- create_time1: str = <default>
- create_time2: str = <default>
- start_time: str = <default> 💡 开始日期,格式为 YYYY-MM-DD
- end_time: str = <default> 💡 结束日期(含),格式为 YYYY-MM-DD
- tag_arr: str = <default> 💡 标签文本
- tag_arr[]: str = <default> 💡 标签文本(多个用 "[]" 后缀)
- ...
- tag_arr[0]: str = <default> 💡 标签文本(多个用 "[0]","[1]",... 后缀)
- tag_arr[1]: str = <default> 💡 标签文本
- ...
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "search"})
if isinstance(payload, str):
payload = {"q": payload}
payload = {"has_picknews": 1, "limit": 1150, "start": 0, **payload}
if request_kwargs.get("method", "").upper() == "POST":
return self.request(url=api, data=payload, async_=async_, **request_kwargs)
else:
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_set_cate(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_set_cate(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_set_cate(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改记录的分类
POST https://note.115.com/?ct=note&ac=update_note_cate
:payload:
- cid: int 💡 分类 id
- nid: int | str 💡 记录 id,多个用逗号 "," 隔开
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "update_note_cate"})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_set_cate2(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_set_cate2(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_set_cate2(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改记录的分类
POST https://note.115.com/api/2.0/api.php?ac=set_note_cate
:payload:
- cid: int 💡 分类 id
- nid: int | str 💡 记录 id,多个用逗号 "," 隔开
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "set_note_cate"})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_set_tag(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_set_tag(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_set_tag(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改记录的标签
POST https://note.115.com/api/2.0/api.php?ac=set_tag
:payload:
- nid: int 💡 记录 id
- tags: str 💡 标签文本
- tags[]: str 💡 标签文本(多个用 "[]" 后缀)
- ...
- tags[0]: str 💡 标签文本(多个用 "[0]","[1]",... 后缀)
- tags[1]: str 💡 标签文本
- ...
- has_picknews: 0 | 1 = <default>
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "set_tag"})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_tag_color(
self,
payload: str | Iterable[str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_tag_color(
self,
payload: str | Iterable[str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_tag_color(
self,
payload: str | Iterable[str] | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""上传标签,返回标签并提供颜色
POST https://note.115.com/api/2.0/api.php?ac=get_tag_color
:payload:
- tags: str = <default> 💡 标签文本
- tags[]: str = <default> 💡 标签文本(多个用 "[]" 后缀)
- ...
- tags[0]: str = <default> 💡 标签文本(多个用 "[0]","[1]",... 后缀)
- tags[1]: str = <default> 💡 标签文本
- ...
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "get_tag_color"})
if isinstance(payload, str):
payload = {"tags": payload}
elif payload and not isinstance(payload, dict) and not (isinstance(payload, Sequence) and not isinstance(payload[0], str)):
payload = {f"tags[{i}]": t for i, t in enumerate(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def note_tag_latest(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_tag_latest(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_tag_latest(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取最近使用的标签
.. note::
这个接口支持 GET 和 POST 请求方法
GET https://note.115.com/api/2.0/api.php?ac=get_latest_tags
:payload:
- q: str = "" 💡 搜索词
- is_return_color: 0 | 1 = 1 💡 是否返回颜色
- limit: int = 1150 💡 最多返回数量
"""
api = complete_url("/api/2.0/api.php", base_url=base_url, query={"ac": "get_latest_tags"})
if isinstance(payload, str):
payload = {"q": payload}
payload = {"is_return_color": 1, "limit": 1150, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def note_user_setting(
self,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_user_setting(
self,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_user_setting(
self,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取记录的列表展示的配置(目前只有【每页数量设置】)
GET https://note.115.com/?ct=note&ac=get_user_setting
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "get_user_setting"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def note_user_setting_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def note_user_setting_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def note_user_setting_set(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://note.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改记录的列表展示的配置
POST https://note.115.com/?ct=note&ac=set_user_setting
:payload:
- note_page_size: 20 | 25 | 50 | 100 💡 每页数量设置
"""
api = complete_url(base_url=base_url, query={"ct": "note", "ac": "set_user_setting"})
if isinstance(payload, int):
payload = {"note_page_size": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Photo API ##########
@overload
def photo_album(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_album(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_album(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取相册信息
GET https://webapi.115.com/photo/album
:payload:
- album_id: int | str 💡 相册 id,如果为 -1,则是【默认加密相册】
"""
api = complete_url("/photo/album", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"album_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_album_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_album_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_album_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建相册
POST https://webapi.115.com/photo/albumadd
:payload:
- album_name: str = <default> 💡 相册名称
- album_desc: str = <default> 💡 相册描述
- is_secret: 0 | 1 = <default> 💡 是否加密
"""
api = complete_url("/photo/albumadd", base_url=base_url)
if isinstance(payload, str):
payload = {"album_name": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_album_group(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_album_group(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_album_group(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取相册分组
GET https://webapi.115.com/photo/albumgroup
:payload:
- home_page: 0 | 1 = 1
- limit: int = 100
"""
api = complete_url("/photo/albumgroup", base_url=base_url)
payload = {"home_page": 1, "limit": 100, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_album_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_album_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_album_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取相册列表
GET https://webapi.115.com/photo/albumlist
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 9999 💡 最多返回数量
- album_type: int = 1💡 相册类型。已知:
- 1: 个人相册
- 5: 应用相册
- 6: 加密相册
"""
api = complete_url("/photo/albumlist", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"album_type": 1, "limit": 9999, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_album_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_album_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_album_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""更新相册
POST https://webapi.115.com/photo/album
:payload:
- album_id: int | str 💡 相册 id,如果为 -1,则是【默认加密相册】
- album_name: str = <default> 💡 相册名称
- album_desc: str = <default> 💡 相册描述
- album_state: 0 | 1 = <default> 💡 是否删除:0:保留 1:删除
- is_secret: 0 | 1 = <default> 💡 是否加密
"""
api = complete_url("/photo/album", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_bind(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_bind(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_bind(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""把图片或视频转存到相册
POST https://webapi.115.com/photo/photo
.. note::
虽然被认为是图片的格式很多(你可以用这个方法 `client.fs_files_second_type({"type": 2})` 获得网盘中的所有图片格式),但仅支持以下格式图片添加到相册:jpg,jpeg,png,gif,svg,webp,heic,bmp,dng
.. caution::
添加到相册,其实就是复制到 "/手机相册" 目录,如果需要删除,就直接用 ``fs_delete`` 即可
:payload:
- file_ids: int | str 💡 文件 id,多个用逗号 "," 隔开
- to_album_id: int | str = 1 💡 相册 id
- -1: 添加到【默认加密相册】
- 1: 添加到照片
- action: str = "addtoalbum" 💡 动作
- "addtoalbum": 添加到相册
"""
api = complete_url("/photo/photo", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
elif not isinstance(payload, dict):
payload = {"file_ids": ",".join(map(str, payload))}
payload = {"action": "addtoalbum", "to_album_id": 1, **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取相册的文件列表
GET https://webapi.115.com/photo/photolist
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 最多返回数量
- album_id: int | str = <default> 💡 相册 id。如果为 -1,则是【默认加密相册】;如果不指定,则是所有相册
- key_word: str = <default>
- type: int = <default>
- tr: str = <default> 💡 时间线,是一个日期,格式为 YYYYMMDD
- order: str = <default> 💡 排序依据,例如 "add_time"
- is_asc: 0 | 1 = <default> 💡 是否升序排列
"""
api = complete_url("/photo/photolist", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload.setdefault("limit", 1150)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享相册信息
GET https://webapi.115.com/photo/sharealbum
:payload:
- album_id: int | str 💡 相册 id
"""
api = complete_url("/photo/sharealbum", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"album_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum_add(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""新建共享相册
POST https://webapi.115.com/photo/sharealbumadd
:payload:
- album_name: str = <default> 💡 相册名称
- album_desc: str = <default> 💡 相册描述
"""
api = complete_url("/photo/sharealbumadd", base_url=base_url)
if isinstance(payload, str):
payload = {"album_name": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享相册列表
GET https://webapi.115.com/photo/sharealbumlist
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 最多返回数量
- is_asc: 0 | 1 = <default> 💡 是否升序排列
- order: str = <default> 💡 排序依据,例如 "update_time"
"""
api = complete_url("/photo/sharealbumlist", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum_member(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum_member(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum_member(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享相册的成员用户列表
GET https://webapi.115.com/photo/sharealbummember
:payload:
- album_id: int | str = <default> 💡 相册 id
- order: str = <default> 💡 排序依据,例如 "join_time"
- is_asc: 0 | 1 = <default> 💡 是否升序排列
"""
api = complete_url("/photo/sharealbummember", base_url=base_url)
if isinstance(payload, int):
payload = {"album_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum_record_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum_record_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum_record_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享相册的操作记录列表
GET https://webapi.115.com/photo/sharealbumrecordlist
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 最多返回数量
- album_id: int | str = <default> 💡 相册 id
"""
api = complete_url("/photo/sharealbumrecordlist", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum_record_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum_record_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum_record_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""在共享相册中增加或删除 1 条记录
POST https://webapi.115.com/photo/sharealbumrecord
:payload:
- album_id: int | str 💡 相册 id
- action: "add" | "del" 💡 操作。"add":添加记录 "del":删除记录
- record_id: int | str = <default> 💡 记录 id
- record_content: str = <default> 💡 记录的描述文本
- file_ids: int | str = <default> 💡 记录关联的(在网盘中的)文件 id,多个用逗号 "," 隔开
"""
api = complete_url("/photo/sharealbumrecord", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_sharealbum_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_sharealbum_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_sharealbum_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""更新共享相册
POST https://webapi.115.com/photo/sharealbum
:payload:
- album_id: int | str 💡 相册 id
- album_name: str = <default> 💡 相册名称
- album_desc: str = <default> 💡 相册描述
- album_cover: str = <default> 💡 相册封面,图片的 sha1 值
- album_state: 0 | 1 = <default> 💡 是否删除:0:保留 1:删除
- is_top: 0 | 1 = <default> 💡 是否置顶
- user_nick_name: str = <default> 💡 用户昵称
"""
api = complete_url("/photo/sharealbum", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_share_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_share_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_share_list(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享相册的图片列表
GET https://webapi.115.com/photo/sharephotolist
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 1150 💡 最多返回数量
- album_id: int | str = <default> 💡 相册 id
- record_id: int | str = <default> 💡 操作记录 id
- key_word: str = <default>
- type: int = <default>
- tr: str = <default> 💡 时间线,是一个日期,格式为 YYYYMMDD
- order: str = <default> 💡 排序依据,例如 "add_time"
- is_asc: 0 | 1 = <default> 💡 是否升序排列
"""
api = complete_url("/photo/sharephotolist", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload.setdefault("limit", 1150)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_share_remove(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_share_remove(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_share_remove(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""把共享相册的图片移除
POST https://webapi.115.com/photo/sharephoto
:payload:
- album_id: int | str 💡 相册 id
- photo_ids: int | str = <default> 💡 (在相册中的)图片 id,多个用逗号 "," 隔开
"""
api = complete_url("/photo/sharephoto", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_share_save(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_share_save(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_share_save(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""把共享相册的图片保存到照片库
POST https://webapi.115.com/photo/sharephotosave
:payload:
- album_id: int | str 💡 相册 id
- photo_ids: int | str = <default> 💡 (在相册中的)图片 id,多个用逗号 "," 隔开
"""
api = complete_url("/photo/sharephotosave", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def photo_share_timeline(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_share_timeline(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_share_timeline(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享相册的时间线列表,然后你可以通过 `client.photo_share_list` 获取对应时间线的图片列表
GET https://webapi.115.com/photo/sharephototimeline
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 99999 💡 最多返回数量
- album_id: int | str = <default> 💡 相册 id。如果为 -1,则是【默认加密相册】;如果不指定,则是所有相册
- key_word: str = <default>
"""
api = complete_url("/photo/sharephototimeline", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload.setdefault("limit", 99999)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def photo_timeline(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def photo_timeline(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def photo_timeline(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取时间线列表,然后你可以通过 `client.photo_list` 获取对应时间线的图片列表
GET https://webapi.115.com/photo/phototimeline
:payload:
- offset: int = 0 💡 开始索引,从 0 开始
- limit: int = 99999 💡 最多返回数量
- album_id: int | str = <default> 💡 相册 id。如果为 -1,则是【默认加密相册】;如果不指定,则是所有相册
- key_word: str = <default>
"""
api = complete_url("/photo/phototimeline", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload.setdefault("limit", 99999)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
########## Recyclebin API ##########
@overload
def recyclebin_clean(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_clean(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_clean(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:删除或清空
POST https://webapi.115.com/rb/secret_del
.. note::
只要不指定 `tid`,就会清空回收站,如果有目录正在删除中则会被阻止
.. note::
可以在设置中的【账号安全/安全密钥】页面下,关闭【文件(隐藏模式/清空删除回收站)】的按钮,就不需要传安全密钥了
:payload:
- tid: int | str = "" 💡 不传就是清空,多个用逗号 "," 隔开
- password: int | str = "000000" 💡 安全密钥,是 6 位数字
"""
api = complete_url("/rb/secret_del", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"tid": payload}
elif not isinstance(payload, dict):
payload = {"tid": ",".join(map(str, payload))}
payload.setdefault("password", password)
payload["password"] = format(payload.get("password") or "", ">06")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_clean2(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_clean2(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_clean2(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:删除或清空
POST https://webapi.115.com/rb/clean
.. note::
如果没有指定任一 `rid`,就是清空回收站,如果有目录正在删除中则会被阻止
.. tip::
这个接口必须提供安全密钥。如果不提供,则默认使用 "000000",在不必要的情况下,完全可以把安全密钥设为这个值
:payload:
- rid[0]: int | str 💡 不传就是清空
- rid[1]: int | str
- ...
- password: int | str = "000000" 💡 安全密钥
"""
api = complete_url("/rb/clean", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"rid[0]": payload}
elif not isinstance(payload, dict):
payload = {f"rid[{i}]": rid for i, rid in enumerate(payload)}
payload.setdefault("password", password)
payload["password"] = format(payload.get("password") or "", ">06")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_clean_app(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_clean_app(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_clean_app(
self,
payload: int | str | Iterable[int | str] | dict = "",
/,
password: str = "000000",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:删除或清空
POST https://proapi.115.com/{app}/rb/secret_del
.. note::
只要不指定 `tid`,就是清空回收站,如果有目录正在删除中则会被阻止
.. note::
可以在设置中的【账号安全/安全密钥】页面下,关闭【文件(隐藏模式/清空删除回收站)】的按钮,就不需要传安全密钥了
:payload:
- tid: int | str = "" 💡 不传就是清空,多个用逗号 "," 隔开
- password: int | str = "000000" 💡 安全密钥,是 6 位数字
- user_id: int | str = <default> 💡 用户 id
"""
api = complete_url("/rb/secret_del", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"tid": payload}
elif not isinstance(payload, dict):
payload = {"tid": ",".join(map(str, payload))}
payload.setdefault("user_id", self.user_id)
payload.setdefault("password", password)
payload["password"] = format(payload.get("password") or "", ">06")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:文件信息
POST https://webapi.115.com/rb/rb_info
:payload:
- rid: int | str
"""
api = complete_url("/rb/rb_info", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"rid": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:列表
GET https://webapi.115.com/rb
:payload:
- aid: int = 7 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- cid: int | str = 0
- limit: int = 32
- offset: int = 0
- o: str = <default> 💡 排序依据:dtime:删除时间
- asc: 0 | 1 = <default>
- source: str = <default>
"""
api = complete_url("/rb", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"aid": 7, "cid": 0, "limit": 32, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:列表
GET https://proapi.115.com/{app}/rb
:payload:
- aid: int = 7 💡 area_id
- 0: 会被视为 1
- 1: 正常文件
- 2: <unknown>
- 3: <unknown>
- 4: <unknown>
- 5: <unknown>
- 7: 回收站文件
- 9: <unknown>
- 12: 瞬间文件
- 15: <unknown>
- 120: 彻底删除文件、简历附件
- <其它>: 会被视为 0
- cid: int | str = 0
- limit: int = 32
- offset: int = 0
- source: str = <default>
"""
api = complete_url("/rb", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"aid": 7, "cid": 0, "limit": 32, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_revert(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_revert(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_revert(
self,
payload: int | str | Iterable[int | str] | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:还原
POST https://webapi.115.com/rb/revert
:payload:
- rid[0]: int | str
- rid[1]: int | str
- ...
"""
api = complete_url("/rb/revert", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"rid[0]": payload}
elif not isinstance(payload, dict):
payload = {f"rid[{i}]": rid for i, rid in enumerate(payload)}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def recyclebin_revert_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def recyclebin_revert_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def recyclebin_revert_app(
self,
payload: int | str | Iterable[int | str] | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""回收站:还原
POST https://proapi.115.com/{app}/rb/revert
.. caution::
⚠️ 请不要并发执行,限制在 5 万个文件和目录以内
:payload:
- tid: int | str 💡 多个用逗号 "," 隔开
- user_id: int | str = <default> 💡 用户 id
"""
api = complete_url("/rb/revert", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"tid": payload}
elif not isinstance(payload, dict):
payload = {"tid": ",".join(map(str, payload))}
payload.setdefault("user_id", self.user_id)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Share API ##########
@overload
def share_access_user_list(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_access_user_list(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_access_user_list(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""访问账号列表
GET https://webapi.115.com/share/access_user_list
:payload:
- share_code: str
"""
api = complete_url("/share/access_user_list", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_activate(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_activate(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_activate(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""激活分享
POST https://webapi.115.com/share/activeshare
:payload:
- share_code: str
"""
api = complete_url("/share/activeshare", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_batch_renewal_long_skip(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_batch_renewal_long_skip(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_batch_renewal_long_skip(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""将免登录下载链接设为长期
POST https://webapi.115.com/share/batch_renewal_long_skip
.. attention::
链接必须开启免登录下载,并且需年费及以上 VIP 会员
:payload:
- share_code: str
"""
api = complete_url("/share/batch_renewal_long_skip", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_batch_skip_login_down(
self,
payload: str | Iterable[str] | dict,
/,
skip_login: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_batch_skip_login_down(
self,
payload: str | Iterable[str] | dict,
/,
skip_login: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_batch_skip_login_down(
self,
payload: str | Iterable[str] | dict,
/,
skip_login: bool = True,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""批量开启或关闭免登录下载
POST https://webapi.115.com/share/batch_skip_login_down
.. attention::
链接必须开启免登录下载,并且需年费及以上 VIP 会员
:payload:
- share_code: str 💡 分享码,多个用逗号 "," 隔开
- skip_login: 0 | 1 = 1 💡 是否允许免登录下载
"""
api = complete_url("/share/batch_skip_login_down", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
elif not isinstance(payload, dict):
payload = {"share_code": ",".join(payload)}
payload = cast(dict[str, Any], payload)
payload.setdefault("skip_login", int(skip_login))
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_downlist(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_downlist(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_downlist(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享链接的某个目录中可下载的文件的列表(只含文件,不含目录,任意深度,简略信息)
GET https://webapi.115.com/share/downlist
.. attention::
cid 不能为 0 或 空
:payload:
- share_code: str
- receive_code: str
- cid: int | str
"""
api = complete_url("/share/downlist", base_url=base_url)
if not isinstance(payload, dict):
payload = {"cid": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_downlist_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_downlist_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_downlist_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享链接的某个目录中可下载的文件的列表(只含文件,不含目录,任意深度,简略信息)
GET https://proapi.115.com/app/share/downlist
.. attention::
cid 不能为 0 或 空
:payload:
- share_code: str
- receive_code: str
- cid: int | str
"""
if app in ("", "chrome"):
api = complete_url("/app/share/downlist", base_url)
else:
if app not in ("windows", "mac", "linux", "os_windows", "os_mac", "os_linux"):
app = "os_windows"
api = complete_url("/2.0/share/downlist", base_url=base_url, app=app)
if not isinstance(payload, dict):
payload = {"cid": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_download_url(
self,
payload: int | str | dict,
/,
share_url: str = "",
strict: bool = True,
app: str = "android",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115URL:
...
@overload
def share_download_url(
self,
payload: int | str | dict,
/,
share_url: str = "",
strict: bool = True,
app: str = "android",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115URL]:
...
[docs]
def share_download_url(
self,
payload: int | str | dict,
/,
share_url: str = "",
strict: bool = True,
app: str = "android",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115URL | Coroutine[Any, Any, P115URL]:
"""获取分享链接中某个文件的下载链接
:param payload: 请求参数,如果为 int 或 str,则视为 `file_id`
- file_id: int | str 💡 文件 id
- receive_code: str 💡 接收码(也就是密码)
- share_code: str 💡 分享码
:param url: 分享链接,如果提供的话,会被拆解并合并到 `payload` 中,优先级较高
:param strict: 如果为 True,当目标是目录时,会抛出 IsADirectoryError 异常
:param app: 使用此设备的接口
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 下载链接
"""
def gen_step():
if app in ("web", "desktop"):
resp = yield self.share_download_url_web(
payload, share_url=share_url, async_=async_, **request_kwargs)
else:
resp = yield self.share_download_url_app(
payload, share_url=share_url, app=app, async_=async_, **request_kwargs)
resp["payload"] = payload
resp["is_download"] = True
check_response(resp)
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
url = data["url"]
if strict and not url:
throw(errno.EISDIR, resp)
return P115URL(
url["url"] if url else "",
id=int(data["fid"]),
name=data["fn"],
size=int(data["fs"]),
sha1=data.get("sha1", ""),
is_dir=not url,
)
return run_gen_step(gen_step, async_)
@overload
def share_download_url_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_download_url_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_download_url_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享链接中某个文件的下载链接
POST https://proapi.115.com/app/share/downurl
:payload:
- file_id: int | str
- receive_code: str
- share_code: str
- dl: 0 | 1 = <default>
"""
if not isinstance(payload, dict):
payload = {"file_id": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
if app in ("", "chrome"):
api = complete_url("/app/share/downurl", base_url)
def parse(_, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if json["state"] and (data := json.get("data")):
json["data"] = json_loads(rsa_decrypt(data))
return json
request_kwargs.setdefault("parse", parse)
payload = {"data": rsa_encrypt(dumps(payload)).decode()}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
else:
if app not in ("windows", "mac", "linux", "os_windows", "os_mac", "os_linux"):
app = "os_windows"
api = complete_url("/2.0/share/downurl", base_url=base_url, app=app)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_download_url_web(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_download_url_web(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_download_url_web(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享链接中某个文件的下载链接(网页版接口)
GET https://webapi.115.com/share/downurl
.. note::
最大允许下载 200 MB 的文件
:payload:
- file_id: int | str
- receive_code: str
- share_code: str
- dl: int = <default>
"""
api = complete_url("/share/downurl", base_url=base_url)
if not isinstance(payload, dict):
payload = {"file_id": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_info(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取(自己的)分享信息
GET https://webapi.115.com/share/shareinfo
:payload:
- share_code: str
"""
api = complete_url("/share/shareinfo", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_info_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取(自己的)分享信息
GET https://proapi.115.com/{app}/2.0/share/shareinfo
:payload:
- share_code: str
"""
api = complete_url("/2.0/share/shareinfo", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"share_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列(自己的)分享信息列表
GET https://webapi.115.com/share/slist
.. todo::
暂时不清楚 order 有哪些取值
:payload:
- limit: int = 32
- offset: int = 0
- order: str = <default> 💡 排序依据,例如 "create_time"
- asc: 0 | 1 = <default> 💡 是否升序排列
- show_cancel_share: 0 | 1 = 0
- share_state: int = <default>
"""
api = complete_url("/share/slist", base_url=base_url)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"limit": 32, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_list_app(
self,
payload: int | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""罗列(自己的)分享信息列表
GET https://proapi.115.com/{app}/2.0/share/slist
:payload:
- limit: int = 32
- offset: int = 0
"""
api = complete_url("/2.0/share/slist", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"offset": payload}
payload = {"limit": 32, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_notlogin_dl_quota(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_notlogin_dl_quota(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_notlogin_dl_quota(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""免登录下载流量配额
GET https://webapi.115.com/user/notlogin_dl_quota
"""
api = complete_url("/user/notlogin_dl_quota", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def share_notlogin_dl_quota_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_notlogin_dl_quota_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_notlogin_dl_quota_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""免登录下载流量配额
GET https://proapi.115.com/{app}/2.0/user/notlogin_dl_quota
"""
api = complete_url("/2.0/user/notlogin_dl_quota", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def share_receive(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_receive(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_receive(
self,
payload: int | str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""接收分享链接的某些文件或目录
POST https://webapi.115.com/share/receive
:payload:
- share_code: str
- receive_code: str
- file_id: int | str 💡 有多个时,用逗号 "," 分隔
- cid: int | str = <default> 💡 这是你网盘的目录 cid,如果不指定则用默认
- is_check: 0 | 1 = <default>
- target_folder: str = <default>
"""
api = complete_url("/share/receive", base_url=base_url)
if not isinstance(payload, dict):
payload = {"file_id": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_receive_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_receive_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_receive_app(
self,
payload: int | str | dict,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""接收分享链接的某些文件或目录
POST https://proapi.115.com/{app}/2.0/share/receive
:payload:
- share_code: str
- receive_code: str
- file_id: int | str 💡 有多个时,用逗号 "," 分隔
- cid: int | str = <default> 💡 这是你网盘的目录 cid,如果不指定则用默认
- is_check: 0 | 1 = <default>
"""
api = complete_url("/2.0/share/receive", base_url=base_url, app=app)
if not isinstance(payload, dict):
payload = {"file_id": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_recvcode(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_recvcode(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_recvcode(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""取消访问码
GET https://webapi.115.com/share/recvcode
:payload:
- share_code: str
- action: str = "cancel"
"""
api = complete_url("/share/recvcode", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
payload.setdefault("action", "cancel")
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_recvcode_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_recvcode_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_recvcode_app(
self,
payload: str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""取消访问码
GET https://proapi.115.com/{app}/2.0/share/recvcode
:payload:
- share_code: str
- action: str = "cancel"
"""
api = complete_url("/2.0/share/recvcode", base_url=base_url, app=app)
if isinstance(payload, str):
payload = {"share_code": payload}
payload.setdefault("action", "cancel")
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_reshare(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_reshare(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_reshare(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""续期分享
POST https://webapi.115.com/share/reshare
:payload:
- share_code: str
- ignore_warn: 0 | 1 = 1 💡 忽略信息提示,传 1 就行了
- renewal_skip_login: str = <default>
"""
api = complete_url("/share/reshare", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
payload.setdefault("ignore_warn", 1)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_search(
self,
payload: str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_search(
self,
payload: str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_search(
self,
payload: str | dict,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""从分享链接搜索文件或目录
GET https://webapi.115.com/share/search
.. attention::
最多只能取回前 10,000 条数据,也就是 `limit + offset <= 10_000`,不过可以一次性取完
:payload:
- share_code: str 💡 分享码
- receive_code: str 💡 接收码(即密码),如果是自己的分享,则不用传
- cid: int | str = 0 💡 目录 id,对应 parent_id
- limit: int = 32 💡 一页大小,意思就是 page_size
- offset: int = 0 💡 索引偏移,索引从 0 开始计算
- search_value: str = "." 💡 搜索文本,仅支持搜索文件名
- suffix: str = <default> 💡 文件后缀(扩展名),优先级高于 `type`
- type: int = <default> 💡 文件类型
- 0: 全部
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 99: 所有文件
"""
api = complete_url("/share/search", base_url=base_url)
if not isinstance(payload, dict):
payload = {"search_value": payload}
payload = {"cid": 0, "limit": 32, "offset": 0, "search_value": ".", **payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_send(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_send(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_send(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""创建(自己的)分享
POST https://webapi.115.com/share/send
:payload:
- file_ids: int | str 💡 文件列表,有多个用逗号 "," 隔开
- is_asc: 0 | 1 = 1 💡 是否升序排列
- order: str = "file_name" 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- ignore_warn: 0 | 1 = 1 💡 忽略信息提示,传 1 就行了
"""
api = complete_url("/share/send", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
payload = {"ignore_warn": 1, "is_asc": 1, "order": "file_name", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_send_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_send_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_send_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""创建(自己的)分享
POST https://proapi.115.com/{app}/2.0/share/send
:payload:
- file_ids: int | str 💡 文件列表,有多个用逗号 "," 隔开
- is_asc: 0 | 1 = 1 💡 是否升序排列
- order: str = "file_name" 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- ignore_warn: 0 | 1 = 1 💡 忽略信息提示,传 1 就行了
"""
api = complete_url("/2.0/share/send", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_ids": payload}
payload = {"ignore_warn": 1, "is_asc": 1, "order": "file_name", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_setting(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_setting(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_setting(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享设置
GET https://webapi.115.com/share/setting
"""
api = complete_url("/share/setting", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def share_skip_login_check(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_skip_login_check(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_skip_login_check(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""检查是否可免登录下载
POST https://webapi.115.com/share/is_skip_login
:payload:
- share_code: str 💡 分享码
- receive_code: str 💡 接收码(访问密码)
- file_id: int | str = 1 💡 文件 id(可以随便填一个非 0 的值)
"""
api = complete_url("/share/is_skip_login", base_url=base_url)
payload.setdefault("file_id", 1)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_skip_login_down(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_skip_login_down(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_skip_login_down(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""开启或关闭免登录下载
POST https://webapi.115.com/share/skip_login_down
:payload:
- share_code: str 💡 分享码
- skip_login: 0 | 1 = 1 💡 是否开启
"""
api = complete_url("/share/skip_login_down", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
payload.setdefault("skip_login", 1)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_skip_login_download_url(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
strict: bool = True,
app: str = "chrome",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115URL:
...
@overload
def share_skip_login_download_url(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
strict: bool = True,
app: str = "chrome",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115URL]:
...
[docs]
def share_skip_login_download_url(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
strict: bool = True,
app: str = "chrome",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115URL | Coroutine[Any, Any, P115URL]:
"""获取分享链接中某个文件的下载链接
.. note::
可以作为 ``staticmethod`` 使用
:param payload: 请求参数,如果为 int 或 str,则视为 `file_id`
- file_id: int | str 💡 文件 id
- receive_code: str 💡 接收码(访问密码)
- share_code: str 💡 分享码
:param share_url: 分享链接,如果提供的话,会被拆解并合并到 `payload` 中,优先级较高
:param strict: 如果为 True,当目标是目录时,会抛出 IsADirectoryError 异常
:param app: 使用此设备的接口
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 下载链接
"""
cls: P115Client = __class__ # type: ignore
def gen_step():
if app in ("web", "desktop"):
resp = yield cls.share_skip_login_download_url_web(
self,
payload,
share_url=share_url,
async_=async_, # type: ignore
**request_kwargs,
)
else:
resp = yield cls.share_skip_login_download_url_app(
self,
payload,
share_url=share_url,
app=app,
async_=async_, # type: ignore
**request_kwargs,
)
resp["payload"] = payload
resp["is_download"] = True
check_response(resp)
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
url = data["url"]
if strict and not url:
throw(errno.EISDIR, resp)
return P115URL(
url["url"] if url else "",
id=int(data["fid"]),
name=data["fn"],
size=int(data["fs"]),
sha1=data.get("sha1", ""),
is_dir=not url,
)
return run_gen_step(gen_step, async_)
@overload
def share_skip_login_download_url_app(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_skip_login_download_url_app(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_skip_login_download_url_app(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
app: str = "chrome",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取免登录下载链接
POST https://proapi.115.com/app/share/skip_login_downurl
.. note::
可以作为 ``staticmethod`` 使用
:payload:
- file_id: int | str
- receive_code: str
- share_code: str
"""
if isinstance(self, ClientRequestMixin):
assert payload is not None
request_kwargs["self"] = self
else:
payload = self
if not isinstance(payload, dict):
payload = {"file_id": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
if app in ("", "chrome"):
api = complete_url("/app/share/skip_login_downurl", base_url)
def parse(_, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if json["state"] and (data := json.get("data")):
json["data"] = json_loads(rsa_decrypt(data))
return json
request_kwargs.setdefault("parse", parse)
payload = {"data": rsa_encrypt(dumps(payload)).decode()}
else:
api = complete_url("/2.0/share/skip_login_downurl", base_url=base_url, app=app)
request, request_kwargs = get_request(
url=api, method="POST", data=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
def share_skip_login_download_url_web(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_skip_login_download_url_web(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_skip_login_download_url_web(
self: int | str | dict | ClientRequestMixin,
payload: None | int | str | dict = None,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取免登录下载链接
POST https://webapi.115.com/share/skip_login_downurl
.. note::
可以作为 ``staticmethod`` 使用
:payload:
- share_code: str 💡 分享码
- receive_code: str 💡 接收码(访问密码)
- file_id: int | str 💡 文件 id
"""
api = complete_url("/share/skip_login_downurl", base_url=base_url)
if isinstance(self, ClientRequestMixin):
assert payload is not None
request_kwargs["self"] = self
else:
payload = self
if not isinstance(payload, dict):
payload = {"file_id": payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
request, request_kwargs = get_request(
url=api, method="POST", data=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
def share_skip_login_down_first(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_skip_login_down_first(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_skip_login_down_first(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""免登录下载信息
GET https://webapi.115.com/share/skip_login_down_first
:payload:
- share_code: str 💡 分享码
"""
api = complete_url("/share/skip_login_down_first", base_url=base_url)
if isinstance(payload, str):
payload = {"share_code": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_skip_login_down_details(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_skip_login_down_details(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_skip_login_down_details(
self,
payload: str | dict = "",
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""流量消耗明细
GET https://webapi.115.com/share/skip_login_down_details
:payload:
- start_time: str = <default> 💡 开始时间,格式为 "YYYY-MM-DD hh:mm:ss",默认为今天零点
- end_time: str = <default> 💡 结束时间(含),默认为明天零点
- share_code: str = "" 💡 分享码,如果为空则统计所有分享
- offset: int = 0
- limit: int = 32
"""
api = complete_url("/share/skip_login_down_details", base_url=base_url)
today = date.today()
default_start_time = f"{today} 00:00:00"
default_end_time = f"{today + timedelta(days=1)} 00:00:00"
if isinstance(payload, str):
payload = {"start_time": payload or default_start_time}
payload = {"share_code": "", "limit": 32, "offset": 0, "start_time": default_start_time, "end_time": default_end_time, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_snap(
self: int | str | dict | ClientRequestMixin,
payload: int | str | dict = 0,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_snap(
self: int | str | dict | ClientRequestMixin,
payload: int | str | dict = 0,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_snap(
self: int | str | dict | ClientRequestMixin,
payload: int | str | dict = 0,
/,
share_url: str = "",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享链接的某个目录中的文件和子目录的列表(包含详细信息)
GET https://webapi.115.com/share/snap
.. note::
可以作为 ``staticmethod`` 使用
如果是登录状态,且查看自己的分享时,则可以不提供 receive_code,而且即使还在审核中,也能获取文件列表
.. caution::
虽然可以不登录即可获取数据,但是一旦过于频繁,会封禁 IP 一段时间
:payload:
- share_code: str
- receive_code: str
- cid: int | str = 0
- limit: int = 32
- offset: int = 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "user_ptime": 创建时间/修改时间
"""
api = complete_url("/share/snap", base_url=base_url)
if isinstance(self, ClientRequestMixin):
request_kwargs["self"] = self
else:
payload = self
if not isinstance(payload, dict):
payload = {"cid": payload}
payload = {"cid": 0, "limit": 32, "offset": 0, **payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
request, request_kwargs = get_request(url=api, params=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
def share_snap_app(
self,
payload: int | str | dict = 0,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_snap_app(
self,
payload: int | str | dict = 0,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_snap_app(
self,
payload: int | str | dict = 0,
/,
share_url: str = "",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取分享链接的某个目录中的文件和子目录的列表(包含详细信息)
GET https://proapi.115.com/{app}/2.0/share/snap
.. caution::
这个接口必须登录使用,并且对于其它人的网盘文件,每个目录中最多获取前 1000 条(但获取自己的资源正常)
:payload:
- share_code: str
- receive_code: str
- cid: int | str = 0
- limit: int = 32
- offset: int = 0
- asc: 0 | 1 = <default> 💡 是否升序排列
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "user_ptime": 创建时间/修改时间
"""
api = complete_url("/2.0/share/snap", base_url=base_url, app=app)
if not isinstance(payload, dict):
payload = {"cid": payload}
payload = {"cid": 0, "limit": 32, "offset": 0, **payload}
if share_url:
share_payload = share_extract_payload(share_url)
payload["share_code"] = share_payload["share_code"]
payload["receive_code"] = share_payload.get("receive_code") or ""
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def share_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_update(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""变更(自己的)分享的配置(例如改访问密码,取消分享)
POST https://webapi.115.com/share/updateshare
:payload:
- share_code: str 💡 分享码,多个用逗号 "," 隔开
- receive_code: str = <default> 💡 接收码(访问密码)
- share_duration: int = <default> 💡 分享天数: n(填入指定天数),-1(长期)
- is_custom_code: 0 | 1 = <default> 💡 用户自定义口令(不用管)
- auto_fill_recvcode: 0 | 1 = <default> 💡 分享链接自动填充口令(不用管)
- auto_renewal: 0 | 1 = <default> 💡 是否自动续期
- share_channel: int = <default> 💡 分享渠道代码(不用管)
- action: str = <default> 💡 操作: "cancel":取消分享 "delete":删除分享
- skip_login_down_flow_limit: "" | int = <default> 💡 设置免登录下载限制流量,如果为 "" 则不限,单位: 字节
- access_user_ids = int | str = <default> 💡 设置访问账号,多个用逗号 "," 隔开
- receive_user_limit: int = <default> 💡 接收次数
- reset_receive_user: 0 | 1 = <default> 💡 重置接收次数
"""
api = complete_url("/share/updateshare", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def share_update_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def share_update_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def share_update_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""变更(自己的)分享的配置(例如改访问密码,取消分享)
POST https://proapi.115.com/{app}/2.0/share/updateshare
:payload:
- share_code: str 💡 分享码,多个用逗号 "," 隔开
- receive_code: str = <default> 💡 接收码(访问密码)
- share_duration: int = <default> 💡 分享天数: n(填入指定天数),-1(长期)
- is_custom_code: 0 | 1 = <default> 💡 用户自定义口令(不用管)
- auto_fill_recvcode: 0 | 1 = <default> 💡 分享链接自动填充口令(不用管)
- share_channel: int = <default> 💡 分享渠道代码(不用管)
- action: str = <default> 💡 操作: "cancel":取消分享 "delete":删除分享
- skip_login_down_flow_limit: "" | int = <default> 💡 设置免登录下载限制流量,如果为 "" 则不限,单位: 字节
- access_user_ids = int | str = <default> 💡 设置访问账号,多个用逗号 "," 隔开
- receive_user_limit: int = <default> 💡 接收次数
- reset_receive_user: 0 | 1 = <default> 💡 重置接收次数
"""
api = complete_url("/2.0/share/updateshare", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Tool API ##########
@overload
def tool_clear_empty_folder(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_clear_empty_folder(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def tool_repeat(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_repeat(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def tool_repeat_delete(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_repeat_delete(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def tool_repeat_delete_status(
self,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_repeat_delete_status(
self,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def tool_repeat_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_repeat_list(
self,
payload: int | dict = 0,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def tool_repeat_status(
self,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_repeat_status(
self,
/,
base_url: str | Callable[[], str] = "https://aps.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
@overload
def tool_space(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def tool_space(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
########## Upload API ##########
@overload
def upload_info(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_info(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_info(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取和上传有关的信息,其中 "user_id" 和 "userkey" 是至关重要的
GET https://webapi.115.com/files/uploadinfo
"""
api = complete_url("/files/uploadinfo", base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def upload_info_app(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_info_app(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_info_app(
self,
/,
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取和上传有关的信息,其中 "user_id" 和 "userkey" 是至关重要的
GET https://proapi.115.com/app/uploadinfo
"""
api = complete_url("/app/uploadinfo", base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def upload_init(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_init(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_init(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""初始化上传任务,可能秒传
POST https://uplb.115.com/4.0/initupload.php
.. caution::
这个接口,偶尔会返回 HTTP 401 错误,你只需要再次重试即可
:payload:
- fileid: str 💡 文件的 sha1
- filename: str 💡 文件名
- filesize: int 💡 文件大小
- target: str = "U_1_0" 💡 保存目标,格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}"
- sign_key: str = "" 💡 2 次验证的 key
- sign_val: str = "" 💡 2 次验证的值
- topupload: int | str = "true" 💡 上传调度文件类型调度标记
- userid: int | str = <default> 💡 用户 id
- userkey: str = <default> 💡 用户的 key
"""
api = complete_url("/4.0/initupload.php", base_url=base_url)
payload = {
"appid": 0,
"target": "U_1_0",
"sign_key": "",
"sign_val": "",
"topupload": "true",
"appversion": _app_version,
**payload,
}
appversion = payload["appversion"]
if "userid" not in payload:
payload["userid"] = self.user_id
if "userkey" not in payload:
payload["userkey"] = self.user_key
request_kwargs["headers"] = dict_update(
dict(request_kwargs.get("headers") or ()),
{
"content-type": "application/x-www-form-urlencoded",
"user-agent": f"Mozilla/5.0 115disk/{appversion} 115Browser/{appversion} 115wangpan_android/{appversion}",
},
)
request_kwargs.update(make_upload_payload(payload))
def parse_upload_init_response(_, content: bytes, /) -> dict:
data = ecdh_aes_decrypt(content)
return json_loads(data)
request_kwargs.setdefault("parse", parse_upload_init_response)
return self.request(url=api, method="POST", async_=async_, **request_kwargs)
@overload
def upload_key(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_key(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_key(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 user_key
GET https://proapi.115.com/{app}/2.0/user/upload_key
"""
api = complete_url("/2.0/user/upload_key", base_url=base_url, app=app)
def gen_step():
resp = yield self.request(url=api, async_=async_, **request_kwargs)
if resp["state"]:
self.user_key = resp["data"]["userkey"]
return resp
return run_gen_step(gen_step, async_)
@overload
def upload_resume(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_resume(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_resume(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取恢复断点续传所需信息
POST https://uplb.115.com/3.0/resumeupload.php
:payload:
- fileid: str 💡 文件的 sha1 值
- filesize: int 💡 文件大小,单位是字节
- target: str 💡 上传目标,默认为 "U_1_0",格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}"
- pickcode: str 💡 提取码
- userid: int = <default> 💡 用户 id
"""
api = complete_url("/3.0/resumeupload.php", base_url=base_url)
if isinstance(payload, str):
payload = {"pickcode": payload}
else:
payload = dict(payload)
if "pickcode" not in payload:
if "pick_code" in payload:
payload["pickcode"] = payload["pick_code"]
callback_var: None | dict = None
if "callback_var" in payload:
callback_var = loads(payload["callback_var"])
elif "callback" in payload:
callback_var = loads(payload["callback"]["callback_var"])
if callback_var:
payload.update(
pickcode=callback_var["x:pick_code"],
target=callback_var["x:target"],
userid=callback_var["x:user_id"],
)
payload.setdefault("fileid", "0" * 40)
payload.setdefault("filesize", 1)
payload.setdefault("target", "U_1_0")
if "userid" not in payload:
payload["userid"] = self.user_id
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def upload_avatar(
self,
/,
file: ( Buffer | str | PathLike | SupportsRead | Iterable[Buffer] ),
app: str = "web",
base_url: str | Callable[[], str] = "https://ictxl.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_avatar(
self,
/,
file: ( Buffer | str | PathLike | SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
app: str = "web",
base_url: str | Callable[[], str] = "https://ictxl.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_avatar(
self,
/,
file: ( Buffer | str | PathLike | SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
app: str = "web",
base_url: str | Callable[[], str] = "https://ictxl.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""上传一张图片,可用于作为头像(图片时效性很短,请尽快使用)
POST https://ictxl.115.com/app/1.1/{app}/1.2/upload/set_avatar
.. attention::
此接口采用 multi-part 上传,其实是可以一次传多个文件的,但我做的封装只允许传一张图片。
一次接口调用的上传流量,算上分片分隔符,大概是不能超过 32 MB,需要进一步测验。
:param file: 待上传的文件
:param app: 使用此设备的接口
:param base_url: 接口的基地址
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
api = complete_url(f"/app/1.1/{app}/1.2/upload/set_avatar", base_url=base_url)
if isinstance(file, (str, PathLike)):
file = open(file, "rb")
return self.request(url=api, method="POST", files={"file": ("a.jpg", file)}, async_=async_, **request_kwargs)
@overload
def upload_avatar2(
self,
/,
file: Buffer | str | PathLike,
app: str = "web",
base_url: str | Callable[[], str] = "https://job.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_avatar2(
self,
/,
file: Buffer | str | PathLike,
app: str = "web",
base_url: str | Callable[[], str] = "https://job.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_avatar2(
self,
/,
file: Buffer | str | PathLike,
app: str = "web",
base_url: str | Callable[[], str] = "https://job.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""上传一张图片,可用于作为头像
POST https://job.115.com/api/1.0/{app}/26.0/5/upload/avatar
:param file: 待上传的文件数据
:param app: 使用此设备的接口
:param base_url: 接口的基地址
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
api = complete_url(f"/api/1.0/{app}/1.0/upload/avatar", base_url=base_url)
if isinstance(file, (str, PathLike)):
data: Buffer = open(file, "rb").read()
else:
data = file
headers = dict(request_kwargs.pop("headers", None) or ())
headers["content-type"] = "application/x-www-form-urlencoded"
return self.request(
url=api,
method="POST",
data=b"img="+b64encode(data),
headers=headers,
async_=async_,
**request_kwargs,
)
@overload
def upload_chat_image_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_chat_image_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_chat_image_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""聊天窗口中上传图片接口的初始化
POST https://115.com/api/chat/image/upload
.. caution::
此接口不支持秒传,最大支持上传 50 MB 的文件,上传成功后不占用空间
.. caution::
通过扩展名来识别,仅支持以下格式图片(jpg,jpeg,png,gif,svg,webp,heic,bmp,dng)
.. note::
``target`` 随便设置,例如 ``"U_3_-1"``、``"U_4_-1"``、``"U_5_-2"``、``"U_3_-10"`` 等
:payload:
- filename: str = <default> 💡 文件名,默认为一个新的 uuid4 对象的字符串表示
- target: str = ``"U_3_-1"`` 💡 上传目标,格式为 ``f"U_{aid}_{pid}"``
- filesize: int | str = <default> 💡 图片大小
- height: int = <default> 💡 图片高度
- width: int = <default> 💡 图片宽度
"""
api = complete_url("/api/chat/image/upload", base_url=base_url)
if isinstance(payload, str):
payload = {"filename": payload}
elif "filename" not in payload:
payload["filename"] = str(uuid4()) + ".jpg"
payload = {"filesize": 1, "width": 1, "height": 1, "target": "U_3_-1", "userid": 11500, **payload}
return self.request(url=api, method="POST", json=payload, async_=async_, **request_kwargs)
@overload
def upload_image(
self,
/,
file: ( Buffer | str | PathLike | SupportsRead | Iterable[Buffer] ),
app: str = "web",
base_url: str | Callable[[], str] = "https://credentials.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_image(
self,
/,
file: ( Buffer | str | PathLike | SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
app: str = "web",
base_url: str | Callable[[], str] = "https://credentials.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_image(
self,
/,
file: ( Buffer | str | PathLike | SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
app: str = "web",
base_url: str | Callable[[], str] = "https://credentials.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""上传一张图片,可用于作为证件照
POST https://credentials.115.com/api/1.0/{app}/1.0/credentials/upload_images
.. attention::
此接口采用 multi-part 上传,其实是可以一次传多个文件的,但我做的封装只允许传一张图片,最大允许传 10 MB
:param file: 待上传的文件
:param app: 使用此设备的接口
:param base_url: 接口的基地址
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
api = complete_url(f"/api/1.0/{app}/1.0/credentials/upload_images", base_url=base_url)
if isinstance(file, (str, PathLike)):
file = open(file, "rb")
return self.request(url=api, method="POST", files={"image": ("a.jpg", file)}, async_=async_, **request_kwargs)
@overload
def upload_image_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_image_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_image_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""网页端的上传图片接口的初始化
POST https://uplb.115.com/3.0/imginitupload.php
.. caution::
此接口不支持秒传,最大支持上传 50 MB 的文件,上传成功后不占用空间
.. caution::
通过扩展名来识别,仅支持以下格式图片(jpg,jpeg,png,gif,svg,webp,heic,bmp,dng)
.. note::
``target`` 随便设置,例如 ``"U_3_-1"``、``"U_4_-1"``、``"U_5_-2"``、``"U_3_-10"`` 等
:payload:
- filename: str = <default> 💡 文件名,默认为一个新的 uuid4 对象的字符串表示
- target: str = ``"U_4_-1"`` 💡 上传目标,格式为 ``f"U_{aid}_{pid}"``
- filesize: int | str = <default> 💡 图片大小
- height: int = <default> 💡 图片高度
- width: int = <default> 💡 图片宽度
"""
api = complete_url("/3.0/imginitupload.php", base_url=base_url)
if isinstance(payload, str):
payload = {"filename": payload}
elif "filename" not in payload:
payload["filename"] = str(uuid4()) + ".jpg"
payload.setdefault("target", "U_4_-1")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def upload_sample_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_sample_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_sample_init(
self,
payload: str | dict,
/,
base_url: str | Callable[[], str] = "https://uplb.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""网页端的上传接口的初始化
POST https://uplb.115.com/3.0/sampleinitupload.php
.. caution::
此接口不支持秒传
.. note::
如果上传到自己的网盘,则 ``target`` 的格式是 "U_{aid}_{pid}",如果上传到共享目录,格式为 "S_{share_id}_{pid}"
:payload:
- filename: str = <default> 💡 文件名,默认为一个新的 uuid4 对象的字符串表示
- target: str = "U_1_0" 💡 上传目标,格式为 f"U_{aid}_{pid}"(网盘)或 f"S_{share_id}_{pid}"(共享目录)
- path: str = <default> 💡 保存目录,是在 `target` 对应目录下的相对路径,默认为 `target` 所对应目录本身
- filesize: int | str = <default> 💡 文件大小
"""
api = complete_url("/3.0/sampleinitupload.php", base_url=base_url)
if isinstance(payload, str):
payload = {"filename": payload}
elif "filename" not in payload:
payload["filename"] = str(uuid4())
payload.setdefault("target", "U_1_0")
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
# NOTE: 下列是关于上传功能的封装方法
@overload
def upload_file_init(
self,
/,
filename: str,
filesha1: str,
filesize: int,
dirname: str = "",
read_range_bytes_or_hash: None | Callable[[str], str | Buffer] = None,
pid: int | str = 0,
share_id: int = 0,
payload: None | Mapping = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_file_init(
self,
/,
filename: str,
filesha1: str,
filesize: int,
dirname: str = "",
read_range_bytes_or_hash: None | Callable[[str], str | Buffer] = None,
pid: int | str = 0,
share_id: int = 0,
payload: None | Mapping = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_file_init(
self,
/,
filename: str,
filesha1: str,
filesize: int,
dirname: str = "",
read_range_bytes_or_hash: None | Callable[[str], str | Buffer] = None,
pid: int | str = 0,
share_id: int = 0,
payload: None | Mapping = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""初始化上传,可能秒传,此接口是对 `upload_init` 的封装
.. note::
- 文件大小 和 sha1 是必需的,只有 sha1 是没用的。
- 如果文件大于等于 1 MB (1048576 B),就需要 2 次检验一个范围哈希,就必须提供 `read_range_bytes_or_hash`
:param filename: 文件名
:param filesize: 文件大小
:param filesha1: 文件的 sha1
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param read_range_bytes_or_hash: 调用以获取 2 次验证的数据或计算 sha1,接受一个数据范围,格式符合:
`HTTP Range Requests <https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests>`_,
返回值如果是 str,则视为计算好的 sha1,如果为 Buffer,则视为数据(之后会被计算 sha1)
:param pid: 上传文件到此目录的 id,或者指定的 target(格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}",但这里的 `aid` 无论如何取值,都视为 1)
:param share_id: 共享 id
:param payload: 其它的查询参数
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
if isinstance(pid, str) and pid.startswith(("U_", "S_")):
target = pid
else:
pid = to_id(pid)
if share_id:
target = f"S_{share_id}_{pid}"
else:
target = f"U_1_{pid}"
default_payload = payload
def gen_step():
payload = {
"filename": filename,
"fileid": filesha1.upper(),
"filesize": filesize,
"target": target,
"path": dirname,
}
if default_payload:
payload.update(default_payload)
resp = yield self.upload_init(
payload,
async_=async_,
**request_kwargs,
)
status = resp["status"]
if status == 7:
if read_range_bytes_or_hash is None:
raise ValueError("filesize >= 1 MB, thus need pass the `read_range_bytes_or_hash` argument")
payload["sign_key"] = resp["sign_key"]
sign_check: str = resp["sign_check"]
content: str | Buffer
if async_:
content = yield ensure_async(read_range_bytes_or_hash)(sign_check)
else:
content = read_range_bytes_or_hash(sign_check)
if isinstance(content, str):
payload["sign_val"] = content.upper()
else:
payload["sign_val"] = sha1(content).hexdigest().upper()
resp = yield self.upload_init(
payload,
async_=async_, # type: ignore
**request_kwargs,
)
status = resp["status"]
resp["reuse"] = status == 2
resp["state"] = status in (1, 2)
return resp
return run_gen_step(gen_step, async_)
@overload
@staticmethod
def _init_file(
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] ),
filename: str = "",
*,
async_: Literal[False] = False,
) -> SupportsRead:
...
@overload
@staticmethod
def _init_file(
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
filename: str = "",
*,
async_: Literal[True],
) -> Coroutine[Any, Any, SupportsRead]:
...
@staticmethod
def _init_file(
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
filename: str = "",
*,
async_: Literal[False, True] = False,
) -> SupportsRead | Coroutine[Any, Any, SupportsRead]:
def gen_step():
nonlocal file, filename
if isinstance(file, SupportsRead):
if not filename:
from os.path import basename
filename = getattr(file, "name", "")
filename = basename(filename)
elif not isinstance(file, Buffer):
path = file
is_url: None | bool = None
if isinstance(path, str):
is_url = path.startswith(("http://", "https://"))
elif isinstance(path, (URL, SupportsGeturl)):
is_url = True
if isinstance(path, URL):
path = str(path)
else:
path = path.geturl()
elif isinstance(path, PathLike):
is_url = False
path = fsdecode(path)
if is_url is not None:
path = cast(str, path)
if is_url:
if async_:
async def process():
return await AsyncHTTPFileReader.new(
cast(str, path),
headers={"user-agent": "", "accept-encoding": "identity"},
)
file = yield process()
else:
from httpfile import HTTPFileReader
file = HTTPFileReader(
path, headers={"user-agent": "", "accept-encoding": "identity"})
file = cast(HTTPFileReader, file)
if not filename:
filename = file.name
else:
file = open(path, "rb")
if not filename:
if is_url:
from posixpath import basename
from urllib.parse import unquote
filename = basename(unquote(urlsplit(path).path))
else:
from os.path import basename
filename = basename(path)
return file, filename
return run_gen_step(gen_step, async_)
@overload
def upload_chat_image(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] ),
pid: int | str = "U_3_-1",
share_id: int = 0,
filename: str = "1.jpg",
dirname: str = "",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_chat_image(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
pid: int | str = "U_3_-1",
share_id: int = 0,
filename: str = "1.jpg",
dirname: str = "",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_chat_image(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
pid: int | str = "U_3_-1",
share_id: int = 0,
filename: str = "1.jpg",
dirname: str = "",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""聊天窗口中上传一张图片
.. caution::
不支持秒传,但也不必传文件大小和 sha1,最大支持上传 50 MB 的文件
.. note::
``target`` 随便设置,例如 ``"U_3_-1"``、``"U_4_-1"``、``"U_5_-2"``、``"U_3_-10"`` 等
:param file: 待上传的文件
:param pid: 上传文件到此目录的 id 或 pickcode,或者指定的 target(格式为 ``f"U_{aid}_{pid}"`` 或 ``f"S_{share_id}_{pid}"``)
:param share_id: 共享 id
:param filename: 文件名,如果为空,则会自动确定
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
if isinstance(pid, str) and pid.startswith(("U_", "S_")):
target = pid
else:
pid = to_id(pid)
if share_id:
target = f"S_{share_id}_{pid}"
else:
target = f"U_1_{pid}"
def gen_step():
nonlocal file, filename
file, filename = yield self._init_file(file, filename, async_=async_) # type: ignore
resp = yield self.upload_chat_image_init(
{
"filename": filename or str(uuid4()) + ".jpg",
"path": dirname,
"target": target,
},
async_=async_,
**request_kwargs,
)
check_response(resp)
def parse(_, content: bytes):
data = json_maybe_decrypt_loads(content)
data["oss_info"] = resp
return data
request_kwargs.setdefault("parse", parse)
return self.request(
url=resp["host"],
method="POST",
data={
"host": resp["host"],
"key": resp["object"],
"policy": resp["policy"],
"OSSAccessKeyId": resp["accessid"],
"signature": resp["signature"],
"callback": resp["callback"],
},
files={"file": file},
async_=async_,
**request_kwargs,
)
return run_gen_step(gen_step, async_)
@overload
def upload_file_image(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] ),
pid: int | str = "U_4_-1",
share_id: int = 0,
filename: str = "1.jpg",
dirname: str = "",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_file_image(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
pid: int | str = "U_4_-1",
share_id: int = 0,
filename: str = "1.jpg",
dirname: str = "",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_file_image(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
pid: int | str = "U_4_-1",
share_id: int = 0,
filename: str = "1.jpg",
dirname: str = "",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""网页端的上传图片接口
.. caution::
不支持秒传,但也不必传文件大小和 sha1,最大支持上传 50 MB 的文件
.. note::
``target`` 随便设置,例如 ``"U_3_-1"``、``"U_4_-1"``、``"U_5_-2"``、``"U_3_-10"`` 等
:param file: 待上传的文件
:param pid: 上传文件到此目录的 id 或 pickcode,或者指定的 target(格式为 ``f"U_{aid}_{pid}"`` 或 ``f"S_{share_id}_{pid}"``)
:param share_id: 共享 id
:param filename: 文件名,如果为空,则会自动确定
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
if isinstance(pid, str) and pid.startswith(("U_", "S_")):
target = pid
else:
pid = to_id(pid)
if share_id:
target = f"S_{share_id}_{pid}"
else:
target = f"U_1_{pid}"
def gen_step():
nonlocal file, filename
file, filename = yield self._init_file(file, filename, async_=async_) # type: ignore
resp = yield self.upload_image_init(
{
"filename": filename or str(uuid4()) + ".jpg",
"path": dirname,
"target": target,
},
async_=async_,
**request_kwargs,
)
def parse(_, content: bytes):
data = json_maybe_decrypt_loads(content)
data["oss_info"] = resp
return data
request_kwargs.setdefault("parse", parse)
return self.request(
url=resp["host"],
method="POST",
data={
"name": filename,
"key": resp["object"],
"policy": resp["policy"],
"OSSAccessKeyId": resp["accessid"],
"success_action_status": "200",
"callback": resp["callback"],
"signature": resp["signature"],
},
files={"file": file},
async_=async_,
**request_kwargs,
)
return run_gen_step(gen_step, async_)
@overload
def upload_file_sample(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] ),
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
dirname: str = "",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_file_sample(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
dirname: str = "",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_file_sample(
self,
/,
file: ( Buffer | str | PathLike | URL | SupportsGeturl |
SupportsRead | Iterable[Buffer] | AsyncIterable[Buffer] ),
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
dirname: str = "",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""网页端的上传接口
.. caution::
不支持秒传,但也不必传文件大小和 sha1
.. note::
如果上传到共享目录,占用的是创建者的网盘空间
.. note::
只要上传后的 ``aid`` 或 ``area_id`` 不为 1,则不占用空间,这是 ``upload_file`` 所不能的(因为即使指定了 ``"U_{aid}_{pid}"``,也会忽略其中的 ``aid``,强行视为 1)
.. note::
如果我们把文件名分成两部分,名字 + 扩展名(含前缀点号 .),那么就有以下限制:
1. 如果只有名字,最长 255 字节,超出则截掉尾部
2. 如果只有扩展名,最长 255 字节,超出则截掉尾部
3. 如果两者皆有,则情况比较复杂:
1. (😯 这属于通常情况了)如果扩展名长度 <= 253 字节,则总长度不超过 255 字节,名字的超出部分会被替换为两个点号 ..,占用 2 个字节
2. (😂 特意构造或者误断)如果扩展名长度 > 253 字节,允许的长度上限至少是 512 字节,又会有几套限制规则(具体会是哪一个,我目前还比较迷糊):
1. 总长不超过 552 字节,超出部分会直接截掉名字的尾部,仍然不够则再截掉扩展名的尾部,此时只有 552 字节的扩展名部分
2. 总长不超过 512 字节,超出部分会直接截掉名字的尾部
3. 总长不超过 512 字节,超出部分会直接截掉名字的尾部并替换为 2 个点号 ..,仍然不够则再截掉扩展名的尾部,极端情况下,扩展名为 510 字节,名字部分变成两个点号 ..,占 2 字节
4. 总长不超过 512 字节,只要超过 255 字节,名字就被整体替换为 2 个点号 ..,仍然不够则再截掉扩展名的尾部
5. 总长不超过 512 字节,只要超过 255 字节,只会把名字的最后两个字符替换为 2 个点号 ..
6. ... (其它未被发现的规则,例如我还未测试仅含中文的场景)
.. note::
通过 ``pid``,支持随意指定上传目标。特别是当格式为 ``f"U_{aid}_{pid}"``,允许其中的 ``aid != 1`` 和 ``pid < 0`` (可能有特殊指代)。
这里有一些特殊的位置:
- ``U_0_{n}``: 等同于 ``pid="U_1_0"``,无论 ``n`` 是什么值
- ``U_1_-11``: 上传附件到根目录下的 "记录文件"
- ``U_3_-6``: 上传附件到根目录下的 "消息文件"
- ``U_3_-15``: 上传封面到临时目录,等同于 ``pid="U_15_0"``
- ``U_3_-24``: 上传文件到根目录下的 "手机备份"
- ``U_3_{n}``: ``n`` 可取 -1,-2,-3,-4,-5,-8,-9,-10,-12,-13,等同于 ``pid="U_120_0"``
- ``U_{a}_{n}``: ``n`` 可取 0 或所有 ``aid=1`` 的文件或目录的 id,则上传目标为 ``aid=a, cid=n``
.. caution::
如果最终的 ``aid`` 为 1 或 15,则剩余网盘空间会减掉上传文件的大小。
尽量不要使 ``aid=15``,因为这会导致白白占掉空间,不能删除,也不能正常下载。
:param file: 待上传的文件
:param pid: 上传文件到此目录的 id 或 pickcode,或者指定的 target(格式为 ``f"U_{aid}_{pid}"`` 或 ``f"S_{share_id}_{pid}"``)
:param share_id: 共享 id
:param filename: 文件名,如果为空,则会自动确定
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
if isinstance(pid, str) and pid.startswith(("U_", "S_")):
target = pid
else:
pid = to_id(pid)
if share_id:
target = f"S_{share_id}_{pid}"
else:
target = f"U_1_{pid}"
def gen_step():
nonlocal file, filename
file, filename = yield self._init_file(file, filename, async_=async_) # type: ignore
resp = yield self.upload_sample_init(
{
"filename": filename or str(uuid4()),
"path": dirname,
"target": target,
},
async_=async_,
**request_kwargs,
)
def parse(_, content: bytes):
data = json_maybe_decrypt_loads(content)
data["oss_info"] = resp
return data
request_kwargs.setdefault("parse", parse)
return self.request(
url=resp["host"],
method="POST",
data={
"name": filename,
"key": resp["object"],
"policy": resp["policy"],
"OSSAccessKeyId": resp["accessid"],
"success_action_status": "200",
"callback": resp["callback"],
"signature": resp["signature"],
},
files={"file": file},
async_=async_,
**request_kwargs,
)
return run_gen_step(gen_step, async_)
@overload
def upload_file(
self,
/,
file: Buffer | str | PathLike | URL | SupportsGeturl | SupportsRead,
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
filesha1: str = "",
filesize: int = -1,
dirname: str = "",
payload: None | Mapping = None,
partsize: int = 0,
callback: None | dict = None,
upload_id: str = "",
endpoint: str = "https://oss-cn-shenzhen.aliyuncs.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def upload_file(
self,
/,
file: Buffer | str | PathLike | URL | SupportsGeturl | SupportsRead,
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
filesha1: str = "",
filesize: int = -1,
dirname: str = "",
payload: None | Mapping = None,
partsize: int = 0,
callback: None | dict = None,
upload_id: str = "",
endpoint: str = "https://oss-cn-shenzhen.aliyuncs.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def upload_file(
self,
/,
file: Buffer | str | PathLike | URL | SupportsGeturl | SupportsRead,
pid: int | str = 0,
share_id: int = 0,
filename: str = "",
filesha1: str = "",
filesize: int = -1,
dirname: str = "",
payload: None | Mapping = None,
partsize: int = 0,
callback: None | dict = None,
upload_id: str = "",
endpoint: str = "https://oss-cn-shenzhen.aliyuncs.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""上传文件
.. note::
如果提供了 ``callback``,则强制为分块上传。
此时,最好提供一下 ``upload_id``,否则就是从头开始。
此时可以省略 ``pid``、``filename``、``filesha1``、``filesize``、``user_id``、``user_key``、``partsize``
.. caution::
``partsize > 0`` 时,不要把 ``partsize`` 设置得太小,起码得 10 MB (10485760) 以上
.. note::
这个文件无论把文件传到哪,都会占用空间。这里有一些特殊的位置:
- ``U_3_-8``: 此时 ``aid=120``,会平白占用空间,根本不能回收,所以要慎用
- ``U_3_-9``: 此时 ``aid=12``,可以通过 ``fs_delete_app`` 删除上传后的文件 id 来释放空间
:param file: 待上传的文件
:param pid: 上传文件到此目录的 id 或 pickcode,或者指定的 target(格式为 f"U_{aid}_{pid}" 或 f"S_{share_id}_{pid}",但这里的 `aid` 无论如何取值,都视为 1)
:param share_id: 共享 id
:param filename: 文件名,如果为空,则会自动确定
:param filesha1: 文件的 sha1,如果为空,则会自动确定
:param filesize: 文件大小,如果为 -1,则会自动确定
:param dirname: 保存目录,是在 `pid` 对应目录下的相对路径,默认为 `pid` 所对应目录本身
:param payload: 其它的查询参数
:param partsize: 分块上传的分块大小。如果为 0,则不做分块上传;如果 < 0,则会自动确定
:param callback: 回调数据
:param upload_id: 上传任务 id
:param endpoint: 上传目的网址
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 接口响应
"""
return upload_file(
file=file,
pid=pid,
share_id=share_id,
filename=filename,
filesha1=filesha1,
filesize=filesize,
dirname=dirname,
user_id=self.user_id,
user_key=self.user_key,
payload=payload,
partsize=partsize,
callback=callback,
upload_id=upload_id,
endpoint=endpoint,
async_=async_, # type: ignore
**request_kwargs,
)
########## User API ##########
@overload
def user_base_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_base_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_base_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://qrcodeapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户的基本信息
GET https://qrcodeapi.115.com/app/1.0/{app}/1.0/user/base_info
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/base_info", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_card(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_card(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_card(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户信息
GET https://proapi.115.com/{app}/user/card
"""
api = complete_url("/user/card", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_count_space_nums(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_count_space_nums(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_count_space_nums(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取当前已用空间、可用空间、登录设备等信息
GET https://proapi.115.com/{app}/2.0/user/count_space_nums
"""
api = complete_url("/2.0/user/count_space_nums", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_display_uid_list(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_display_uid_list(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_display_uid_list(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""账号信息 VIP 相关信息
GET https://passportapi.115.com/app/1.0/{app}/1.0/user/display_uid_list
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/display_uid_list", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_fingerprint(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_fingerprint(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_fingerprint(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取截图时嵌入的水印
GET https://webapi.115.com/user/fingerprint
"""
api = complete_url("/user/fingerprint", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload # type: ignore
def user_info(
self: int | str | dict | ClientRequestMixin = 11500,
payload: None | int | str | dict = None,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_info(
self: int | str | dict | ClientRequestMixin = 11500,
payload: None | int | str | dict = None,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_info(
self: int | str | dict | ClientRequestMixin = 11500,
payload: None | int | str | dict = None,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户信息
GET https://my.115.com/proapi/3.0/index.php
.. note::
可以作为 ``staticmethod`` 使用,但必须指定查询参数 ``uid``
:payload:
- uid: int | str
- method: str = "user_info" 💡 需要获取的数据类别
- "user_info": 用户信息
- "get_interests_list": 兴趣列表
- "get_jobs_list": 工作列表
- "get_privacy_settings": 隐私资料
- "get_public": 公开资料
- "check_secret_token": 隐私令牌
- ...
"""
api = complete_url("/proapi/3.0/index.php", base_url=base_url, query={"method": "user_info"})
if isinstance(self, ClientRequestMixin):
request_kwargs["self"] = self
if payload is None:
if isinstance(self, P115OpenClient):
payload = self.user_id
else:
payload = 11500
else:
payload = self
if isinstance(payload, int):
payload = {"uid": payload}
elif isinstance(payload, str):
payload = {"method": payload}
payload.setdefault("method", "user_info")
request, request_kwargs = get_request(url=api, params=payload, **request_kwargs)
return request(async_=async_, **request_kwargs)
@overload
def user_info2(
self,
payload: int | str | dict = "",
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://home.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_info2(
self,
payload: int | str | dict = "",
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://home.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_info2(
self,
payload: int | str | dict = "",
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://home.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户信息
GET https://home.115.com/api/1.0/{app}/1.0/user/user_info
:payload:
- uid: int | str
"""
api = complete_url(f"/api/1.0/{app}/1.0/user/user_info", base_url=base_url)
if not payload:
payload = {"uid": self.user_id}
elif not isinstance(payload, dict):
payload = {"uid": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def user_info3(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_info3(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_info3(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户信息
GET https://my.115.com/?ct=ajax&ac=nav
"""
api = complete_url(base_url=base_url, query={"ct": "ajax", "ac": "nav"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_info4(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_info4(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_info4(
self,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户信息
GET https://webapi.115.com/user/info
"""
api = complete_url("/user/info", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_info_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_info_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_info_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""更新用户信息
POST https://my.115.com/proapi/3.0/index.php?method=set_user
:payload:
- user_name: str = <default> 💡 网名(1-15 个中英文字符,15天允许修改一次网名)
- gender: -1 | 0 | 1 = <default> 💡 性别。-1:未知 0:女 1:男
- calendar_type: 0 | 1 = <default> 💡 日历类型。0:公历 1:农历
- birthday: str = <default> 💡 生日,格式为 年-月-日(不需要补前 0,初始值为 0-0-0)
- height: int = <default> 💡 身高
- weight: int = <default> 💡 体重
- blood_type: "A" | "B" | "C" | "D" | "O" = <default> 💡 血型。A:A型 B:B型 C:AB型 O:O型 D:其它
- is_marry: int = <default> 💡 感情
- 0: 保密
- 1: 单身
- 2: 恋爱中
- 3: 已婚
- 4: 分居
- 5: 离异
- 9: 请选择
- education: int = <default> 💡 学历
- -1: 选择学历
- 0: 初中
- 1: 高中
- 2: 中专
- 3: 大专
- 4: 本科
- 5: 硕士
- 6: 博士及以上
- job: int = <default> 💡 职业
- -1: 选择职业
- 1: 计算机/互联网/通信
- 2: 生产/工艺/制造
- 3: 医疗/护理/制药
- 4: 金融/银行/投资/保险
- 5: 商业/服务业/个体经营
- 6: 文化/广告/传媒
- 7: 娱乐/艺术/表演
- 8: 律师/法务
- 9: 教育/培训
- 10: 公务员/行政/事业单位
- 11: 模特
- 12: 空姐
- 13: 学生
- 14: 其他职业
- salary: str = <default> 💡 收入
- ""
- "2千-3千"
- "3千-4.5千"
- "4.5千-6千"
- "7千-8千"
- "8千-1万"
- "1万以下"
- "1万-2万"
- "2万-3万"
- "3万-4万"
- "4万-5万"
- "5万以上"
- location_birth: int = <default> 💡 家乡。填 115 给出的地区编码,初始值为 0
- location: int = <default> 💡 现居地。填 115 给出的地区编码,初始值为 0
- location_link: int = <default> 💡 快递地址。填 115 给出的地区编码,初始值为 0
- address: str = <default> 💡 输入详细街道地址
- wechat: str = <default> 💡 微信
- weibo: str = <default> 💡 微博
- alipay: str = <default> 💡 支付宝
- pub_mobile: str = <default> 💡 电话
- pub_email: str = <default> 💡 邮箱
- homepage: str = <default> 💡 个人网站
- like_celeb: str = <default> 💡 最喜欢的名人
- like_music: str = <default> 💡 最喜欢的音乐
- like_animal: str = <default> 💡 最喜欢的动物
- like_book: str = <default> 💡 最喜欢的书籍
- like_video: str = <default> 💡 最喜欢的视频
- interest: str = <default> 💡 兴趣爱好
"""
api = complete_url("/proapi/3.0/index.php", base_url=base_url, query={"method": "set_user"})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_interests_list(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_interests_list(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_interests_list(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""用户兴趣列表
GET https://my.115.com/proapi/3.0/index.php?method=get_interests_list
"""
api = complete_url("/proapi/3.0/index.php", base_url=base_url, query={"method": "get_interests_list"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_my(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_my(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_my(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取此用户信息
GET https://my.115.com/?ct=ajax&ac=
"""
api = complete_url(base_url=base_url, query={"ct": "ajax", "ac": "nav"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_my_info(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_my_info(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_my_info(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取此用户信息(更全)
GET https://my.115.com/?ct=ajax&ac=get_user_aq
"""
api = complete_url(base_url=base_url, query={"ct": "ajax", "ac": "get_user_aq"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_points_balance(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://points.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_points_balance(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://points.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_points_balance(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://points.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""剩余的签到积分
GET https://points.115.com/api/1.0/{app}/1.0/user/balance
"""
api = complete_url(f"/api/1.0/{app}/1.0/user/balance", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_points_sign(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_points_sign(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_points_sign(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取签到信息
GET https://proapi.115.com/{app}/2.0/user/points_sign
"""
api = complete_url("/2.0/user/points_sign", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_points_sign_post(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_points_sign_post(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_points_sign_post(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""每日签到(注意:不要用 web,即浏览器,的 cookies,会失败)
POST https://proapi.115.com/{app}/2.0/user/points_sign
"""
api = complete_url("/2.0/user/points_sign", base_url=base_url, app=app)
t = int(time())
payload = {
"token": sha1(b"%d-Points_Sign@#115-%d" % (self.user_id, t)).hexdigest(),
"token_time": t,
}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_points_transaction(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://points.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_points_transaction(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://points.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_points_transaction(
self,
payload: int | dict = 0,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://points.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""签到记录
GET https://points.115.com/api/1.0/{app}/1.0/user/transaction
payload:
- start: int = 0
- limit: int = 32
- month: str = <default> 💡 月份,格式为 YYYYMM
"""
api = complete_url(f"/api/1.0/{app}/1.0/user/transaction", base_url=base_url)
if isinstance(payload, int):
payload = {"start": payload}
payload.setdefault("limit", 32)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def user_public(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_public(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_public(
self,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""用户隐私设置
GET https://my.115.com/proapi/3.0/index.php?method=get_public
"""
api = complete_url("/proapi/3.0/index.php", base_url=base_url, query={"method": "get_public"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_public_set(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_public_set(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_public_set(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://my.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置用户隐私
POST https://my.115.com/proapi/3.0/index.php?method=set_public
:payload:
- column: str 💡 隐私项
- open: 0 | 1 = 1 💡 是否公开可见
"""
api = complete_url("/proapi/3.0/index.php", base_url=base_url, query={"method": "set_public"})
if isinstance(payload, str):
payload = {"column": payload}
payload.setdefault("open", 1)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_relation_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://home.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_relation_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://home.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_relation_info(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://home.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户的关系信息
GET https://home.115.com/api/1.0/{app}/1.0/my_star/relation_info
"""
api = complete_url(f"api/1.0/{app}/1.0/my_star/relation_info", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_security_key_check(
self,
payload: int | str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_security_key_check(
self,
payload: int | str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_security_key_check(
self,
payload: int | str | dict = "",
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取安全密钥对应的 token,可以提供给某些接口,作为通过安全密钥验证的凭证
POST https://passportapi.115.com/app/1.0/{app}/1.0/user/security_key_check
:payload:
- passwd: int | str = "000000" 💡 安全密钥,值为实际安全密钥的 md5 哈希值
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/security_key_check", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"passwd": payload}
payload["passwd"] = md5_secret_password(payload.get("passwd"))
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_setting(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取此账户的设置
GET https://115.com/?ac=setting&even=saveedit&is_wl_tpl=1
"""
api = complete_url(base_url=base_url, query={"ac": "setting", "even": "saveedit", "is_wl_tpl": 1})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_setting2(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting2(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting2(
self,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取此账户的设置
GET https://115.com/?ct=user_setting&ac=get
"""
api = complete_url(base_url=base_url, query={"ct": "user_setting", "ac": "get"})
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_setting_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改此账户的网页版设置(提示:较为复杂,自己抓包研究)
POST https://115.com/?ac=setting&even=saveedit&is_wl_tpl=1
"""
api = complete_url(base_url=base_url, query={"ct": "setting", "even": "saveedit", "is_wl_tpl": 1})
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_setting_web(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting_web(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting_web(
self,
payload: dict = {},
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取此账户的 app 版设置(提示:较为复杂,自己抓包研究)
GET https://webapi.115.com/user/setting
:payload:
- keys: str 💡 查询的设置参数,多个用逗号 "," 隔开
"""
api = complete_url("/user/setting", base_url=base_url)
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def user_setting_web_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting_web_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting_web_set(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取(并可修改)此账户的网页版设置(提示:较为复杂,自己抓包研究)
POST https://webapi.115.com/user/setting
"""
api = complete_url("/user/setting", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_setting_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting_app(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取此账户的 app 版设置(提示:较为复杂,自己抓包研究)
GET https://proapi.115.com/{app}/1.0/user/setting
"""
api = complete_url("/1.0/user/setting", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_setting_app_set(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_setting_app_set(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_setting_app_set(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取(并可修改)此账户的网页版设置(提示:较为复杂,自己抓包研究)
POST https://proapi.115.com/{app}/1.0/user/setting
"""
api = complete_url("/1.0/user/setting", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_sign(
self,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_sign(
self,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_sign(
self,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""个性签名
GET https://q.115.com/home/setting/sign
"""
api = complete_url("/home/setting/sign", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_sign_set(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_sign_set(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_sign_set(
self,
payload: dict | str,
/,
base_url: str | Callable[[], str] = "https://q.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""修改个性签名
POST https://q.115.com/ajax_users/save_sign
:payload:
- content: str 💡 个性签名,支持 HTML
"""
api = complete_url("/ajax_users/save_sign", base_url=base_url)
if isinstance(payload, str):
payload = {"content": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_space_info(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_space_info(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_space_info(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取使用空间的统计数据(较为简略,如需更详细,请用 ``P115Client.fs_index_info()``)
GET https://proapi.115.com/{app}/user/space_info
"""
api = complete_url("/user/space_info", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_teen_mode_state(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_teen_mode_state(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_teen_mode_state(
self,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取青少年(未成年)模式状态
GET https://passportapi.115.com/app/1.0/{app}/1.0/user/teen_mode_state
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/teen_mode_state", base_url=base_url)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_teen_mode_state_set(
self,
payload: bool | dict = True,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_teen_mode_state_set(
self,
payload: bool | dict = True,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_teen_mode_state_set(
self,
payload: bool | dict = True,
/,
app: str = "web",
base_url: str | Callable[[], str] = "https://passportapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""开关青少年(未成年)模式状态
POST https://passportapi.115.com/app/1.0/{app}/1.0/user/teen_mode_set_state
:payload:
- state: 0 | 1 💡 是否开启
- passwd: str = "0000" 💡 密码(4 位数字),需要经过 md5 签名处理,`md5(f"{passwd}{user_id}62454aa2c6fd4".encode("ascii")).hexdigest()`
"""
api = complete_url(f"/app/1.0/{app}/1.0/user/teen_mode_set_state", base_url=base_url)
if isinstance(payload, bool):
payload = {"state": int(payload), "passwd": "0000"}
else:
payload.setdefault("passwd", "0000")
if len(str(passwd := payload.get("passwd"))):
payload["passwd"] = md5(f"{passwd:>04}{self.user_id}62454aa2c6fd4".encode("ascii")).hexdigest()
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def user_vip_check_spw(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_vip_check_spw(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_vip_check_spw(
self,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取用户积分、余额等信息
GET https://proapi.115.com/{app}/vip/check_spw
"""
api = complete_url("/vip/check_spw", base_url=base_url, app=app)
return self.request(url=api, async_=async_, **request_kwargs)
@overload
def user_vip_limit(
self,
payload: int | dict = 2,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def user_vip_limit(
self,
payload: int | dict = 2,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def user_vip_limit(
self,
payload: int | dict = 2,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取 vip 的某些限制
GET https://webapi.115.com/user/vip_limit
:payload:
- feature: int = 2
"""
api = complete_url("/user/vip_limit", base_url=base_url)
if isinstance(payload, int):
payload = {"feature": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
########## User Share API ##########
@overload
def usershare_add_folder(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_add_folder(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_add_folder(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""转存共享文件或目录
POST https://webapi.115.com/usershare/add_folder
:payload:
- share_id: int | str
- pid: int | str
- cname: str
"""
api = complete_url("/usershare/add_folder", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_add_folder_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_add_folder_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_add_folder_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""转存共享文件或目录
POST https://proapi.115.com/{app}/2.0/usershare/add_folder
:payload:
- share_id: int | str
- pid: int | str
- cname: str
"""
api = complete_url("/2.0/usershare/add_folder", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_action(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_action(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_action(
self,
payload: int | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享动态列表
GET https://webapi.115.com/usershare/action
:payload:
- share_id: int | str
- offset: int = 0
- limit: int = 32
"""
api = complete_url("/usershare/action", base_url=base_url)
if isinstance(payload, int):
payload = {"share_id": payload}
payload = {"limit": 32, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_action_app(
self,
payload: int | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_action_app(
self,
payload: int | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_action_app(
self,
payload: int | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享动态列表
GET https://proapi.115.com/{app}/2.0/usershare/action
:payload:
- share_id: int | str
- offset: int = 0
- limit: int = 32
"""
api = complete_url("/2.0/usershare/action", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"share_id": payload}
payload = {"limit": 32, "offset": 0, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_copy(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_copy(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_copy(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""转存文件或目录
POST https://webapi.115.com/usershare/copy
:payload:
- share_id: int | str
- file_ids: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- to_cid: int = 0 💡 转存到你的网盘中的目录 id
- copy_type: 0 | 1 = 0 💡 复制类型:0:共享->网盘 1:网盘->共享
"""
api = complete_url("/usershare/copy", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_copy_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_copy_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_copy_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""转存文件或目录
POST https://proapi.115.com/{app}/2.0/usershare/copy
:payload:
- share_id: int | str
- file_ids: int | str 💡 文件或目录的 id,多个用逗号 "," 隔开
- to_cid: int = 0 💡 转存到你的网盘中的目录 id
- copy_type: 0 | 1 = 0 💡 复制类型:0:共享->网盘 1:网盘->共享
"""
api = complete_url("/2.0/usershare/copy", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_delete(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_delete(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_delete(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除共享文件或目录
POST https://webapi.115.com/usershare/delete
:payload:
- share_id: int | str
- file_ids: int | str 💡 共享中的文件或目录的 id,多个用逗号 "," 隔开
- ignore_warn: 0 | 1 = 0
"""
api = complete_url("/usershare/delete", base_url=base_url)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_delete_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_delete_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_delete_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""删除共享文件或目录
POST https://proapi.115.com/{app}/2.0/usershare/delete
:payload:
- share_id: int | str
- file_ids: int | str 💡 共享中的文件或目录的 id,多个用逗号 "," 隔开
- ignore_warn: 0 | 1 = 0
"""
api = complete_url("/2.0/usershare/delete", base_url=base_url, app=app)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_download_url(
self,
pickcode: str,
/,
share_id: int | str,
strict: bool = True,
user_agent: None | str = None,
app: str = "chrome",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> P115URL:
...
@overload
def usershare_download_url(
self,
pickcode: str,
/,
share_id: int | str,
strict: bool = True,
user_agent: None | str = None,
app: str = "chrome",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, P115URL]:
...
[docs]
def usershare_download_url(
self,
pickcode: str,
/,
share_id: int | str,
strict: bool = True,
user_agent: None | str = None,
app: str = "chrome",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> P115URL | Coroutine[Any, Any, P115URL]:
"""获取共享文件的下载链接
.. note::
获取的直链中,部分查询参数的解释:
- ``t``: 过期时间戳
- ``u``: 用户 id
- ``c``: 允许同时打开次数,如果为 0,则是无限次数
- ``f``: 请求时要求携带请求头
- 如果为空,则无要求
- 如果为 1,则需要 user-agent(和请求直链时的一致)
- 如果为 3,则需要 user-agent(和请求直链时的一致) 和 Cookie(由请求直链时的响应所返回的 Set-Cookie 响应头)
:param pickcode: 提取码
:param share_id: 共享 id
:param strict: 如果为 True,当目标是目录时,会抛出 IsADirectoryError 异常
:param user_agent: 如果不为 None,则作为请求头 "user-agent" 的值
:param app: 使用此设备的接口
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 下载链接
"""
def gen_step():
payload = {"share_id": share_id, "pick_code": pickcode}
if app in ("web", "desktop"):
resp = yield self.usershare_download_url_web(
payload,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["payload"] = payload
resp["is_download"] = True
try:
check_response(resp)
except IsADirectoryError:
if strict:
raise
return P115URL(
resp.get("file_url", ""),
id=int(resp["file_id"]),
pickcode=pickcode,
name=resp["file_name"],
size=int(resp["file_size"]),
is_dir=not resp["state"],
headers=resp["headers"],
)
else:
resp = yield self.download_url_app(
payload,
user_agent=user_agent,
app=app,
async_=async_,
**request_kwargs,
)
resp["payload"] = payload
resp["is_download"] = True
check_response(resp)
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
if "url" in data:
url = data["url"]
return P115URL(
url,
pickcode=pickcode,
name=unquote(urlsplit(url).path.rsplit("/", 1)[-1]),
is_dir=False,
headers=resp["headers"],
)
for fid, info in data.items():
url = info["url"]
if strict and not url:
throw(
errno.EISDIR,
f"{fid} is a directory, with response {resp}",
)
return P115URL(
url["url"] if url else "",
id=int(fid),
pickcode=info["pick_code"],
name=info["file_name"],
size=int(info["file_size"]),
sha1=info["sha1"],
is_dir=not url,
headers=resp["headers"],
)
throw(
errno.ENOENT,
f"no such pickcode: {pickcode!r}, with response {resp}",
)
return run_gen_step(gen_step, async_)
@overload
def usershare_download_urls(
self,
pickcodes: str | Iterable[str],
/,
share_id: int | str,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict[int, P115URL]:
...
@overload
def usershare_download_urls(
self,
pickcodes: str | Iterable[str],
/,
share_id: int | str,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict[int, P115URL]]:
...
[docs]
def usershare_download_urls(
self,
pickcodes: str | Iterable[str],
/,
share_id: int | str,
strict: bool = True,
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict[int, P115URL] | Coroutine[Any, Any, dict[int, P115URL]]:
"""批量获取共享文件的下载链接
.. note::
获取的直链中,部分查询参数的解释:
- ``t``: 过期时间戳
- ``u``: 用户 id
- ``c``: 允许同时打开次数,如果为 0,则是无限次数
- ``f``: 请求时要求携带请求头
- 如果为空,则无要求
- 如果为 1,则需要 user-agent(和请求直链时的一致)
- 如果为 3,则需要 user-agent(和请求直链时的一致) 和 Cookie(由请求直链时的响应所返回的 Set-Cookie 响应头)
:param pickcodes: 提取码,多个用逗号 "," 隔开
:param share_id: 共享 id
:param strict: 如果为 True,当目标是目录时,会直接忽略
:param user_agent: 如果不为 None,则作为请求头 "user-agent" 的值
:param async_: 是否异步
:param request_kwargs: 其余请求参数
:return: 一批下载链接
"""
if not isinstance(pickcodes, str):
pickcodes = ",".join(pickcodes)
def gen_step():
payload = {"pickcode": pickcodes, "share_id": share_id}
resp = yield self.download_url_app(
payload,
user_agent=user_agent,
async_=async_,
**request_kwargs,
)
resp["payload"] = payload
resp["is_download"] = True
data = resp.get("data")
if not data:
resp["state"] = False
resp["errno"] = resp.get("errno") or 50015
resp.setdefault("message", "文件不存在、是目录或者不支持此操作")
urls: dict[int, P115URL] = {}
if resp.get("errno") != 50003:
check_response(resp)
for fid, info in data.items():
url = info["url"]
if strict and not url:
continue
fid = int(fid)
urls[fid] = P115URL(
url["url"] if url else "",
id=fid,
pickcode=info["pick_code"],
name=info["file_name"],
size=int(info["file_size"]),
sha1=info["sha1"],
is_dir=not url,
headers=resp["headers"],
)
return urls
return run_gen_step(gen_step, async_)
@overload
def usershare_download_url_web(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_download_url_web(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_download_url_web(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
user_agent: None | str = None,
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取文件的下载链接(网页版接口)
POST https://webapi.115.com/usershare/download
.. note::
下载的文件大小没有 200 MB 的限制
:payload:
- share_id: int | str
- pick_code: str
- dl: int = 0
"""
api = complete_url("/usershare/download", base_url=base_url)
headers = request_kwargs["headers"] = dict(request_kwargs.get("headers") or ())
if user_agent is None:
headers.setdefault("user-agent", "")
else:
headers["user-agent"] = user_agent
def parse(resp, content: bytes, /) -> dict:
json = json_maybe_decrypt_loads(content)
if "Set-Cookie" in resp.headers:
if isinstance(resp.headers, Mapping):
match = CRE_SET_COOKIE.search(resp.headers["Set-Cookie"])
if match is not None:
headers["cookie"] = match[0]
else:
for k, v in reversed(resp.headers.items()):
if k == "Set-Cookie" and CRE_SET_COOKIE.match(v) is not None:
headers["cookie"] = v
break
json["headers"] = headers
return json
request_kwargs.setdefault("parse", parse)
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_file_list(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_file_list(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_file_list(
self,
payload: dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""共享目录中的文件列表
GET https://webapi.115.com/usershare/filelist
:payload:
- share_id: int | str
- cid: int | str 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,目前最大值是 1,150,以前是没限制的
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数,好像也可以写成 ``countfolders``
- cur: 0 | 1 = <default> 💡 是否只搜索当前目录
- custom_order: 0 | 1 = <default> 💡 启用自定义排序,如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 1
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- show_dir: 0 | 1 = 1 💡 是否显示目录,好像也可以写成 showdir
- star: 0 | 1 = <default> 💡 是否星标文件
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- suffix_type: int = <default>
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-11: 大概相当于 1
- 12: 文档+图片+视频,相当于 1、2、4
- 13: ???,音频
- 14: ???,文档
- 15: 图片+视频,相当于 2、4
- 16: 字幕
- 17~98: 大概相当于 1
- 99: 所有文件
- >=100: 大概相当于 1
"""
api = complete_url("/usershare/filelist", base_url=base_url)
payload = {"limit": 32, "offset": 0, "show_dir": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_file_list_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_file_list_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_file_list_app(
self,
payload: dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""共享目录中的文件列表
GET https://proapi.115.com/{app}/2.0/usershare/filelist
:payload:
- share_id: int | str
- cid: int | str 💡 目录 id,对应 parent_id
- limit: int = 32 💡 分页大小,目前最大值是 1,150,以前是没限制的
- offset: int = 0 💡 分页开始的索引,索引从 0 开始计算
- asc: 0 | 1 = <default> 💡 是否升序排列。0:降序 1:升序
- count_folders: 0 | 1 = 1 💡 统计文件数和目录数,好像也可以写成 ``countfolders``
- cur: 0 | 1 = <default> 💡 是否只搜索当前目录
- custom_order: 0 | 1 = <default> 💡 启用自定义排序,如果指定了 "asc"、"fc_mix"、"o" 中其一,则此参数会被自动设置为 1
- 0: 使用记忆排序(自定义排序失效)
- 1: 使用自定义排序(不使用记忆排序)
- 2: 自定义排序(非目录置顶)
- fc_mix: 0 | 1 = <default> 💡 是否目录和文件混合,如果为 0 则目录在前(目录置顶)
- nf: 0 | 1 = <default> 💡 不要显示文件(即仅显示目录),但如果 show_dir=0,则此参数无效
- o: str = <default> 💡 用某字段排序
- "file_name": 文件名
- "file_size": 文件大小
- "file_type": 文件种类
- "user_utime": 修改时间
- "user_ptime": 创建时间
- "user_otime": 上一次打开时间
- record_open_time: 0 | 1 = 1 💡 是否要记录目录的打开时间
- show_dir: 0 | 1 = 1 💡 是否显示目录,好像也可以写成 showdir
- star: 0 | 1 = <default> 💡 是否星标文件
- suffix: str = <default> 💡 后缀名(优先级高于 `type`)
- suffix_type: int = <default>
- type: int = <default> 💡 文件类型
- 0: 全部(仅当前目录)
- 1: 文档
- 2: 图片
- 3: 音频
- 4: 视频
- 5: 压缩包
- 6: 软件/应用
- 7: 书籍
- 8-11: 大概相当于 1
- 12: 文档+图片+视频,相当于 1、2、4
- 13: ???,音频
- 14: 大概相当于 1
- 15: 图片+视频,相当于 2、4
- >= 16: 相当于 8
"""
api = complete_url("/2.0/usershare/filelist", base_url=base_url, app=app)
payload = {"limit": 32, "offset": 0, "show_dir": 1, **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_info(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享信息
GET https://webapi.115.com/usershare/shareinfo
:payload:
- share_id: int | str
"""
api = complete_url("/usershare/shareinfo", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"share_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_info_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_info_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_info_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享信息
GET https://proapi.115.com/{app}/2.0/usershare/shareinfo
:payload:
- share_id: int | str
"""
api = complete_url("/2.0/usershare/shareinfo", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"share_id": payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_invite(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_invite(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_invite(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享链接
POST https://webapi.115.com/usershare/invite
:payload:
- share_id: int | str
"""
api = complete_url("/usershare/invite", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"share_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_invite_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_invite_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_invite_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""获取共享链接
POST https://proapi.115.com/{app}/2.0/usershare/invite
:payload:
- share_id: int | str
"""
api = complete_url("/2.0/usershare/invite", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"share_id": payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_list(
self,
payload: int | str | dict = 0,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""共享列表
GET https://webapi.115.com/usershare/list
:payload:
- offset: int = 0
- limit: int = 1150
- type: "all" | "others" | "mine" = "all" 💡 类型:all:全部共享 others:他人共享 mine:我共享的
"""
api = complete_url("/usershare/list", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, "type": "all", **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_list_app(
self,
payload: int | str | dict = 0,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""共享列表
GET https://proapi.115.com/{app}/2.0/usershare/list
:payload:
- offset: int = 0
- limit: int = 1150
- type: "all" | "others" | "mine" = "all" 💡 类型:all:全部共享 others:他人共享 mine:我共享的
"""
api = complete_url("/2.0/usershare/list", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"offset": payload}
payload = {"limit": 1150, "offset": 0, "type": "all", **payload}
return self.request(url=api, params=payload, async_=async_, **request_kwargs)
@overload
def usershare_member(
self,
payload: int | dict,
/,
method: str = "POST",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_member(
self,
payload: int | dict,
/,
method: str = "POST",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_member(
self,
payload: int | dict,
/,
method: str = "POST",
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""某共享的成员信息
POST https://webapi.115.com/usershare/member
:payload:
- share_id: int | str
- action: str = "member_list"
- "member_list": 用户列表
- "member_info": 用户信息
- "noticeset": 设置通知
- "priviset": 设置权限(共享拥有者用于对待他人)
- "remove": 移除成员
- "join": 加入邀请
- "quit": 退出共享
- notice_set: 0 | 1 = <default> 💡 是否开启通知,``action`` 为 "noticeset" 时设置
- privi_set[{user_id}]: int | str = <default> 💡 设置权限,``action`` 为 "priviset" 时设置,多个用逗号 "," 隔开
- 2: 上传
- 8: 转存/下载
- member_uid: int | str = <default> 💡 用户 id,``action`` 为 "remove" 时设置
- delete_file: 0 | 1 = <default> 💡 是否同时删除他共享的文件,``action`` 为 "remove" 或 "quit" 时设置
- invite_code: str = <default> 💡 分享码,``action`` 为 "join" 时设置
"""
api = complete_url("/usershare/member", base_url=base_url)
if isinstance(payload, int):
payload = {"share_id": payload}
payload.setdefault("action", "member_list")
return self.request(url=api, method=method, payload=payload, async_=async_, **request_kwargs)
@overload
def usershare_member_app(
self,
payload: int | dict,
/,
method: str = "POST",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_member_app(
self,
payload: int | dict,
/,
method: str = "POST",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_member_app(
self,
payload: int | dict,
/,
method: str = "POST",
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""某共享的成员信息
POST https://proapi.115.com/{app}/2.0/usershare/member
:payload:
- share_id: int | str
- action: str = "member_list"
- "member_list": 用户列表
- "member_info": 用户信息
- "noticeset": 设置通知
- "priviset": 设置权限(共享拥有者用于对待他人)
- "remove": 移除成员
- "join": 加入邀请
- "quit": 退出共享
- notice_set: 0 | 1 = <default> 💡 是否开启通知,``action`` 为 "noticeset" 时设置
- privi_set[{user_id}]: int | str = <default> 💡 设置权限,``action`` 为 "priviset" 时设置,多个用逗号 "," 隔开
- 2: 上传
- 8: 转存/下载
- member_uid: int | str = <default> 💡 用户 id,``action`` 为 "remove" 时设置
- delete_file: 0 | 1 = <default> 💡 是否同时删除他共享的文件,``action`` 为 "remove" 或 "quit" 时设置
- invite_code: str = <default> 💡 分享码,``action`` 为 "join" 时设置
"""
api = complete_url("/2.0/usershare/member", base_url=base_url, app=app)
if isinstance(payload, int):
payload = {"share_id": payload}
payload.setdefault("action", "member_list")
return self.request(url=api, method=method, payload=payload, async_=async_, **request_kwargs)
@overload
def usershare_share(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_share(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_share(
self,
payload: int | str | dict,
/,
base_url: str | Callable[[], str] = "https://webapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置共享
POST https://webapi.115.com/usershare/share
:payload:
- file_id: int | str 💡 文件或目录的 id
- share_opt: 1 | 2 = 1 💡 1: 设置 2: 取消
- ignore_warn: 0 | 1 = 0
- safe_pwd: str = ""
"""
api = complete_url("/usershare/share", base_url=base_url)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload = {"ignore_warn": 0, "share_opt": 1, "safe_pwd": "", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
@overload
def usershare_share_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False] = False,
**request_kwargs,
) -> dict:
...
@overload
def usershare_share_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[True],
**request_kwargs,
) -> Coroutine[Any, Any, dict]:
...
[docs]
def usershare_share_app(
self,
payload: int | str | dict,
/,
app: str = "android",
base_url: str | Callable[[], str] = "https://proapi.115.com",
*,
async_: Literal[False, True] = False,
**request_kwargs,
) -> dict | Coroutine[Any, Any, dict]:
"""设置共享
POST https://proapi.115.com/{app}/2.0/usershare/share
:payload:
- file_id: int | str 💡 文件或目录的 id
- share_opt: 1 | 2 = 1 💡 1: 设置 2: 取消
- ignore_warn: 0 | 1 = 0
- safe_pwd: str = ""
"""
api = complete_url("/2.0/usershare/share", base_url=base_url, app=app)
if isinstance(payload, (int, str)):
payload = {"file_id": payload}
payload = {"ignore_warn": 0, "share_opt": 1, "safe_pwd": "", **payload}
return self.request(url=api, method="POST", data=payload, async_=async_, **request_kwargs)
########## Extension API ##########
@overload
def get_fs(
self,
arg: None = None,
/,
refresh: bool = False,
id_to_readdir: None | int | dict[int, dict[int, MutableMapping]] = None,
) -> P115FileSystem:
...
@overload
def get_fs(
self,
arg: int,
/,
refresh: bool = False,
id_to_readdir: None | int | dict[int, dict[int, MutableMapping]] = None,
) -> P115ZipFileSystem:
...
@overload
def get_fs(
self,
arg: str,
/,
refresh: bool = False,
id_to_readdir: None | int | dict[int, dict[int, MutableMapping]] = None,
) -> P115ShareFileSystem:
...
def get_fs(
self,
arg: None | int | str = None,
/,
refresh: bool = False,
id_to_readdir: None | int | dict[int, dict[int, MutableMapping]] = None,
) -> P115FileSystemBase:
if arg is None:
return P115FileSystem(self, refresh=refresh, id_to_readdir=id_to_readdir)
elif isinstance(arg, int):
return P115ZipFileSystem(self, self.to_pickcode(arg), refresh=refresh, id_to_readdir=id_to_readdir)
else:
return P115ShareFileSystem(self, arg, refresh=refresh, id_to_readdir=id_to_readdir)
with temp_globals():
CRE_CLIENT_API_search: Final = re_compile(r"^ *((?:GET|POST|PUT|DELETE|PATCH) +.+)", MULTILINE).search
for name in dir(P115Client):
method = getattr(P115Client, name)
if not (callable(method) and method.__doc__):
continue
if match := CRE_CLIENT_API_search(method.__doc__):
api = match[1]
name = "P115Client." + name
CLIENT_METHOD_API_MAP[name] = api
try:
CLIENT_API_METHODS_MAP[api].append(name)
except KeyError:
CLIENT_API_METHODS_MAP[api] = [name]
from .fs import P115FileSystemBase, P115FileSystem, P115ShareFileSystem, P115ZipFileSystem
# TODO: 支持对接口调用进行频率统计,默认就会开启,配置项目:1. 允许记录多少条或者多大时间窗口,默认记录最近 10 条(无限时间窗口) 2. 可以设置一个 key 函数,默认用 (url, method) 为 key 3. 数据和统计由单独的对象来承载,就行 headers 和 cookies 属性那样,可以被随意查看,这个对象由各种配置项目,可以随意修改,client初始化时候支持传入此对象 4. 可以修改时间窗口和数量限制 5. 可以获取数据,就像字典一样使用 dict[key, list[timestamp]] 6. 有一些做好的统计方法,你也可以自己来执行统计 7. 即使有些历史数据被移除,有些统计方法可以持续更新,覆盖从早到现在的所有数据,比如 加总、计数
# TODO: 有些方法需要被移走,例如 open, hash, ed2k 等,这些方法完全可以单独使用,没必要专门给 client 提供,client 类必须是必要的,非必要的方法一律移除
# TODO: 增加一个 __eq__ 方法,只要 user_id 相等即可
# TODO: 删除、复制、移动、还原似乎是不可同时进行的?
# TODO: 把 refresh_token 保存到 cookies 里面,读取时候,也要设置 refresh_token,如果有 refresh_token,保存 cookies 时也要保存它
# TODO: 执行请求时,不要携带无关的 cookies,必须根据 url 认真确定,且对于会过期的 cookie 真正移除
# TODO: cookies.txt 支持多种格式,不仅仅 "key=val;",还支持 json 等等
# TODO: 为每个接口每次调用的时间进行记录,request 支持 cooldown 参数