add many new features

This commit is contained in:
rachpt 2020-05-18 23:05:25 +08:00
parent d9521cbab6
commit 987106bb26
14 changed files with 1355 additions and 341 deletions

138
.gitignore vendored Normal file
View File

@ -0,0 +1,138 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# other
.config
.directory
.idea/
*.log
/downloads
/bak

316
189.py
View File

@ -1,316 +0,0 @@
import base64
import hashlib
import json
import os
import re
import sys
import time
import requests
import rsa
from requests_toolbelt.multipart.encoder import MultipartEncoder
session = requests.session()
session.headers.update({
'Referer': 'https://open.e.189.cn/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
})
RSA_KEY = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDY7mpaUysvgQkbp0iIn2ezoUyh
i1zPFn0HCXloLFWT7uoNkqtrphpQ/63LEcPz1VYzmDuDIf3iGxQKzeoHTiVMSmW6
FlhDeqVOG094hFJvZeK4OzA6HVwzwnEW5vIZ7d+u61RV1bsFxmB68+8JXs3ycGcE
4anY+YzZJcyOcEGKVQIDAQAB
-----END PUBLIC KEY-----
"""
def encrypt(password: str) -> str:
return base64.b64encode(
rsa.encrypt(
(password).encode('utf-8'),
rsa.PublicKey.load_pkcs1_openssl_pem(RSA_KEY.encode())
)
).decode()
b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
BI_RM = list("0123456789abcdefghijklmnopqrstuvwxyz")
def int2char(a):
return BI_RM[a]
def b64tohex(a):
d = ""
e = 0
for i in range(len(a)):
if list(a)[i] != "=":
v = b64map.index(list(a)[i])
if 0 == e:
e = 1
d += int2char(v >> 2)
c = 3 & v
elif 1 == e:
e = 2
d += int2char(c << 2 | v >> 4)
c = 15 & v
elif 2 == e:
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
else:
e = 0
d += int2char(c << 2 | v >> 4)
d += int2char(15 & v)
if e == 1:
d += int2char(c << 2)
# print(d)
return d
def redirect():
r = session.get("https://cloud.189.cn/udb/udb_login.jsp?pageId=1&redirectURL=/main.action")
captchaToken = re.findall(r"captchaToken' value='(.+?)'", r.text)[0]
lt = re.findall(r'lt = "(.+?)"', r.text)[0]
returnUrl = re.findall(r"returnUrl = '(.+?)'", r.text)[0]
paramId = re.findall(r'paramId = "(.+?)"', r.text)[0]
session.headers.update({"lt": lt})
return captchaToken, returnUrl, paramId
def md5(s):
hl = hashlib.md5()
hl.update(s.encode(encoding='utf-8'))
return hl.hexdigest()
def needcaptcha(captchaToken):
r = session.post(
url="https://open.e.189.cn/api/logbox/oauth2/needcaptcha.do",
data={
"accountType": "01",
"userName": "{RSA}" + b64tohex(encrypt(username)),
"appKey": "cloud"
}
)
if r.text == "0":
print("DONT NEED CAPTCHA")
return ""
else:
print("NEED CAPTCHA")
r = session.get(
url="https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do",
params={"token": captchaToken}
)
with open("./captcha.png", "wb") as f:
f.write(r.content)
f.close()
return input("验证码下载完成,打开 ./captcha.png 查看: ")
def save_cookie(username: str):
with open(f"./.{username}", mode="w") as f:
json.dump(session.cookies.get_dict(), f, indent=2)
f.close()
def load_cookie(username: str):
cookie_file = f"./.{username}"
if os.path.exists(cookie_file):
with open(cookie_file, mode="r") as f:
cookie_dict = json.loads(f.read())
f.close()
[session.cookies.set(k, v, domain=".cloud.189.cn") for k, v in cookie_dict.items()]
r = session.get("https://cloud.189.cn/v2/getUserLevelInfo.action")
if "InvalidSessionKey" not in r.text: return True
return False
def login():
if load_cookie(username):
return
captchaToken, returnUrl, paramId = redirect()
validateCode = needcaptcha(captchaToken)
r = session.post(
url="https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do",
data={
"appKey": "cloud",
"accountType": '01',
"userName": "{RSA}" + b64tohex(encrypt(username)),
"password": "{RSA}" + b64tohex(encrypt(password)),
"validateCode": validateCode,
"captchaToken": captchaToken,
"returnUrl": returnUrl,
"mailSuffix": "@189.cn",
"paramId": paramId
}
)
msg = r.json()["msg"]
if "登录成功" == msg:
session.get(r.json()["toUrl"])
save_cookie(username)
else:
print(msg)
os._exit(0)
def get_file_size_str(filesize: int) -> str:
if 0 < filesize < 1024**2:
return f"{round(filesize/1024, 2)}KB"
elif 1024**2 < filesize < 1024**3:
return f"{round(filesize/1024**2, 2)}MB"
elif 1024**3 < filesize < 1024**4:
return f"{round(filesize/1024**3, 2)}GB"
elif 1024**4 < filesize < 1024**5:
return f"{round(filesize/1024**4, 2)}TB"
else: return f"{filesize}Bytes"
def share_file(fileId):
expireTime_dict = {
"1": "1",
"2": "7",
"3": "2099"
}
expireTime = input("请选择分享有效期1、1天2、7天3、永久")
withAccessCode = input("请选择分享形式1、私密分享2、公开分享")
if withAccessCode == "1":
url = "https://cloud.189.cn/v2/privateLinkShare.action"
params = {
"fileId": fileId,
"expireTime": expireTime_dict[expireTime],
"withAccessCode": withAccessCode
}
else:
url = "https://cloud.189.cn/v2/createOutLinkShare.action"
params = {
"fileId": fileId,
"expireTime": expireTime_dict[expireTime]
}
r = session.get(url=url, params=params).json()
msg = f"链接:{r['shortShareUrl']} "
msg += "" if not r.get("accessCode") else f"访问码:{r['accessCode']}"
print(msg)
def get_files():
r = session.get(
url="https://cloud.189.cn/v2/listFiles.action",
params={
"fileId": "-11", # 根目录
"inGroupSpace": "false",
"orderBy": "1",
"order": "ASC",
"pageNum": "1",
"pageSize": "60"
}
).json()
for file in r["data"]:
folder_or_file = "文件夹: " if file["isFolder"] else f"大小: {get_file_size_str(file['fileSize'])} 文件名: "
filename = file["fileName"]
print(f"{folder_or_file}{filename} {'' if file['isFolder'] else '文件ID: ' + file['fileId']}")
def upload(filePath):
session.headers["Referer"] = "https://cloud.189.cn"
def get_upload_url():
while True:
try:
r = session.post("https://cloud.189.cn/v2/getUserUploadUrl.action")
return "https:" + r.json()["uploadUrl"]
except:
login()
def get_session_key():
r = session.get(
url="https://cloud.189.cn/main.action",
headers={"Host": "cloud.189.cn"}
)
sessionKey = re.findall(r"sessionKey = '(.+?)'", r.text)[0]
return sessionKey
def upload_file():
filename = os.path.basename(filePath)
filesize = os.path.getsize(filePath)
print(f"正在上传: {filename} 大小: {get_file_size_str(filesize)}")
upload_url = get_upload_url()
multipart_data = MultipartEncoder(
fields={
"sessionKey": get_session_key(),
"parentId": "-11", # 上传文件夹 根目录
"albumId": "undefined",
"opertype": "1",
"fname": filename,
'file': (filename, open(filePath, 'rb'), 'application/octet-stream')
}
)
r = session.post(
url=upload_url,
data=multipart_data,
headers={
"Content-Type": multipart_data.content_type
}
).json()
print(f"上传完毕文件ID{r['id']} 上传时间: {r['createDate']}")
upload_file()
def task(task_type, fileid):
taskInfos = []
def get_file_info(download=False):
r = session.get(f"https://cloud.189.cn/v2/getFileInfo.action?fileId={fileid}").json()
filename = r["fileName"]
if download:
print(f"开始下载: {filename} 大小: {get_file_size_str(r['fileSize'])}")
return "https:"+r["downloadUrl"], filename
taskInfo = {}
taskInfo["fileId"] = fileid
taskInfo["srcParentId"] = "-11" # 根目录
taskInfo["fileName"] = filename
taskInfo["isFolder"] = 1 if r["isFolder"] else 0
taskInfos.append(taskInfo)
def create_batch_task(action):
r = session.post(
url="https://cloud.189.cn/createBatchTask.action",
data={
"type": action,
"taskInfos": json.dumps(taskInfos)
}
)
if r.text:
print("删除成功!")
def download(url, filename, filepath="./"):
print(f"下载链接:{url}")
if input("需要继续下载吗? 1、继续下载2、取消下载") == "1":
r = session.get(url, stream=True)
with open(filepath+filename, "wb") as f:
for chunk in r.iter_content(chunk_size=1024**2):
f.write(chunk)
f.close()
print(f"{filename} 下载完成!")
if task_type == "delete":
get_file_info()
create_batch_task("DELETE")
elif task_type == "download":
url, filename = get_file_info(True)
download(url, filename)
if __name__ == "__main__":
username = ""
password = ""
login()
try:
if sys.argv[1] == "upload":
if os.path.isdir(sys.argv[2]):
[upload(f"{os.path.abspath(sys.argv[2])}/{file}")
for file in os.listdir(sys.argv[2])]
elif os.path.isfile(sys.argv[2]):
upload(sys.argv[2])
else:
print("未知类型文件")
elif sys.argv[1] in ["delete", "download"]:
task(sys.argv[1], int(sys.argv[2]))
elif sys.argv[1] == "list":
get_files()
elif sys.argv[1] == "share":
share_file(int(sys.argv[2]))
except IndexError:
print("请输入正确的参数:\n \
upload [filename]: 上传文件\n \
download [file id]: 下载文件\n \
list: 列出根目录文件及文件夹\n \
share: 分享文件\n \
delete [file id]: 删除文件")
except ValueError:
print("文件ID输入错误")

View File

@ -1,39 +1,45 @@
# Cloud189 CLI
![alt cloud189](./src/view.png "使用图片")
<h3 align="center">- cloud189-cli -</h3>
### 一:准备
1. Python 版本 >= 3.6
2. 安装依赖
~~~shell
```sh
pip install -r requirements.txt
~~~
```
3. 配置
打开``189.py``, 找到以下两行
~~~python
username = ""
password = ""
~~~
修改为自己的天翼云盘手机号和密码即可
运行 ``python cloud189-cli.py``, 输入用户名与密码
账号为自己的天翼云盘手机号,密码不会有回显。
也可以 自接两次回车后,输入 `clogin` 按提示输入 `cookie` 登录。
### 二:使用
0. 不加参数者进入交互模式
```sh
python cloud189-cli.py # 提示符为 >
> bye# 退出
```
1. 查看**根目录**的文件
~~~shell
python 189.py list
~~~
```sh
python cloud189-cli.py ls
```
2. 上传文件至**根目录**
~~~shell
python 189.py upload 文件
~~~
```sh
python cloud189-cli.py upload '文件路径'
```
3. 下载**根目录**的文件
~~~shell
python 189.py download 文件ID # 文件ID 第一步看
~~~
```sh
python cloud189-cli.py down 文件ID # 文件ID 第一步看
```
4. 分享**根目录**的文件
~~~shell
python 189.py share 文件ID # 文件ID 第一步看
~~~
```sh
python cloud189-cli.py share 文件ID # 文件ID 第一步看
```
5. 删除**根目录**的文件
~~~shell
python 189.py delete 文件ID # 文件ID 第一步看
~~~
```sh
python cloud189-cli.py delete 文件ID # 文件ID 第一步看
```
### 三:免责
您使用本工具做的任何事情都雨我无瓜。

24
cloud189-cli.py Normal file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
import sys
from cloud189.cli.cli import Commander
from cloud189.cli.utils import set_console_style, print_logo, check_update, error
if __name__ == '__main__':
set_console_style()
# check_update()
commander = Commander()
commander.login()
if len(sys.argv) >= 2:
cmd, arg = (sys.argv[1], '') if len(sys.argv) == 2 else (sys.argv[1], sys.argv[2])
commander.run_one(cmd, arg)
else:
print_logo()
while True:
try:
commander.run()
except KeyboardInterrupt:
pass
except Exception as e:
error(e)

1
cloud189/__init__.py Normal file
View File

@ -0,0 +1 @@
__all__ = ['api', 'cli']

5
cloud189/api/__init__.py Normal file
View File

@ -0,0 +1,5 @@
from cloud189.api.core import Cloud189
version = '0.0.1'
__all__ = ['utils', 'Cloud189', 'version']

263
cloud189/api/core.py Normal file
View File

@ -0,0 +1,263 @@
"""
天翼云盘 API封装了对天翼云的各种操作解除了上传格式大小限制
"""
import os
import re
import json
import requests
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning
from cloud189.api.utils import *
__all__ = ['Cloud189']
class Cloud189(object):
FAILED = -1
SUCCESS = 0
ID_ERROR = 1
PASSWORD_ERROR = 2
LACK_PASSWORD = 3
ZIP_ERROR = 4
MKDIR_ERROR = 5
URL_INVALID = 6
FILE_CANCELLED = 7
PATH_ERROR = 8
NETWORK_ERROR = 9
CAPTCHA_ERROR = 10
def __init__(self):
self._session = requests.Session()
self._captcha_handler = None
self._timeout = 15 # 每个请求的超时(不包含下载响应体的用时)
self._host_url = 'https://cloud.189.cn'
self._auth_url = 'https://open.e.189.cn/api/logbox/oauth2/'
self._cookies = None
self._headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0',
'Referer': 'https://open.e.189.cn/',
}
disable_warnings(InsecureRequestWarning) # 全局禁用 SSL 警告
def _get(self, url, **kwargs):
try:
kwargs.setdefault('timeout', self._timeout)
kwargs.setdefault('headers', self._headers)
return self._session.get(url, verify=False, **kwargs)
except requests.Timeout:
logger.warning("Encountered timeout error while requesting network!")
raise TimeoutError
except (requests.RequestException, Exception) as e:
logger.error(f"Unexpected error: {e=}")
def _post(self, url, data, **kwargs):
try:
kwargs.setdefault('timeout', self._timeout)
kwargs.setdefault('headers', self._headers)
return self._session.post(url, data, verify=False, **kwargs)
except requests.Timeout:
logger.warning("Encountered timeout error while requesting network!")
raise TimeoutError
except (requests.RequestException, Exception) as e:
logger.error(f"Unexpected error: {e=}")
def get_cookie(self):
return self._session.cookies.get_dict()
def login_by_cookie(self, cookies: dict):
try:
for k, v in cookies.items():
self._session.cookies.set(k, v, domain=".cloud.189.cn")
r = self._get(self._host_url + "/v2/getUserLevelInfo.action")
if "InvalidSessionKey" not in r.text:
return Cloud189.SUCCESS
except: pass
return Cloud189.FAILED
def login(self, username, password):
captchaToken, returnUrl, paramId = self.redirect()
validateCode = self.needcaptcha(captchaToken, username)
url = self._auth_url + "loginSubmit.do"
data = {
"appKey": "cloud",
"accountType": '01',
"userName": "{RSA}" + b64tohex(encrypt(username)),
"password": "{RSA}" + b64tohex(encrypt(password)),
"validateCode": validateCode,
"captchaToken": captchaToken,
"returnUrl": returnUrl,
"mailSuffix": "@189.cn",
"paramId": paramId
}
r = self._post(url, data=data)
msg = r.json()["msg"]
if msg == "登录成功":
self._get(r.json()["toUrl"])
return Cloud189.SUCCESS
print(msg)
return Cloud189.FAILED
def redirect(self):
r = self._get(self._host_url + "/udb/udb_login.jsp?pageId=1&redirectURL=/main.action")
captchaToken = re.findall(r"captchaToken' value='(.+?)'", r.text)[0]
lt = re.findall(r'lt = "(.+?)"', r.text)[0]
returnUrl = re.findall(r"returnUrl = '(.+?)'", r.text)[0]
paramId = re.findall(r'paramId = "(.+?)"', r.text)[0]
self._session.headers.update({"lt": lt})
return captchaToken, returnUrl, paramId
def needcaptcha(self, captchaToken, username):
r = self._post(
url= self._auth_url + "needcaptcha.do",
data={
"accountType": "01",
"userName": "{RSA}" + b64tohex(encrypt(username)),
"appKey": "cloud"
}
)
if r.text == "0":
print("DONT NEED CAPTCHA")
return ""
else:
print("NEED CAPTCHA")
r = self._get(
url= self._auth_url + "picCaptcha.do",
params={"token": captchaToken}
)
with open("./captcha.png", "wb") as f:
f.write(r.content)
f.close()
return input("验证码下载完成,打开 ./captcha.png 查看: ")
def get_file_size_str(self, filesize: int) -> str:
if 0 < filesize < 1024**2:
return f"{round(filesize/1024, 2)}KB"
elif 1024**2 < filesize < 1024**3:
return f"{round(filesize/1024**2, 2)}MB"
elif 1024**3 < filesize < 1024**4:
return f"{round(filesize/1024**3, 2)}GB"
elif 1024**4 < filesize < 1024**5:
return f"{round(filesize/1024**4, 2)}TB"
else: return f"{filesize}Bytes"
def share_file(self, fid):
expireTime_dict = {
"1": "1",
"2": "7",
"3": "2099"
}
expireTime = input("请选择分享有效期1、1天2、7天3、永久")
withAccessCode = input("请选择分享形式1、私密分享2、公开分享")
if withAccessCode == "1":
url = self._host_url + "/v2/privateLinkShare.action"
params = {
"fileId": fid,
"expireTime": expireTime_dict[expireTime],
"withAccessCode": withAccessCode
}
else:
url = self._host_url + "/v2/createOutLinkShare.action"
params = {
"fileId": fid,
"expireTime": expireTime_dict[expireTime]
}
r = self._get(url=url, params=params).json()
msg = f"链接:{r['shortShareUrl']} "
msg += "" if not r.get("accessCode") else f"访问码:{r['accessCode']}"
print(msg)
def get_files(self):
url = self._host_url + "/v2/listFiles.action"
params = {
"fileId": "-11", # 根目录
"inGroupSpace": "false",
"orderBy": "1",
"order": "ASC",
"pageNum": "1",
"pageSize": "60"
}
r = self._get(url, params=params).json()
for file in r["data"]:
folder_or_file = "文件夹: " if file["isFolder"] else f"大小: {self.get_file_size_str(file['fileSize'])} 文件名: "
filename = file["fileName"]
print(f"{folder_or_file}{filename} {'' if file['isFolder'] else '文件ID: ' + file['fileId']}")
def upload(self, filePath):
self._session.headers["Referer"] = self._host_url
def get_upload_url():
r = self._get(self._host_url + "/v2/getUserUploadUrl.action")
# print(r.text)
return "https:" + r.json()["uploadUrl"]
def get_session_key():
r = self._get(
url=self._host_url + "/main.action",
headers={"Host": "cloud.189.cn"}
)
sessionKey = re.findall(r"sessionKey = '(.+?)'", r.text)[0]
return sessionKey
def upload_file():
filename = os.path.basename(filePath)
filesize = os.path.getsize(filePath)
print(f"正在上传: {filename} 大小: {self.get_file_size_str(filesize)}")
upload_url = get_upload_url()
multipart_data = MultipartEncoder(
fields={
"sessionKey": get_session_key(),
"parentId": "-11", # 上传文件夹 根目录
"albumId": "undefined",
"opertype": "1",
"fname": filename,
'file': (filename, open(filePath, 'rb'), 'application/octet-stream')
}
)
headers = {"Content-Type": multipart_data.content_type}
r = self._post(url=upload_url, data=multipart_data, headers=headers).json()
print(f"上传完毕文件ID{r['id']} 上传时间: {r['createDate']}")
upload_file()
def get_file_info_by_id(self, fid):
infos = {}
r = self._get(self._host_url + f"/v2/getFileInfo.action?fileId={fid}").json()
# print(r)
infos["fname"] = r["fileName"]
infos["fid"] = fid
infos["size"] = r['fileSize']
infos["srcParentId"] = "-11" # 根目录
infos["isfolder"] = True if r["isFolder"] else False
infos["durl"] = "https:"+r["downloadUrl"]
return infos
def download_by_id(self, fid, save_path='./downloads'):
infos = self.get_file_info_by_id(fid)
if infos:
if not os.path.isdir(save_path):
os.mkdir(save_path)
r = self._get(infos['durl'], stream=True)
save_path = save_path + os.sep + infos['fname']
with open(save_path, "wb") as f:
for chunk in r.iter_content(chunk_size=1024**2):
f.write(chunk)
f.close()
print(f"{infos['fname']} 下载完成!")
def delete_by_id(self, fid):
infos = self.get_file_info_by_id(fid)
taskInfo = {"fileId": infos["fid"],
"srcParentId": "-11", # 根目录
"fileName": infos["fname"],
"isFolder": 1 if infos["isfolder"] else 0 }
url = self._host_url + "/createBatchTask.action"
post_data = { "type": "DELETE", "taskInfos": json.dumps([taskInfo,])}
r = self._post(url, data=post_data)
if r.text:
print("删除成功!")

77
cloud189/api/utils.py Normal file
View File

@ -0,0 +1,77 @@
"""
API 处理网页数据数据切片时使用的工具
"""
import logging
import hashlib
import rsa
from base64 import b64encode
__all__ = ['logger', 'md5', 'encrypt', 'int2char', 'b64tohex']
# 调试日志设置
logger = logging.getLogger('lanzou')
logger.setLevel(logging.ERROR)
formatter = logging.Formatter(
fmt="%(asctime)s [line:%(lineno)d] %(funcName)s %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S")
console = logging.StreamHandler()
console.setFormatter(formatter)
logger.addHandler(console)
RSA_KEY = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDY7mpaUysvgQkbp0iIn2ezoUyh
i1zPFn0HCXloLFWT7uoNkqtrphpQ/63LEcPz1VYzmDuDIf3iGxQKzeoHTiVMSmW6
FlhDeqVOG094hFJvZeK4OzA6HVwzwnEW5vIZ7d+u61RV1bsFxmB68+8JXs3ycGcE
4anY+YzZJcyOcEGKVQIDAQAB
-----END PUBLIC KEY-----
"""
def encrypt(password: str) -> str:
return b64encode(
rsa.encrypt(
(password).encode('utf-8'),
rsa.PublicKey.load_pkcs1_openssl_pem(RSA_KEY.encode())
)
).decode()
b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
BI_RM = list("0123456789abcdefghijklmnopqrstuvwxyz")
def int2char(a):
return BI_RM[a]
def b64tohex(a):
d = ""
e = 0
for i in range(len(a)):
if list(a)[i] != "=":
v = b64map.index(list(a)[i])
if 0 == e:
e = 1
d += int2char(v >> 2)
c = 3 & v
elif 1 == e:
e = 2
d += int2char(c << 2 | v >> 4)
c = 15 & v
elif 2 == e:
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
else:
e = 0
d += int2char(c << 2 | v >> 4)
d += int2char(15 & v)
if e == 1:
d += int2char(c << 2)
# print(d)
return d
def md5(s):
hl = hashlib.md5()
hl.update(s.encode(encoding='utf-8'))
return hl.hexdigest()

