mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-16 09:20:43 +08:00
1.飞牛自动下载资源
This commit is contained in:
parent
219966826c
commit
40a82b807c
@ -88,7 +88,8 @@ docker run -d \
|
||||
|
||||
## 🤝 贡献者
|
||||
|
||||
| 插件 | 说明 | 贡献者 |
|
||||
| ------- | -------------------- | --------------------------------------- |
|
||||
| plex.py | 自动刷新 Plex 媒体库 | [zhazhayu](https://github.com/zhazhayu) |
|
||||
| alist_strm_gen.py | 自动生成strm | [xiaoQQya](https://github.com/xiaoQQya) |
|
||||
| 插件 | 说明 | 贡献者 |
|
||||
|-------------------|---------------| --------------------------------------- |
|
||||
| plex.py | 自动刷新 Plex 媒体库 | [zhazhayu](https://github.com/zhazhayu) |
|
||||
| alist_strm_gen.py | 自动生成strm | [xiaoQQya](https://github.com/xiaoQQya) |
|
||||
| fnos.py | 飞牛自动下载转存资源 | [key762](https://github.com/key762) |
|
||||
@ -4,5 +4,6 @@
|
||||
"alist_strm_gen",
|
||||
"aria2",
|
||||
"emby",
|
||||
"fnos",
|
||||
"plex"
|
||||
]
|
||||
221
plugins/fnos.py
Normal file
221
plugins/fnos.py
Normal file
@ -0,0 +1,221 @@
|
||||
import ssl
|
||||
import json
|
||||
import sys
|
||||
import certifi
|
||||
import asyncio
|
||||
import websockets
|
||||
import base64
|
||||
import secrets
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import HMAC, SHA256
|
||||
|
||||
"""
|
||||
配合 飞牛系统的Alist 项目,转存后自动下载
|
||||
"""
|
||||
|
||||
async def create_websocket(url):
|
||||
if 'wss' in url:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_3
|
||||
ssl_context.load_verify_locations(certifi.where())
|
||||
return await websockets.connect(url, ssl=ssl_context, ping_interval=None)
|
||||
else:
|
||||
return await websockets.connect(url, ping_interval=None)
|
||||
|
||||
async def wss_connect(websocket):
|
||||
response = await websocket.recv()
|
||||
return response
|
||||
|
||||
async def close_websocket(websocket):
|
||||
await websocket.close()
|
||||
|
||||
async def send_ping(websocket):
|
||||
while True:
|
||||
await asyncio.sleep(5) # 每10秒发送一次Ping消息
|
||||
await websocket.send('{"req":"ping"}')
|
||||
|
||||
def rsa_encrypt(message, public_key):
|
||||
public_key = RSA.import_key(public_key)
|
||||
cipher = Cipher_pkcs1_v1_5.new(public_key)
|
||||
cipher_text = base64.b64encode(cipher.encrypt(message.encode('utf-8')))
|
||||
return cipher_text.decode('utf-8')
|
||||
|
||||
def encrypt(text, key, iv):
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
|
||||
encrypted = base64.b64encode(cipher.encrypt(pad(text).encode()))
|
||||
return encrypted.decode()
|
||||
|
||||
def unpad(data):
|
||||
pad = data[-1]
|
||||
if type(pad) is int:
|
||||
pad = chr(pad)
|
||||
return data[:-ord(pad)]
|
||||
|
||||
def decrypt(text, key, iv):
|
||||
# 将加密数据转换位bytes类型数据
|
||||
encodebytes = base64.decodebytes(text.encode())
|
||||
# 解密
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
text_decrypted = cipher.decrypt(encodebytes)
|
||||
text_decrypted = unpad(text_decrypted)
|
||||
return base64.b64encode(text_decrypted).decode()
|
||||
|
||||
oneMark = True
|
||||
def print_progress_bar(iteration, total, prefix='', suffix='', length=35):
|
||||
global oneMark
|
||||
percent = (iteration / total) * 100
|
||||
filled_length = int(length * iteration // total)
|
||||
bar = '#' * filled_length + ' ' * (length - filled_length)
|
||||
percent_str = str(int(percent)).zfill(2)
|
||||
if percent < 100:
|
||||
percent_str = " " + percent_str
|
||||
if oneMark:
|
||||
print(f'{prefix} {bar} {percent_str}% {suffix}', end='')
|
||||
oneMark = False
|
||||
else:
|
||||
print(f'\r{prefix} {bar} {percent_str}% {suffix}', end='')
|
||||
sys.stdout.flush()
|
||||
|
||||
def seconds_to_hms(seconds):
|
||||
hours = seconds // 3600
|
||||
remainder = seconds % 3600
|
||||
minutes = remainder // 60
|
||||
seconds = remainder % 60
|
||||
hours_str = str(int(hours)).zfill(2)
|
||||
minutes_str = str(int(minutes)).zfill(2)
|
||||
seconds_str = str(int(seconds)).zfill(2)
|
||||
return f'{hours_str}:{minutes_str}:{seconds_str}'
|
||||
|
||||
def format_byte_repr(byte_num):
|
||||
KB = 1024
|
||||
MB = KB * KB
|
||||
GB = MB * KB
|
||||
TB = GB * KB
|
||||
try:
|
||||
if isinstance(byte_num, str):
|
||||
byte_num = int(byte_num)
|
||||
if byte_num > TB:
|
||||
result = '%sTB' % round(byte_num / TB, 2)
|
||||
elif byte_num > GB:
|
||||
result = '%sGB' % round(byte_num / GB, 2)
|
||||
elif byte_num > MB:
|
||||
result = '%sMB' % round(byte_num / MB, 2)
|
||||
elif byte_num > KB:
|
||||
result = '%sKB' % round(byte_num / KB, 2)
|
||||
else:
|
||||
result = '%sB' % byte_num
|
||||
return result
|
||||
except Exception as e:
|
||||
print(e.args)
|
||||
return byte_num
|
||||
|
||||
class Fnos:
|
||||
|
||||
default_config = {
|
||||
"websocket": "", # 飞牛的websocket地址
|
||||
"user": "", # 飞牛的用户账号
|
||||
"password": "", # 飞牛的用户密码
|
||||
"mount_path": "", # Alist挂载的地址
|
||||
"download_wait": "true", # 是否等待下载完成
|
||||
}
|
||||
default_task_config = {
|
||||
"download_path": "", # 下载路径
|
||||
}
|
||||
is_active = True
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.plugin_name = self.__class__.__name__.lower()
|
||||
if kwargs:
|
||||
for key, _ in self.default_config.items():
|
||||
if key in kwargs:
|
||||
setattr(self, key, kwargs[key])
|
||||
else:
|
||||
print(f"{self.__class__.__name__} 模块缺少必要参数: {key}")
|
||||
if self.websocket and self.user and self.password and self.mount_path and self.download_wait:
|
||||
self.is_active = True
|
||||
|
||||
def run(self, task, **kwargs):
|
||||
dramaList = []
|
||||
if kwargs['tree'] is not None:
|
||||
for node in kwargs['tree'].all_nodes():
|
||||
if node.data['is_dir'] is False:
|
||||
dramaList.append(f'"{self.mount_path}{node.data['path']}"')
|
||||
if len(dramaList) < 0:
|
||||
print(f"飞牛:😄 此次转存无需下载文件!")
|
||||
else:
|
||||
task_config = task.get("addition", {}).get(self.plugin_name, self.default_task_config)
|
||||
print(f"飞牛:🎞️ 转存有需下载文件️")
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
websocket = loop.run_until_complete(create_websocket(self.websocket))
|
||||
loop.run_until_complete(websocket.send('{"reqid":"676cf70d00000000000000000001","req":"util.crypto.getRSAPub"}'))
|
||||
try:
|
||||
aesKeyByte = None
|
||||
aesIvByte = None
|
||||
num = 0
|
||||
asyncio.ensure_future(send_ping(websocket))
|
||||
while True:
|
||||
response = loop.run_until_complete(wss_connect(websocket))
|
||||
if "-----BEGIN PUBLIC KEY-----" in response:
|
||||
pub = json.loads(response).get("pub")
|
||||
si = json.loads(response).get("si")
|
||||
userData = '{"reqid":"676cf70d00000000000000000002","user":"'+self.user+'","password":"'+self.password+'","deviceType":"Browser","deviceName":"Mac OS-Google Chrome","stay":true,"req":"user.login","si":"' + si + '"}'
|
||||
aesKeyStr = "lUfJn1XJ9akUvmmwQplpVIy1XNC2jJ3q"
|
||||
aesIv = secrets.token_bytes(16)
|
||||
aesIvBase64 = base64.b64encode(aesIv).decode('utf-8')
|
||||
iv = aesIvBase64
|
||||
rsa = rsa_encrypt(aesKeyStr, pub)
|
||||
aes = encrypt(userData, aesKeyStr.encode(), aesIv)
|
||||
aesKeyByte = aesKeyStr.encode()
|
||||
aesIvByte = aesIv
|
||||
sendMsg = '{"rsa":"' + rsa + '","iv":"' + iv + '","aes":"' + aes + '","req":"encrypted"}'
|
||||
loop.run_until_complete(websocket.send(sendMsg))
|
||||
elif "676cf70d00000000000000000002" in response:
|
||||
print(f"飞牛:👨 用户认证成功🏅")
|
||||
secret = json.loads(response).get('secret')
|
||||
keys = decrypt(secret, aesKeyByte, aesIvByte)
|
||||
Secret = base64.b64decode(keys)
|
||||
a = '{"reqid":"676cf70d00000000000000000003","files":['+','.join(dramaList)+'],"pathTo":"'+task_config.get("download_path")+'","overwrite":1,"description":"剧集自动下载","req":"file.cp"}'
|
||||
mark = base64.b64encode(HMAC.new(Secret, a.encode(), digestmod=SHA256).digest()).decode()
|
||||
loop.run_until_complete(websocket.send(mark + a))
|
||||
elif "pong" in response:
|
||||
pass
|
||||
elif "676cf70d00000000000000000003" in response and '"sysNotify":"taskId"' in response:
|
||||
print(f"飞牛:💼 收到资源下载任务")
|
||||
pass
|
||||
elif "676cf70d00000000000000000003" in response and 'percent' in response:
|
||||
data = json.loads(response)
|
||||
if 'true' in self.download_wait.lower():
|
||||
if num != 0 or num < int(data.get('percent')):
|
||||
time = seconds_to_hms(data.get('time'))
|
||||
du = format_byte_repr(data.get('size')) + '/' + format_byte_repr(data.get('sizeTotal'))
|
||||
speed = format_byte_repr(data.get('speed')) + '/S'
|
||||
suffix = f'{time} {du} {speed}'
|
||||
print_progress_bar(data.get('percent'), 100, prefix='⌛飞牛: ️', suffix=suffix)
|
||||
num = data.get('percent')
|
||||
else:
|
||||
print(f"飞牛:🎞️ 下载任务后台执行")
|
||||
break
|
||||
elif '"taskInfo":{"reqid":"676cf70d00000000000000000003"' in response:
|
||||
pass
|
||||
elif "676cf70d00000000000000000003" in response and '"result":"succ"' in response:
|
||||
print()
|
||||
print(f"飞牛: 下载任务完成✅")
|
||||
break
|
||||
elif "676cf70d00000000000000000003" in response and '"result":"fail"' in response:
|
||||
print()
|
||||
print(f"飞牛: 下载任务异常❌,检查您配置")
|
||||
break
|
||||
elif "676cf70d00000000000000000003" in response and '"result":"cancel"' in response:
|
||||
print()
|
||||
print(f"飞牛: 下载任务被取消❌")
|
||||
break
|
||||
else:
|
||||
print(f"{response}")
|
||||
except Exception as e:
|
||||
print(f"飞牛: 下载任务异常❌ {e}")
|
||||
loop.run_until_complete(close_websocket(websocket))
|
||||
Loading…
Reference in New Issue
Block a user