- hls_dl.py: 通用 HLS 下载器(m3u8 解析/AES-128 解密/ffmpeg 合并) - reqable_mcp.py: Reqable MCP 客户端(stdio, --port 9002) - huangguo_video_dl.user.js: 油猴全集下载脚本 v1.1.2 * 详情页一键下载全部集数,解密转封装后打包 ZIP 存 ~/Downloads * 手写 STORE ZIP(逐集 Blob 卸堆外),修复 JSZip 大文件卡死 * @connect 覆盖 rlkfus.cn / cloudfront.net CDN - README.md: 用法与踩坑记录
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Reqable MCP 客户端(stdio 传输)
|
|
用法:
|
|
python3 reqable_mcp.py list # 列出所有工具
|
|
python3 reqable_mcp.py call <tool> '<json>' # 调用工具,input 为 JSON
|
|
python3 reqable_mcp.py m3u8 # 快捷:搜出所有 m3u8 请求
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
|
|
MCP_BIN = "/Applications/Reqable.app/Contents/Helpers/mcp-server"
|
|
# Reqable 实际监听端口(adb 占了 9000,实际用 9002)
|
|
REQABLE_PORT = 9002
|
|
|
|
|
|
class McpClient:
|
|
def __init__(self):
|
|
self.proc = subprocess.Popen(
|
|
[MCP_BIN, "--port", str(REQABLE_PORT)],
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
self._id = 0
|
|
|
|
def _send(self, obj):
|
|
self.proc.stdin.write(json.dumps(obj) + "\n")
|
|
self.proc.stdin.flush()
|
|
|
|
def _recv(self, expect_id):
|
|
"""读到匹配 id 的 response 为止,跳过 notification"""
|
|
while True:
|
|
line = self.proc.stdout.readline()
|
|
if not line:
|
|
raise RuntimeError("mcp-server 关闭了管道")
|
|
try:
|
|
msg = json.loads(line)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
if msg.get("id") == expect_id:
|
|
if "error" in msg:
|
|
raise RuntimeError(f"MCP error: {msg['error']}")
|
|
return msg.get("result")
|
|
|
|
def request(self, method, params=None):
|
|
self._id += 1
|
|
self._send({"jsonrpc": "2.0", "id": self._id,
|
|
"method": method, "params": params or {}})
|
|
return self._recv(self._id)
|
|
|
|
def initialize(self):
|
|
r = self.request("initialize", {
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {},
|
|
"clientInfo": {"name": "hls-whale", "version": "0.1"},
|
|
})
|
|
self._send({"jsonrpc": "2.0", "method": "notifications/initialized"})
|
|
return r
|
|
|
|
def list_tools(self):
|
|
return self.request("tools/list")["tools"]
|
|
|
|
def call(self, name, arguments):
|
|
return self.request("tools/call", {"name": name, "arguments": arguments})
|
|
|
|
def close(self):
|
|
try:
|
|
self.proc.stdin.close()
|
|
self.proc.wait(timeout=3)
|
|
except Exception:
|
|
self.proc.kill()
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
cmd = sys.argv[1]
|
|
c = McpClient()
|
|
info = c.initialize()
|
|
print(f"[MCP] {info['serverInfo']['name']} {info['serverInfo'].get('version','')}",
|
|
file=sys.stderr)
|
|
|
|
try:
|
|
if cmd == "list":
|
|
for t in c.list_tools():
|
|
desc = (t.get("description") or "").split("\n")[0][:80]
|
|
print(f"{t['name']:40s} {desc}")
|
|
elif cmd == "call":
|
|
name = sys.argv[2]
|
|
args = json.loads(sys.argv[3]) if len(sys.argv) > 3 else {}
|
|
r = c.call(name, args)
|
|
for block in r.get("content", []):
|
|
print(block.get("text", ""))
|
|
if r.get("isError"):
|
|
sys.exit(2)
|
|
elif cmd == "m3u8":
|
|
# 快捷:找出搜索类工具并搜 m3u8
|
|
tools = {t["name"]: t for t in c.list_tools()}
|
|
search = next((n for n in tools if "search" in n or "filter" in n or "find" in n), None)
|
|
if not search:
|
|
print("没找到搜索工具,工具列表:", list(tools), file=sys.stderr)
|
|
sys.exit(1)
|
|
print(f"[用 {search}] 搜 m3u8", file=sys.stderr)
|
|
r = c.call(search, {"keyword": "m3u8"} if "keyword" in json.dumps(tools[search]) else {"query": "m3u8"})
|
|
for block in r.get("content", []):
|
|
print(block.get("text", ""))
|
|
else:
|
|
print(__doc__)
|
|
finally:
|
|
c.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|