5
cloud189/cli/__init__.py Normal file
View File

@ -0,0 +1,5 @@
from cloud189.cli.config import config
version = '0.0.1'
__all__ = ['cli', 'utils', 'version', 'config']

493
cloud189/cli/cli.py Normal file
View File

@ -0,0 +1,493 @@
from getpass import getpass
from sys import exit as exit_cmd
from webbrowser import open_new_tab
from cloud189.api import Cloud189
from cloud189.cli import config
# from cloud189.cli.downloader import Downloader, Uploader
# from cloud189.cli.recovery import Recovery
# from cloud189.cli.manager import global_task_mgr
from cloud189.cli.utils import *
class Commander:
"""网盘命令行"""
def __init__(self):
self._prompt = '> '
self._disk = Cloud189()
# self._task_mgr = global_task_mgr
self._dir_list = ''
self._file_list = ''
self._path_list = ''
self._parent_id = -1
self._parent_name = ''
self._work_name = ''
self._work_id = -1
self._last_work_id = -1
self._reader_mode = False
self._reader_mode = config.reader_mode
self._default_dir_pwd = ''
@staticmethod
def clear():
clear_screen()
@staticmethod
def help():
print_help()
@staticmethod
def update():
check_update()
def bye(self):
# if self._task_mgr.has_alive_task():
# info(f"有任务在后台运行, 退出请直接关闭窗口")
# else:
exit_cmd(0)
def rmode(self):
"""适用于屏幕阅读器用户的显示方式"""
choice = input("以适宜屏幕阅读器的方式显示(y): ")
if choice and choice.lower() == 'y':
config.reader_mode = True
self._reader_mode = True
info("已启用 Reader Mode")
else:
config.reader_mode = False
self._reader_mode = False
info("已关闭 Reader Mode")
def cdrec(self):
"""进入回收站模式"""
pass
# rec = Recovery(self._disk)
# rec.run()
# self.refresh()
def xghost(self):
"""扫描并删除幽灵文件夹"""
choice = input("需要清理幽灵文件夹吗(y): ")
if choice and choice.lower() == 'y':
self._disk.clean_ghost_folders()
info("清理已完成")
else:
info("清理操作已取消")
def refresh(self, dir_id=None):
"""刷新当前文件夹和路径信息"""
pass
'''
dir_id = self._work_id if dir_id is None else dir_id
self._file_list = self._disk.get_file_list(dir_id)
self._dir_list = self._disk.get_dir_list(dir_id)
self._path_list = self._disk.get_full_path(dir_id)
self._prompt = '/'.join(self._path_list.all_name) + ' > '
self._last_work_id = self._work_id
self._work_name = self._path_list[-1].name
self._work_id = self._path_list[-1].id
if dir_id != -1: # 如果存在上级路径
self._parent_name = self._path_list[-2].name
self._parent_id = self._path_list[-2].id'''
def login(self):
"""登录网盘"""
if not config.cookie or self._disk.login_by_cookie(config.cookie) != Cloud189.SUCCESS:
username = input('输入用户名:')
password = getpass('输入密码:')
code = self._disk.login(username, password)
if code == Cloud189.NETWORK_ERROR:
error("登录失败,网络连接异常")
return None
elif code == Cloud189.FAILED:
error('登录失败,用户名或密码错误 :(')
return None
# 登录成功保存用户 cookie
config.cookie = self._disk.get_cookie()
self.refresh()
def clogin(self):
"""使用 cookie 登录"""
open_new_tab('https://cloud.189.cn')
info("请设置 Cookie 内容:")
c_login_user = input("COOKIE_LOGIN_USER=")
if not c_login_user:
error("请输入正确的 Cookie 信息")
return None
cookie = {"COOKIE_LOGIN_USER": str(c_login_user)}
if self._disk.login_by_cookie(cookie) == Cloud189.SUCCESS:
config.cookie = cookie
self.refresh()
else:
error("登录失败,请检查 Cookie 是否正确")
def logout(self):
"""注销"""
clear_screen()
self._prompt = '> '
self._disk.logout()
self._file_list.clear()
self._dir_list.clear()
self._path_list = ''
self._parent_id = -1
self._work_id = -1
self._last_work_id = -1
self._parent_name = ''
self._work_name = ''
config.cookie = None
def ls(self):
"""列出文件(夹)"""
self._disk.get_files()
'''
if self._reader_mode: # 方便屏幕阅读器阅读
for folder in self._dir_list:
pwd_str = '有提取码' if folder.has_pwd else ''
print(f"{folder.name}/ {folder.desc} {pwd_str}")
for file in self._file_list:
pwd_str = '有提取码' if file.has_pwd else ''
print(f"{file.name} 大小:{file.size} 上传时间:{file.time} 下载次数:{file.downs} {pwd_str}")
else: # 普通用户显示方式
for folder in self._dir_list:
pwd_str = '' if folder.has_pwd else ''
print("#{0:<12}{1:<4}{2}{3}/".format(
folder.id, pwd_str, text_align(folder.desc, 28), folder.name))
for file in self._file_list:
pwd_str = '' if file.has_pwd else ''
print("#{0:<12}{1:<4}{2:<12}{3:>8}{4:>6} {5}".format(
file.id, pwd_str, file.time, file.size, file.downs, file.name))
'''
def cd(self, dir_name):
"""切换工作目录"""
if not dir_name:
info('cd .. 返回上级路径, cd - 返回上次路径, cd / 返回根目录')
elif dir_name == '..':
self.refresh(self._parent_id)
elif dir_name == '/':
self.refresh(-1)
elif dir_name == '-':
self.refresh(self._last_work_id)
elif dir_name == '.':
pass
elif folder := self._dir_list.find_by_name(dir_name):
self.refresh(folder.id)
else:
error(f'文件夹不存在: {dir_name}')
def mkdir(self, name):
"""创建文件夹"""
if self._dir_list.find_by_name(name):
error(f'文件夹已存在: {name}')
return None
dir_id = self._disk.mkdir(self._work_id, name, '')
if dir_id == Cloud189.MKDIR_ERROR:
error(f'创建文件夹失败(深度最大 4 级)')
return None
# 创建成功,添加到文件夹列表,减少向服务器请求次数
self._disk.set_passwd(dir_id, self._default_dir_pwd, is_file=False)
# self._dir_list.append(Folder(name, dir_id, bool(self._default_dir_pwd), ''))
def rm(self, fid):
"""删除文件(夹)"""
if fid:
self._disk.delete_by_id(fid)
# if file := self._file_list.find_by_name(name): # 删除文件
# if self._disk.delete(file.id, True) == Cloud189.SUCCESS:
# self._file_list.pop_by_id(file.id)
# else:
# error(f'删除文件失败: {name}')
# elif folder := self._dir_list.find_by_name(name): # 删除文件夹
# if self._disk.delete(folder.id, False) == Cloud189.SUCCESS:
# self._dir_list.pop_by_id(folder.id)
# else:
# error(f'删除文件夹失败(存在子文件夹?): {name}')
# else:
# error(f'文件(夹)不存在: {name}')
def rename(self, name):
"""重命名文件或文件夹(需要会员)"""
if folder := self._dir_list.find_by_name(name):
fid, is_file = folder.id, False
elif file := self._file_list.find_by_name(name):
fid, is_file = file.id, True
else:
error(f'没有这个文件(夹)的啦: {name}')
return None
new_name = input(f'重命名 "{name}"') or ''
if not new_name:
info(f'重命名操作取消')
return None
if is_file:
if self._disk.rename_file(fid, new_name) != Cloud189.SUCCESS:
error('(#°Д°) 文件重命名失败, 请开通会员,文件名不要带后缀')
return None
# 只更新本地索引的文件夹名(调用refresh()要等 1.5s 才能刷新信息)
self._file_list.update_by_id(fid, name=name)
else:
if self._disk.rename_dir(fid, new_name) != Cloud189.SUCCESS:
error('文件夹重命名失败')
return None
self._dir_list.update_by_id(fid, name=new_name)
def mv(self, name):
"""移动文件或文件夹"""
if file := self._file_list.find_by_name(name):
fid, is_file = file.id, True
elif folder := self._dir_list.find_by_name(name):
fid, is_file = folder.id, False
else:
error(f"文件(夹)不存在: {name}")
return None
path_list = self._disk.get_move_paths()
path_list = {'/'.join(path.all_name): path[-1].id for path in path_list}
choice_list = list(path_list.keys())
def _condition(typed_str, choice_str):
path_depth = len(choice_str.split('/'))
# 没有输入时, 补全 Cloud189,深度 1
if not typed_str and path_depth == 1:
return True
# Cloud189/ 深度为 2,补全同深度的文件夹 Cloud189/test 、Cloud189/txt
# Cloud189/tx 应该补全 Cloud189/txt
if path_depth == len(typed_str.split('/')) and choice_str.startswith(typed_str):
return True
set_completer(choice_list, condition=_condition)
choice = input('请输入路径(TAB键补全) : ')
if not choice or choice not in choice_list:
error(f"目标路径不存在: {choice}")
return None
folder_id = path_list.get(choice)
if is_file:
if self._disk.move_file(fid, folder_id) == Cloud189.SUCCESS:
self._file_list.pop_by_id(fid)
else:
error(f"移动文件到 {choice} 失败")
else:
if self._disk.move_folder(fid, folder_id) == Cloud189.SUCCESS:
self._dir_list.pop_by_id(fid)
else:
error(f"移动文件夹到 {choice} 失败")
def down(self, arg):
"""自动选择下载方式"""
if arg:
self._disk.download_by_id(arg)
# downloader = Downloader(self._disk)
# if arg.startswith('http'):
# downloader.set_url(arg)
# elif file := self._file_list.find_by_name(arg): # 如果是文件
# path = '/'.join(self._path_list.all_name) + '/' + arg # 文件在网盘的绝对路径
# downloader.set_fid(file.id, is_file=True, f_path=path)
# elif folder := self._dir_list.find_by_name(arg): # 如果是文件夹
# path = '/'.join(self._path_list.all_name) + '/' + arg + '/' # 文件夹绝对路径, 加 '/' 以便区分
# downloader.set_fid(folder.id, is_file=False, f_path=path)
# else:
# error(f'文件(夹)不存在: {arg}')
# return None
# # 提交下载任务
# self._task_mgr.add_task(downloader)
def jobs(self, arg):
"""显示后台任务列表"""
pass
# if arg.isnumeric():
# self._task_mgr.show_detail(int(arg))
# else:
# self._task_mgr.show_tasks()
def upload(self, path):
"""上传文件(夹)"""
path = path.strip('\"\' ') # 去除直接拖文件到窗口产生的引号
if not os.path.exists(path):
error(f'该路径不存在哦: {path}')
return None
self._disk.upload(path)
# uploader = Uploader(self._disk)
# if os.path.isfile(path):
# uploader.set_upload_path(path, is_file=True)
# else:
# uploader.set_upload_path(path, is_file=False)
# uploader.set_target(self._work_id, self._work_name)
# self._task_mgr.add_task(uploader)
def share(self, fid):
"""分享文件"""
if fid:
self._disk.share_file(fid)
# if file := self._file_list.find_by_name(name): # 文件
# inf = self._disk.get_file_info_by_id(file.id)
# if inf.code != Cloud189.SUCCESS:
# error('获取文件信息出错')
# return None
# print("-" * 50)
# print(f"文件名 : {name}")
# print(f"提取码 : {inf.pwd or '无'}")
# print(f"文件大小 : {inf.size}")
# print(f"上传时间 : {inf.time}")
# print(f"分享链接 : {inf.url}")
# print(f"描述信息 : {inf.desc or '无'}")
# print(f"下载直链 : {inf.durl or '无'}")
# print("-" * 50)
# elif folder := self._dir_list.find_by_name(name): # 文件夹
# inf = self._disk.get_folder_info_by_id(folder.id)
# if inf.code != Cloud189.SUCCESS:
# print('ERROR : 获取文件夹信息出错')
# return None
# print("-" * 80)
# print(f"文件夹名 : {name}")
# print(f"提取码 : {inf.folder.pwd or '无'}")
# print(f"分享链接 : {inf.folder.url}")
# print(f"描述信息 : {inf.folder.desc or '无'}")
# print("-" * 80)
# for file in inf.files:
# print("+ {0:<12}{1:<9}{2}\t{3}".format(file.time, file.size, file.url, file.name))
# if len(inf.files) != 0:
# print("-" * 80)
# else:
# error(f"文件(夹)不存在: {name}")
def passwd(self, name):
"""设置文件(夹)提取码"""
if file := self._file_list.find_by_name(name): # 文件
inf = self._disk.get_share_info(file.id, True)
new_pass = input(f'修改提取码 "{inf.pwd or ""}" -> ')
if 2 <= len(new_pass) <= 6:
if new_pass == 'off': new_pass = ''
if self._disk.set_passwd(file.id, str(new_pass), True) != Cloud189.SUCCESS:
error('设置文件提取码失败')
self.refresh()
else:
error('提取码为2-6位字符,关闭请输入off')
elif folder := self._dir_list.find_by_name(name): # 文件夹
inf = self._disk.get_share_info(folder.id, False)
new_pass = input(f'修改提取码 "{inf.pwd or ""}" -> ')
if 2 <= len(new_pass) <= 12:
if new_pass == 'off': new_pass = ''
if self._disk.set_passwd(folder.id, str(new_pass), False) != Cloud189.SUCCESS:
error('设置文件夹提取码失败')
self.refresh()
else:
error('提取码为2-12位字符,关闭请输入off')
else:
error(f'文件(夹)不存在: {name}')
def desc(self, name):
"""设置文件描述"""
if file := self._file_list.find_by_name(name): # 文件
inf = self._disk.get_share_info(file.id, True)
print(f"当前描述: {inf.desc or ''}")
desc = input(f'修改为 -> ')
if not desc:
error(f'文件描述不允许为空')
return None
if self._disk.set_desc(file.id, str(desc), True) != Cloud189.SUCCESS:
error(f'文件描述修改失败')
self.refresh()
elif folder := self._dir_list.find_by_name(name): # 文件夹
inf = self._disk.get_share_info(folder.id, False)
print(f"当前描述: {inf.desc}")
desc = input(f'修改为 -> ') or ''
if self._disk.set_desc(folder.id, str(desc), False) == Cloud189.SUCCESS:
if len(desc) == 0:
info('文件夹描述已关闭')
else:
error(f'文件夹描述修改失败')
self.refresh()
else:
error(f'文件(夹)不存在: {name}')
def setpath(self):
"""设置下载路径"""
print(f"当前下载路径 : {config.save_path}")
path = input('修改为 -> ').strip("\"\' ")
if os.path.isdir(path):
config.save_path = path
else:
error('路径非法,取消修改')
def setsize(self):
"""设置上传限制"""
print(f"当前限制(MB): {config.max_size}")
max_size = input('修改为 -> ')
if not max_size.isnumeric():
error("请输入大于 100 的数字")
return None
if self._disk.set_max_size(int(max_size)) != Cloud189.SUCCESS:
error("设置失败,限制值必需大于 100")
return None
config.max_size = int(max_size)
def setdelay(self):
"""设置大文件上传延时"""
print("大文件数据块上传延时范围(秒), 如: 0 60")
print(f"当前配置: {config.upload_delay}")
tr = input("请输入延时范围: ").split()
if len(tr) != 2:
error("格式有误!")
return None
tr = (int(tr[0]), int(tr[1]))
self._disk.set_upload_delay(tr)
config.upload_delay = tr
def setpasswd(self):
"""设置文件(夹)默认上传密码"""
print("关闭提取码请输入 off")
print(f"当前配置: 文件: {config.default_file_pwd or ''}, 文件夹: {config.default_dir_pwd or ''}")
file_pwd = input("设置文件默认提取码(2-6位): ")
if 2 <= len(file_pwd) <= 6:
config.default_file_pwd = '' if file_pwd == 'off' else file_pwd
dir_pwd = input("设置文件夹默认提取码(2-12位): ")
if 2 <= len(dir_pwd) <= 12:
config.default_dir_pwd = '' if dir_pwd == 'off' else dir_pwd
info(f"修改成功: 文件: {config.default_file_pwd or ''}, 文件夹: {config.default_dir_pwd or ''}, 配置将在下次启动时生效")
def run_one(self, cmd, arg):
no_arg_cmd = ['bye', 'cdrec', 'clear', 'clogin', 'help', 'login', 'logout', 'ls', 'refresh', 'rmode', 'setpath',
'setsize', 'update', 'xghost', 'setdelay', 'setpasswd']
cmd_with_arg = ['cd', 'desc', 'down', 'jobs', 'mkdir', 'mv', 'passwd', 'rename', 'rm', 'share', 'upload']
if cmd in no_arg_cmd:
getattr(self, cmd)()
elif cmd in cmd_with_arg:
getattr(self, cmd)(arg)
def run(self):
"""处理一条用户命令"""
no_arg_cmd = ['bye', 'cdrec', 'clear', 'clogin', 'help', 'login', 'logout', 'ls', 'refresh', 'rmode', 'setpath',
'setsize', 'update', 'xghost', 'setdelay', 'setpasswd']
cmd_with_arg = ['cd', 'desc', 'down', 'jobs', 'mkdir', 'mv', 'passwd', 'rename', 'rm', 'share', 'upload']
choice_list = self._file_list + self._dir_list
# choice_list = self._file_list.all_name + self._dir_list.all_name
cmd_list = no_arg_cmd + cmd_with_arg
set_completer(choice_list, cmd_list=cmd_list)
try:
args = input(self._prompt).split(' ', 1)
if len(args) == 0:
return None
except KeyboardInterrupt:
print('')
info('退出本程序请输入 bye')
return None
cmd, arg = (args[0], '') if len(args) == 1 else (args[0], args[1]) # 命令, 参数(可带有空格, 没有参数就设为空)
if cmd in no_arg_cmd:
getattr(self, cmd)()
elif cmd in cmd_with_arg:
getattr(self, cmd)(arg)

118
cloud189/cli/config.py Normal file
View File

@ -0,0 +1,118 @@
from pickle import load, dump
__all__ = ['config']
KEY = 152
config_file = './.config'
def encrypt(key, s):
b = bytearray(str(s).encode("utf-8"))
n = len(b)
c = bytearray(n*2)
j = 0
for i in range(0, n):
b1 = b[i]
b2 = b1 ^ key
c1 = b2 % 19
c2 = b2 // 19
c1 = c1 + 46
c2 = c2 + 46
c[j] = c1
c[j+1] = c2
j = j+2
return c.decode("utf-8")
def decrypt(ksa, s):
c = bytearray(str(s).encode("utf-8"))
n = len(c)
if n % 2 != 0:
return ""
n = n // 2
b = bytearray(n)
j = 0
for i in range(0, n):
c1 = c[j]
c2 = c[j + 1]
j = j + 2
c1 = c1 - 46
c2 = c2 - 46
b2 = c2 * 19 + c1
b1 = b2 ^ ksa
b[i] = b1
return b.decode("utf-8")
def save_config(config):
with open(config_file, 'wb') as c:
dump(config, c)
class Config:
def __init__(self):
self._cookie = {}
self._save_path = './downloads'
self._reader_mode = False
def _save(self):
with open(self._config_file, 'wb') as c:
dump(self._config, c)
def encode(self, var):
if isinstance(var, dict):
for k, v in var.items():
var[k] = encrypt(KEY, str(v))
elif var:
var = encrypt(KEY, str(var))
return var
def decode(self, var):
try:
if isinstance(var, dict):
dvar = {} # 新开内存,否则会修改原字典
for k, v in var.items():
dvar[k] = decrypt(KEY, str(v))
elif var:
dvar = decrypt(KEY, var)
else:
dvar = None
except Exception as e:
# print(e)
dvar = None
return dvar
@property
def cookie(self):
return self.decode(self._cookie)
@cookie.setter
def cookie(self, value):
self._cookie = self.encode(value)
save_config(self)
@property
def save_path(self):
return self._save_path
@save_path.setter
def save_path(self, value):
self._save_path = value
save_config(self)
@property
def reader_mode(self):
return self._reader_mode
@reader_mode.setter
def reader_mode(self, value: bool):
self._reader_mode = value
save_config(self)
# 全局配置对象
try:
with open(config_file, 'rb') as c:
config = load(c)
except:
config = Config()

196
cloud189/cli/utils.py Normal file
View File

@ -0,0 +1,196 @@
import os
from platform import system as platform
import readline
import requests
from cloud189.api import Cloud189
from cloud189.cli import version
def error(msg):
print(f"\033[1;31mError : {msg}\033[0m")
def info(msg):
print(f"\033[1;34mInfo : {msg}\033[0m")
def clear_screen():
"""清空屏幕"""
if os.name == 'nt':
os.system('cls')
else:
os.system('clear')
def why_error(code):
"""错误原因"""
if code == Cloud189.URL_INVALID:
return '分享链接无效'
elif code == Cloud189.LACK_PASSWORD:
return '缺少提取码'
elif code == Cloud189.PASSWORD_ERROR:
return '提取码错误'
elif code == Cloud189.FILE_CANCELLED:
return '分享链接已失效'
elif code == Cloud189.ZIP_ERROR:
return '解压过程异常'
elif code == Cloud189.NETWORK_ERROR:
return '网络连接异常'
elif code == Cloud189.CAPTCHA_ERROR:
return '验证码错误'
else:
return '未知错误'
def set_console_style():
"""设置命令行窗口样式"""
if os.name != 'nt':
return None
os.system('mode 120, 40')
os.system(f'title 天翼云盘-cli {version}')
def captcha_handler(img_data):
"""处理下载时出现的验证码"""
img_path = os.getcwd() + os.sep + 'captcha.png'
with open(img_path, 'wb') as f:
f.write(img_data)
m_platform = platform()
if m_platform == 'Darwin':
os.system(f'open {img_path}')
elif m_platform == 'Linux':
os.system(f'xdg-open {img_path}')
else:
os.startfile(img_path)
ans = input('\n请输入验证码:')
os.remove(img_path)
return ans
def text_align(text, length) -> str:
"""中英混合字符串对齐"""
text_len = len(text)
for char in text:
if u'\u4e00' <= char <= u'\u9fff':
text_len += 1
space = length - text_len
return text + ' ' * space
def set_completer(choice_list, *, cmd_list=None, condition=None):
"""设置自动补全"""
if condition is None:
condition = lambda typed, choice: choice.startswith(typed) # 默认筛选条件:选项以键入字符开头
def completer(typed, rank):
tab_list = [] # TAB 补全的选项列表
if cmd_list is not None and not typed: # 内置命令提示
return cmd_list[rank]
for choice in choice_list:
if condition(typed, choice):
tab_list.append(choice)
return tab_list[rank]
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
def print_logo():
"""输出logo"""
clear_screen()
logo_str = f"""
# /$$$$$$ /$$ /$$ /$$ /$$$$$$ /$$$$$$
# /$$__ $$| $$ | $$ /$$$$ /$$__ $$ /$$__ $$
# | $$ \__/| $$ /$$$$$$ /$$ /$$ /$$$$$$$|_ $$ | $$ \ $$| $$ \ $$
# | $$ | $$ /$$__ $$| $$ | $$ /$$__ $$ | $$ | $$$$$$/| $$$$$$$
# | $$ | $$| $$ \ $$| $$ | $$| $$ | $$ | $$ >$$__ $$ \____ $$
# | $$ $$| $$| $$ | $$| $$ | $$| $$ | $$ | $$ | $$ \ $$ /$$ \ $$
# | $$$$$$/| $$| $$$$$$/| $$$$$$/| $$$$$$$ /$$$$$$| $$$$$$/| $$$$$$/
# \______/ |__/ \______/ \______/ \_______/|______/ \______/ \______/
#
--------------------------------------------------------------------------
Github: https://github.com/rachpt/cloud189 (Version: {version})
--------------------------------------------------------------------------
"""
print(logo_str)
def print_help():
clear_screen()
help_text = f"""
天翼云盘-cli v{version}
支持大文件上传无视文件格式限制
支持直链提取批量上传下载断点续传功能
命令帮助 :
help 显示本信息
update 检查更新
rmode 屏幕阅读器模式
refresh 强制刷新文件列表
xghost 清理"幽灵"文件夹
login 使用账号密码登录网盘
clogin 使用 Cookie 登录网盘
logout 注销当前账号
jobs 查看后台任务列表
ls 列出文件()
cd 切换工作目录
cdrec 进入回收站
rm 删除网盘文件()
rename 重命名文件()
desc 修改文件()描述
mv 移动文件()
mkdir 创建新文件夹(最大深度 4)
share 显示文件()分享信息
clear 清空屏幕
clean 清空回收站
upload 上传文件()
down 下载文件()支持 URL 下载
passwd 设置文件()提取码
setpath 设置文件下载路径
setsize 设置单文件大小限制
setpasswd 设置文件()默认提取码
setdelay 设置上传大文件数据块的延时
bye 退出本程序
更详细的介绍请参考本项目的 Github 主页:
https://github.com/rachpt/cloud189
如有 Bug 反馈或建议请在 GitHub Issue
感谢您的使用 ('')
"""
print(help_text)
def check_update():
"""检查更新"""
clear_screen()
print("正在检测更新...")
api = "https://api.github.com/repos/rachpt/cloud189/releases/latest"
tag_name = None
try:
resp = requests.get(api).json()
tag_name, msg = resp['tag_name'], resp['body']
update_url = resp['assets'][0]['browser_download_url']
except (requests.RequestException, AttributeError, KeyError):
error("检查更新时发生异常")
input()
return None
if tag_name:
ver = version.split('.')
ver2 = tag_name.replace('v', '').split('.')
local_version = int(ver[0]) * 100 + int(ver[1]) * 10 + int(ver[2])
remote_version = int(ver2[0]) * 100 + int(ver2[1]) * 10 + int(ver2[2])
if remote_version > local_version:
print(f"程序可以更新 v{version} -> {tag_name}")
print(f"\n@更新说明:\n{msg}")
print(f"\n@Windows 更新:")
print(f"Github: {update_url}")
print("\n@Linux 更新:")
input("git clone https://github.com/rachpt/cloud189.git")
else:
print("(*/ω\*) 暂无新版本发布~")
print("但项目可能已经更新,建议去项目主页看看")
print("如有 Bug 或建议,请提 Issue")
print("Github: https://github.com/rachpt/cloud189")

View File

@ -1,3 +1,7 @@
certifi
chardet
idna
pyreadline
requests
requests_toolbelt
rsa

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB