手机搭建emby服务一
分三部分搭建,第一步安装openlist并生成本地strm文件;第二步安装emby服务并添加媒体库;第三部分内外穿透实现公网访问。
步骤一
1. Termux中删除alist
1.1 如果Termux中原安装了alist先删除
1.2 停止 AList 后台进程
pkill -9 alist1.3 查找并删除文件
通常文件夹名就叫 alist。你可以输入 ls 查看当前目录下的文件夹。
rm -rf ~/alist你可以现在去 Termux 里执行这几条命令试试。
如果在执行 netstat 或者 pgrep 时依然有输出,说明还有残留进程。
1.4 检查是否还有残留的快捷命令:
which alist- 如果返回空行(直接跳到下一行),说明很干净。
- 如果返回了路径(比如 /data/.../bin/alist),请执行 rm 加上那个路径,例如:rm /data/data/com.termux/files/usr/bin/alist。
- 最后确认:
输入 ls 查看当前目录,确认没有 alist 相关的文件夹了。
1.5 输入which alist后输出:/data/data/com.termux/files/usr/bin/which: this version of which' is deprecated; use command -v' in scripts instead.
/data/data/com.termux/files/usr/bin/alist
看来 AList 还没“搬家”干净,它现在正躲在你的系统指令路径里。
那个输出告诉你了两件事:
AList 在哪: 它位于 /data/data/com.termux/files/usr/bin/alist。
小插曲: which 命令在 Termux 里有点老了,建议以后用 command -v,但这不影响咱们现在的操作。
1.6 继续清理
删除系统路径下的 AList:
rm /data/data/com.termux/files/usr/bin/alist再次确认:
command -v alist如果这次没有任何输出(直接跳回新的一行),那么恭喜你,AList 已经从你的 Termux 里彻底消失了!
2. Termux中安装openlist
2.1 你想把 OpenList 安装在哪个文件夹里? 通常建议建一个专门的文件夹,方便以后管理。你可以直接运行下面这串命令来创建目录并进入:
mkdir -p ~/openlist && cd ~/openlist2.2 更新并升级所有软件包
pkg update && pkg upgrade -y2.3 使用curl下载(换个更稳的方法)
curl -L -O https://github.com/OpenListTeam/OpenList/releases/latest/download/openlist-android-arm64.tar.gz2.4 解压并运行
tar -zxvf openlist-android-arm64.tar.gz && chmod +x openlist2.5 如果下载实在太慢可直接浏览器下载
2.5.1 掐断当前下载:
在手机键盘上按下 Ctrl(Termux 工具栏上的按钮)然后按 C。你会看到光标回到了 ~/openlist $。
2.5.2 清理残余:
为了防止文件损坏,先把刚才下载了一半的文件删掉:
rm openlist-android-arm64.tar.gz2.5.3 点击这个链接直接下载到手机:https://github.com/OpenListTeam/OpenList/releases/latest/download/openlist-android-arm64.tar.gz(或者把这个链接贴进你的手机浏览器)。
2.5.4 下载完成后,在 Termux 里输入以下命令把文件从手机下载目录移动过来:
termux-setup-storage
# 弹出权限请求时点“允许”
cp /sdcard/Download/openlist-android-arm64.tar.gz ~/openlist/2.5.5 验证下载是否成功
不管是哪个方法,下载完后输入:
ls -lh2.5.6 解压文件
tar -zxvf openlist-android-arm64.tar.gz2.5.7 授予运行权限
chmod +x openlist2.5.8 启动 OpenList 服务器
./openlist server2.5.9 获取管理员密码
运行命令后,屏幕会飞快地滚动很多日志信息。请在这些信息中仔细寻找下面这一行:
Successfully create admin user, username: admin, password: XXXXXXXX
那个 XXXXXXXX 就是你的随机初始密码。
如果你错过了这一行,或者没记住,别担心:
按 Ctrl + C 停止程序。
输入命令:./openlist admin set 123456(这会把密码强行改为 123456)。
重新输入 ./openlist server 启动。
2.5.10 扫尾工作
既然已经解压成功,那个 47MB 的压缩包就没用了,可以删掉它省点空间:
rm openlist-android-arm64.tar.gz3.开启 Termux 的 Wake Lock(唤醒锁)
这是最简单也最直接的方法。它会告诉系统:“哪怕锁屏了,也请给我的 CPU 留一口气”。
操作方法: 下拉你的手机通知栏,找到 Termux 的通知。
点击: 点击通知上的 Acquire Wake Lock。
或者在命令行输入:
termux-wake-lock4. 创建本地strm存放目录
# 建立一个在手机下载目录里可见的文件夹
mkdir -p ~/storage/downloads/strm_files5. 登陆openlsit,创建strm存储
6. 索引搜索
第一次需全面索引一次
7. 全局勾选
第二步
emby服务端安装
第三步
内外穿透,使用Cloudflare Tunnel方式安装
1. Termux 官方库里已经自带了 cloudflared(Tunnel 的核心程序),所以安装极其简单,不需要折腾脚本。
在 Termux 里执行:
pkg update -y && pkg install cloudflared -y2. 让 Tunnel 认领你的域名
装好之后,我们需要让这台手机和你的 Cloudflare 账号绑定,这样它才能接管你的 363689.xyz。
在 Termux 里输入这行命令:
cloudflared tunnel login这个时候,Termux 屏幕上会弹出一长串网址(URL)。 ---
你现在的任务:把 Termux 里弹出的那串网址复制下来,丢到你手机或电脑的浏览器里打开。 网页会让你登录 Cloudflare,你选择你的 363689.xyz 域名点授权就行。
3. 创建一条隧道
直接在 Termux 里输入并回车:
cloudflared tunnel create emby_tunnel把你的域名绑到隧道上
这一步会自动去 Cloudflare 把 emby.363689.xyz 的解析改成隧道的专用地址,瞬间顶替掉原来那些乱七八糟的IP
cloudflared tunnel route dns emby_tunnel emby.363689.xyz给 openlist 绑一个新域名
cloudflared tunnel route dns emby_tunnel olist.363689.xyz4. 生成配置文件
现在我们要让这条隧道同时干两份活:既转发 Emby,又转发 openlist。
假设你 openlist 的端口是默认的 5244(如果不是,请把下面代码里的 5244 改成你的实际端口)
CRED_FILE=$(ls ~/.cloudflared/*.json | head -n 1)
cat <<EOF > ~/.cloudflared/config.yml
tunnel: emby_tunnel
credentials-file: ${CRED_FILE}
ingress:
- hostname: emby.363689.xyz
service: http://127.0.0.1:8096
- hostname: olist.363689.xyz
service: http://127.0.0.1:5244
- service: http_status:404
EOF5. 启动隧道
cloudflared tunnel run emby_tunnel6.静默启动服务
nohup cloudflared tunnel run emby_tunnel >/dev/null 2>&1 &或强行让隧道走传统的 TCP 协议(HTTP2),UDP 容易被系统掐断。
nohup cloudflared tunnel --protocol http2 --config ~/.cloudflared/config.yml run emby_tunnel > ~/tunnel.log 2>&1 &cd ~/openlistnohup ./openlist server >/dev/null 2>&1 &jobs附录
一、 Aria2
1.Aria2 终极完美安装脚本
# 1. 杀掉可能在后台带病运行的旧进程,清空旧配置
pkill aria2c
rm -rf ~/.config/aria2
# 2. 重新安装并创建必要的文件夹和文件
pkg install aria2 -y
mkdir -p ~/.config/aria2
mkdir -p /storage/emulated/0/Movies
touch ~/.config/aria2/aria2.session
# 3. 写入完美无瑕的配置文件 (修复了 input-file 错误)
cat > ~/.config/aria2/aria2.conf << "EOF"
# --- 基础设置 ---
dir=/storage/emulated/0/Movies
continue=true
disk-cache=32M
# --- 断点续传与进度保存 ---
input-file=/data/data/com.termux/files/home/.config/aria2/aria2.session
save-session=/data/data/com.termux/files/home/.config/aria2/aria2.session
save-session-interval=60
# --- RPC 控制设置 (给 openlist 用的接口) ---
enable-rpc=true
rpc-listen-all=true
rpc-allow-origin-all=true
rpc-listen-port=6800
rpc-secret=xxsky1127
# --- 核心下载提速优化 ---
max-concurrent-downloads=5
max-connection-per-server=16
split=16
min-split-size=10M
# --- BT/磁力链接专属优化 ---
bt-enable-lpd=true
enable-dht=true
enable-peer-exchange=true
bt-seed-unverified=true
bt-save-metadata=true
EOF
# 4. 启动 Aria2 (以太静默守护模式运行)
aria2c --conf-path=$HOME/.config/aria2/aria2.conf -D
echo "✅ Aria2 完美配置完毕并已在后台稳定运行!"2.在后台绑定 Aria2 (建立通讯)
2.1在你的主力机浏览器里,输入服务器的地址进入 openlist 后台(例如:http://192.168.0.117:5244),并点击底部登录管理后台。
2.2在左侧菜单栏,点击 设置 (Settings)。
2.3在顶部的选项卡里,找到并点击 其他 (Other) 标签页。
2.4往下划,找到关于 Aria2 的设置框,对照着填:
- Aria2 URI:填 http://127.0.0.1:6800/jsonrpc (这代表让它找住在自己这台手机里的 Aria2)。
- Aria2 密钥:填 xxsky1127 (咱们刚才在代码里写好的暗号)。
2.5填完后,一定要点击底部的 保存 (Save)。
(现在,openlist 已经成功连上底层的 Aria2 苦力了!)
3.把手机本地的“电影放映室”挂载出来
虽然 Aria2 知道要把电影下到 /storage/emulated/0/Movies 这个文件夹里,但咱们还得让 openlist 把这个文件夹显示在网页上,你才能看到。
3.1还在 openlist 的管理后台,点击左侧菜单的 存储 (Storage)。
3.2点击 添加 (Add)。
3.3驱动 选择:本机存储 (Local)。
3.4在弹出的设置页面,填这两个最关键的:
- 挂载路径:填 /本地高清影院 (或者随便你起个好听的名字,这是在首页显示的文件夹名)。
- 根文件夹路径:填 /storage/emulated/0/Movies (这是 Aria2 的真实下载老巢,一字不差地复制过去)。
3.5其他的什么都不用管,直接划到最下面点击 添加 (Save)。
4.见证奇迹的终极下载测试!
所有管道都已打通,现在咱们来体验一把“全自动遥控下载”的爽快感:
4.1退出管理后台,回到 openlist 的主页 (Home)。
4.2你会看到首页多了一个名叫 “本地高清影院” 的文件夹,点进去。
4.3在这个文件夹的右下角,你会看到一个 离线下载 (Offline Download) 的按钮(或者类似云朵/箭头的图标)。
4.4点开它,把你在网上找好的任意一个磁力链接(magnet:?xt=...)粘贴进去,点击确定!
接下来会发生什么?
openlist 会立刻把这个链接顺着刚才的暗号发给后台的 Aria2。Aria2 会开始满速疯狂下载。下载完成后,电影文件会自动出现在这个“本地高清影院”的文件夹里!
二、定时任务
1.安装定时任务服务
pkg install cronie -y2.脚本
cat > ~/refresh.sh << "EOF"
#!/bin/bash
# 你的真实管理员 Token
TOKEN="openlist-4ba3bee7-2112-42bd-92e7-6f3a7c67a83a7CIOwhmfYpB9Hi8HX1NYfRJUn1iWvyPnyuoOUPC5Fqn5FGxTwNuKHwRSYCT6OZC2"
# 这里你直接写最外层的大目录就行了!不管里面嵌套多少层它都能找到
PATHS=(
"/177/177-电影"
"/177/177-动漫"
"/177/177-电视剧"
)
# 定义一个“无限深度探测”的核心引擎
scan_dir() {
local CURRENT_PATH="$1"
echo "🚀 正在刷新并探测: $CURRENT_PATH"
# 敲门获取当前目录内容
local JSON=$(curl -s -X POST "http://127.0.0.1:5244/api/fs/list" \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"path\":\"$CURRENT_PATH\",\"password\":\"\",\"page\":1,\"per_page\":0,\"refresh\":true}")
# 找出当前目录下的所有子文件夹,存起来(完美解决名字带空格的问题)
local dirs=()
while IFS= read -r line; do
if [ -n "$line" ]; then
dirs+=("$line")
fi
done < <(echo "$JSON" | jq -r '.data.content[]? | select(.is_dir==true) | .name')
# 如果里面还有文件夹,就挨个钻进去继续挖!
for folder in "${dirs[@]}"; do
sleep 2 # 停顿2秒,防止请求过快被网盘拉黑封号
scan_dir "$CURRENT_PATH/$folder"
done
}
# 开始挨个巡逻你设置的最外层大目录
for ROOT_PATH in "${PATHS[@]}"; do
echo "==================================="
echo "🌟 开始执行总任务: $ROOT_PATH"
scan_dir "$ROOT_PATH"
done
echo "==================================="
echo "🎉 所有网盘的每一个角落都已彻底刷新完毕!"
EOF3.安装“解析眼” (jq)
pkg install jq -y4.赐予执行权限
chmod +x ~/refresh.sh5.定时服务在后台活着
crond6.手动测试
~/refresh.sh7.把脚本加入定时任务(这一步会打开一个文本编辑器)
crontab -e随后进入编辑模式粘贴以下代码:
0 * * * * /data/data/com.termux/files/home/refresh.sh按Ctrl+O 和 Ctrl+X保存退出。
或者直接盲写强插大法
echo "0 * * * * /data/data/com.termux/files/home/refresh.sh" > mycron.txt
crontab mycron.txt
rm mycron.txt(这三行代码的意思是:在外面建个文本把任务写好 -> 直接强行塞给 crontab 系统 -> 销毁临时文本。干净利落,全程不需要打开任何编辑器!)
8.检查写成功没
crontab -l如果屏幕上成功显示出 0 /data/data/com.termux/files/home/refresh.sh 这一行字,那就说明定时任务已经完美生效了!
9.定时分目录分时间扫描脚本
9.1 改造脚本
cat > ~/refresh.sh << "EOF"
#!/bin/bash
# 你的真实管理员 Token
TOKEN="openlist-4ba3bee7-2112-42bd-92e7-6f3a7c67a83a7CIOwhmfYpB9Hi8HX1NYfRJUn1iWvyPnyuoOUPC5Fqn5FGxTwNuKHwRSYCT6OZC2"
# 检查有没有告诉它要去哪(如果没有路径,就罢工报错)
if [ -z "$1" ]; then
echo "❌ 错误:请告诉我你要扫哪个目录!(例如: ~/refresh.sh \"/177/177-动漫\")"
exit 1
fi
TARGET_PATH="$1"
# 核心无限深挖引擎(和之前一样)
scan_dir() {
local CURRENT_PATH="$1"
echo "🚀 正在刷新并探测: $CURRENT_PATH"
local JSON=$(curl -s -X POST "http://127.0.0.1:5244/api/fs/list" \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"path\":\"$CURRENT_PATH\",\"password\":\"\",\"page\":1,\"per_page\":0,\"refresh\":true}")
local dirs=()
while IFS= read -r line; do
if [ -n "$line" ]; then
dirs+=("$line")
fi
done < <(echo "$JSON" | jq -r '.data.content[]? | select(.is_dir==true) | .name')
for folder in "${dirs[@]}"; do
sleep 2
scan_dir "$CURRENT_PATH/$folder"
done
}
echo "==================================="
echo "🌟 接收到调度指令,开始执行: $TARGET_PATH"
scan_dir "$TARGET_PATH"
echo "🎉 专项目录任务已彻底完成!"
# 1. 踢一脚 Emby,命令它立刻开始扫描(API密钥去Emby后台生成一个填进来)
curl -X POST "http://127.0.0.1:8096/Library/Refresh?api_key=你的EmbyAPI密钥" > /dev/null
# 2. 给你的微信发通知,告诉你活儿干完了!
curl -s "http://www.pushplus.plus/send?token=你的Token&title=私人影院更新&content=云盘深度扫描已完成,Emby正在同步最新资源!" > /dev/null
# 3. # 触发 Telegram 推送
curl -s -X POST "https://api.telegram.org/bot把你的BOT_TOKEN粘贴在这里/sendMessage" -d "chat_id=把你的CHAT_ID粘贴在这里&text=🎬 您的私人影院云盘已扫描完毕,Emby正在入库!" > /dev/null
EOF或者
cat > ~/refresh.sh << "EOF"
#!/bin/bash
# 你的真实管理员 Token
TOKEN="openlist-4ba3bee7-2112-42bd-92e7-6f3a7c67a83a7CIOwhmfYpB9Hi8HX1NYfRJUn1iWvyPnyuoOUPC5Fqn5FGxTwNuKHwRSYCT6OZC2"
# 检查有没有告诉它要去哪(如果没有路径,就罢工报错)
if [ -z "$1" ]; then
echo "❌ 错误:请告诉我你要扫哪个目录!(例如: ~/refresh.sh \"/177/177-动漫\")"
exit 1
fi
TARGET_PATH="$1"
# 核心无限深挖引擎(和之前一样)
scan_dir() {
local CURRENT_PATH="$1"
echo "🚀 正在刷新并探测: $CURRENT_PATH"
local JSON=$(curl -s -X POST "http://127.0.0.1:5244/api/fs/list" \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"path\":\"$CURRENT_PATH\",\"password\":\"\",\"page\":1,\"per_page\":0,\"refresh\":true}")
local dirs=()
while IFS= read -r line; do
if [ -n "$line" ]; then
dirs+=("$line")
fi
done < <(echo "$JSON" | jq -r '.data.content[]? | select(.is_dir==true) | .name')
for folder in "${dirs[@]}"; do
sleep 2
scan_dir "$CURRENT_PATH/$folder"
done
}
echo "==================================="
echo "🌟 接收到调度指令,开始执行: $TARGET_PATH"
scan_dir "$TARGET_PATH"
echo "🎉 专项目录任务已彻底完成!"
# 让Emby立刻开始扫描(API密钥去Emby后台生成一个填进来)
curl -X POST "http://127.0.0.1:8096/Library/Refresh?api_key=fcf90c63fd1343cf9e6c66a62f73cfe5" > /dev/null
EOF9.2终极排班表
cat > mycron.txt << "EOF"
# 电影:每天 1 次 (晚上 10 点)
0 22 * * * /data/data/com.termux/files/home/refresh.sh "/177/177-电影"
# 动漫:每天 4 次 (11:30、18:30、19:30、21:30)
30 11,18,19,21 * * * /data/data/com.termux/files/home/refresh.sh "/177/177-动漫"
# 电视剧:每天 4 次 (11:00、20:00、21:00、23:30)
0 11,20,21,23 * * * /data/data/com.termux/files/home/refresh.sh "/177/177-电视剧"
EOF
crontab mycron.txt
rm mycron.txt
crontab -l9.3立刻单独扫一下动漫
~/refresh.sh "/177/177-动漫"10.电报/微信媒体通知
10.1.Termux 装上“翻译工具”
回到你的 Termux 命令行(~ $ 界面),依次执行下面两行命令:
pkg install python -ypip install flask requests(这会给系统装上 Python 和专门用来建网站接收数据的 Flask 工具包,大概需要一两分钟。)
10.2.写入咱们的“翻译官”核心代码
cat > ~/tg_bridge.py << "EOF"
from flask import Flask, request
import requests
import time
import threading
import queue
app = Flask("emby_bridge")
# --- 你的配置信息 ---
BOT_TOKEN = "7548615667:AAHn0ls4aBPKBPI2-gpwykwVdEKd0ywOlsc"
CHAT_ID = "-1002906711199"
EMBY_API_KEY = "fcf90c63fd1343cf9e6c66a62f73cfe5"
PUSHPLUS_TOKEN = "564ba5836b054b1698180dbab49892c9"
msg_queue = queue.Queue()
def worker():
while True:
try:
task = msg_queue.get()
if not task: continue
title, msg, target_img_id = task['title'], task['msg'], task['img_id']
full_msg = f"{title}\n\n{msg}"
photo_stream = None
if target_img_id and EMBY_API_KEY:
try:
img_resp = requests.get(f"http://127.0.0.1:8096/Items/{target_img_id}/Images/Primary?api_key={EMBY_API_KEY}", timeout=5)
if img_resp.status_code == 200: photo_stream = img_resp.content
except: pass
try:
if photo_stream:
requests.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendPhoto", data={'chat_id': CHAT_ID, 'caption': full_msg}, files={'photo': ('poster.jpg', photo_stream, 'image/jpeg')}, timeout=15)
else:
requests.get(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage", params={"chat_id": CHAT_ID, "text": full_msg}, timeout=10)
except: pass
if PUSHPLUS_TOKEN:
try: requests.get("http://www.pushplus.plus/send", params={"token": PUSHPLUS_TOKEN, "title": title, "content": msg}, timeout=5)
except: pass
time.sleep(3)
msg_queue.task_done()
except Exception:
time.sleep(3)
threading.Thread(target=worker, daemon=True).start()
@app.route('/emby', methods=['POST'])
def receive_emby():
data = request.json
if not data: return "OK", 200
if data.get('Title') == 'Test Notification':
msg_queue.put({'title': "系统测试", 'msg': "✅ 报告老板:排队双通道测试成功!", 'img_id': None})
return "OK", 200
if 'Item' not in data: return "OK", 200
item = data['Item']
item_type = item.get('Type', '')
# ==========================================
# 🌟 核心修改区:随心所欲的“文案精装修”
# ==========================================
if item_type == 'Movie':
title = "🎬 电影入库啦!"
msg = f"《{item.get('Name', '未知电影')}》 ({item.get('ProductionYear', '')})\n✨ 已准备就绪,随时可以观看!"
elif item_type == 'Series':
title = "📺 新剧开播啦!"
msg = f"《{item.get('Name', '未知剧集')}》 ({item.get('ProductionYear', '')})\n✨ 已全量建档,快来刷剧!"
elif item_type == 'Season':
return "OK", 200
elif item_type == 'Episode':
title = "📺 追剧更新啦!"
# 提取更详细的信息
series_name = item.get('SeriesName', '未知剧集')
season_name = item.get('SeasonName', '') # 比如:第 1 季
ep_name = item.get('Name', '') # 比如:第 2 集,或者一串文件名
# 智能拼接:如果有季名,就拼上季名
if season_name:
msg = f"《{series_name}》 {season_name} - {ep_name}\n✨ 已自动入库!"
else:
msg = f"《{series_name}》 - {ep_name}\n✨ 已自动入库!"
else:
title = "✨ 影院上新"
msg = f"【{item.get('Name', '未知内容')}】 已准备就绪!"
# ==========================================
target_img_id = item.get('SeriesId') if (item_type == 'Episode' and item.get('SeriesId')) else item.get('Id')
msg_queue.put({'title': title, 'msg': msg, 'img_id': target_img_id})
return "OK", 200
app.run(host='0.0.0.0', port=8000)
EOF或者监控播放与入库通知
cat > ~/tg_bridge.py << "EOF"
from flask import Flask, request
import requests
import time
import threading
import queue
app = Flask("emby_bridge")
# ==========================================
# --- 你的配置信息 ---
# ==========================================
BOT_TOKEN = "7548615667:AAHn0ls4aBPKBPI2-gpwykwVdEKd0ywOlsc"
CHAT_ID = "-1002906711199"
EMBY_API_KEY = "fcf90c63fd1343cf9e6c66a62f73cfe5"
PUSHPLUS_TOKEN = "564ba5836b054b1698180dbab49892c9"
# 🌟 老板免打扰特权账号(填你在Emby里看剧用的名字,注意大小写)
# 比如你日志里出现的 Xxsky
BOSS_NAME = "xxsky"
# ==========================================
msg_queue = queue.Queue()
def worker():
while True:
try:
task = msg_queue.get()
if not task: continue
title, msg, target_img_id = task['title'], task['msg'], task['img_id']
full_msg = f"{title}\n\n{msg}"
photo_stream = None
if target_img_id and EMBY_API_KEY:
try:
img_resp = requests.get(f"http://127.0.0.1:8096/Items/{target_img_id}/Images/Primary?api_key={EMBY_API_KEY}", timeout=5)
if img_resp.status_code == 200: photo_stream = img_resp.content
except: pass
try:
if photo_stream:
requests.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendPhoto", data={'chat_id': CHAT_ID, 'caption': full_msg}, files={'photo': ('poster.jpg', photo_stream, 'image/jpeg')}, timeout=15)
else:
requests.get(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage", params={"chat_id": CHAT_ID, "text": full_msg}, timeout=10)
except: pass
if PUSHPLUS_TOKEN:
try: requests.get("http://www.pushplus.plus/send", params={"token": PUSHPLUS_TOKEN, "title": title, "content": msg}, timeout=5)
except: pass
time.sleep(3)
msg_queue.task_done()
except Exception:
time.sleep(3)
threading.Thread(target=worker, daemon=True).start()
@app.route('/emby', methods=['POST'])
def receive_emby():
data = request.json
if not data: return "OK", 200
if data.get('Title') == 'Test Notification':
msg_queue.put({'title': "系统测试", 'msg': "✅ 报告老板:入库与播放双通道测试成功!", 'img_id': None})
return "OK", 200
if 'Item' not in data: return "OK", 200
item = data['Item']
item_type = item.get('Type', '')
# 🌟 获取事件类型 和 播放用户的名字
event = data.get('Event', '')
user_name = data.get('User', {}).get('Name', '某人')
# 1. 过滤无关事件,只保留“新入库”和“开始播放”
if event not in ['library.new', 'playback.start']:
return "OK", 200
# 2. 🌟 老板免打扰拦截:如果是老板在播放,假装没看见,直接下班!
if event == 'playback.start' and user_name == BOSS_NAME:
return "OK", 200
# ==========================================
# 场景一:【新片入库】
# ==========================================
if event == 'library.new':
if item_type == 'Movie':
title, msg = "🎬 电影入库啦!", f"《{item.get('Name')}》 ({item.get('ProductionYear', '')})\n✨ 已准备就绪!"
elif item_type == 'Series':
title, msg = "📺 新剧开播啦!", f"《{item.get('Name')}》 ({item.get('ProductionYear', '')})\n✨ 已全量建档!"
elif item_type == 'Season': return "OK", 200
elif item_type == 'Episode':
season = item.get('SeasonName', '')
ep = item.get('Name', '')
ep_text = f"{season} - {ep}" if season else ep
title, msg = "📺 追剧更新啦!", f"《{item.get('SeriesName')}》 {ep_text}\n✨ 已自动入库!"
else:
title, msg = "✨ 影院上新", f"【{item.get('Name')}】 已准备就绪!"
# ==========================================
# 场景二:【别人在播放】
# ==========================================
elif event == 'playback.start':
title = "▶️ 播放监控"
if item_type == 'Episode':
season = item.get('SeasonName', '')
ep = item.get('Name', '')
ep_text = f"{season} - {ep}" if season else ep
msg = f"👤 【{user_name}】 正在观看:\n《{item.get('SeriesName')}》 {ep_text}"
else:
msg = f"👤 【{user_name}】 正在观看:\n《{item.get('Name')}》"
target_img_id = item.get('SeriesId') if (item_type == 'Episode' and item.get('SeriesId')) else item.get('Id')
msg_queue.put({'title': title, 'msg': msg, 'img_id': target_img_id})
return "OK", 200
app.run(host='0.0.0.0', port=8000)
EOF10.3.静默启动
nohup python ~/tg_bridge.py > /dev/null 2>&1 &或启动
python ~/tg_bridge.py杀掉进程
pkill -f tg_bridge.py或
pkill -9 -f tg_bridge.pypkill -9 python10.4Emby通知设置
设置-admin首选项-通知-添加通知-webhooks
名称:电报/微信入库通知
网址:http://127.0.0.1:8000/emby
请求内容类型:application/json
勾选媒体库
自动转存
1.Termux安装必要的 Python 引擎库
pip install flask requests2.创建你的“追更小本本
touch ~/list.txt3.创建并写入追更核心代码
cat > ~/auto_sync.py << 'EOF'
import requests, time, os, datetime
# ==========================================
ALIST_URL = "http://127.0.0.1:5244"
USERNAME = "admin"
PASSWORD = "你的密码填这里"
# ==========================================
VIDEO_EXTS = ['.mp4', '.mkv', '.ts', '.strm', '.avi', '.rmvb']
def get_token():
try:
res = requests.post(f"{ALIST_URL}/api/auth/login", json={"username": USERNAME, "password": PASSWORD}).json()
return res["data"]["token"] if res.get("code") == 200 else None
except: return None
def get_storages(token):
res = requests.get(f"{ALIST_URL}/api/admin/storage/list", headers={"Authorization": token}).json()
if res.get("code") == 200: return [item['mount_path'] for item in res["data"]["content"]]
return []
def auto_add_storage(token, name, link, pwd):
mount_path = f"/自动追更/{name}"
# 👇 核心防撞墙逻辑
if "189.cn" in link:
print(f" ❌ 致命限制: OpenList 不支持挂载天翼云分享链接,跳过!")
return None
elif "123pan" in link:
driver, addition = "123Pan Share", f'{{"share_link":"{link}","share_pwd":"{pwd}"}}'
else:
print(f" ❌ 无法识别的分享链接: {link}")
return None
payload = {"mount_path": mount_path, "order": 0, "driver": driver, "cache_expiration": 30, "status": "work", "webdav_policy": "302_redirect", "addition": addition}
res = requests.post(f"{ALIST_URL}/api/admin/storage/create", headers={"Authorization": token}, json=payload).json()
if res.get("code") == 200:
print(f" ✅ 挂载成功: {mount_path}")
return mount_path
else:
print(f" ❌ 挂载失败! 报错原话: {res.get('message')}")
return None
def mkdir(token, path):
requests.post(f"{ALIST_URL}/api/fs/mkdir", headers={"Authorization": token}, json={"path": path})
def get_items(token, path):
res = requests.post(f"{ALIST_URL}/api/fs/list", headers={"Authorization": token}, json={"path": path, "password": "", "page": 1, "per_page": 0, "refresh": True}).json()
if res.get("code") == 200 and res.get("data"):
return res["data"].get("content") or []
return []
def copy_files(token, src_dir, dst_dir, file_names):
payload = {"src_dir": src_dir, "dst_dir": dst_dir, "names": file_names}
res = requests.post(f"{ALIST_URL}/api/fs/copy", headers={"Authorization": token}, json=payload).json()
return res.get("code") == 200
def sync_recursive(token, src_path, dst_path):
mkdir(token, dst_path)
print(f" 🔍 正在扫描目录: {src_path}")
src_items = get_items(token, src_path)
print(f" 👀 发现 {len(src_items)} 个项目")
dst_items = get_items(token, dst_path)
dst_names = [i['name'] for i in dst_items]
files_to_copy = []
for item in src_items:
if item['is_dir']:
print(f" 📂 钻入子文件夹: {item['name']} ...")
sync_recursive(token, f"{src_path}/{item['name']}", f"{dst_path}/{item['name']}")
else:
ext = os.path.splitext(item['name'])[1].lower()
if ext not in VIDEO_EXTS: pass
elif item['name'] in dst_names:
print(f" ⏭️ 跳过: {item['name']} (网盘已存在)")
else:
print(f" ✅ 锁定新视频: {item['name']}")
files_to_copy.append(item['name'])
if files_to_copy:
print(f" 🎉 正在火速搬运 {len(files_to_copy)} 个视频到 {dst_path} ...")
copy_files(token, src_path, dst_path, files_to_copy)
time.sleep(2)
def run_sync():
print(f"\n🚀 [{time.strftime('%Y-%m-%d %H:%M:%S')}] 启动全自动深度追更...")
token = get_token()
if not token: return print("❌ OpenList 登录失败!")
existing_mounts = get_storages(token)
if not os.path.exists("/data/data/com.termux/files/home/list.txt"): return print("📝 找不到 list.txt!")
with open("/data/data/com.termux/files/home/list.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
for line in lines:
if not line.strip() or "|" not in line: continue
parts = [p.strip() for p in line.split("|")]
if len(parts) < 4: continue
name, link, pwd, target_dir = parts[0], parts[1], parts[2], parts[3]
print(f"\n👉 正在处理: 《{name}》")
source_dir = f"/自动追更/{name}"
if source_dir not in existing_mounts:
print(f" 🛠️ 准备挂载...")
if not auto_add_storage(token, name, link, pwd): continue
print(f" ⏳ 等待 15 秒让网盘获取数据...")
time.sleep(15)
sync_recursive(token, source_dir, target_dir)
if __name__ == "__main__":
while True:
try: run_sync()
except Exception as e: print(f"报错了: {e}")
now = datetime.datetime.now()
current_hour = now.hour
if 10 <= current_hour <= 12: time.sleep(3600)
elif 18 <= current_hour <= 23: time.sleep(1800)
else: time.sleep(7200)
EOF3.启动全自动追更引擎
nohup python ~/auto_sync.py > sync_log.txt 2>&1 &4.网络文本
rm -f ~/web_ui.py && cat > ~/web_ui.py << "EOF"
from flask import Flask, request, render_template_string
import os
# 🌟 安全重命名:不再使用任何双下划线
app = Flask("web_app")
FILE_PATH = "/data/data/com.termux/files/home/list.txt"
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>🎬 私人影院追更控制台</title>
<style>
body { font-family: sans-serif; padding: 20px; background: #121212; color: #ffffff; }
h2 { text-align: center; color: #00bcd4; }
textarea { width: 100%; height: 60vh; font-size: 16px; padding: 15px; background: #1e1e1e; color: #fff; border: 1px solid #333; border-radius: 8px; box-sizing: border-box; resize: none; }
button { width: 100%; height: 50px; font-size: 18px; background: #00bcd4; color: black; font-weight: bold; border: none; border-radius: 8px; margin-top: 15px; cursor: pointer; }
button:active { background: #0097a7; }
.msg { color: #4caf50; text-align: center; margin-top: 10px; font-weight: bold; }
</style>
</head>
<body>
<h2>🎬 私人影院追更控制台</h2>
<form method="POST">
<textarea name="content" placeholder="格式:剧名 | 链接 | 密码 | 目标路径">{{ content }}</textarea>
<button type="submit">💾 保存并让服务器开始转存</button>
</form>
<div class="msg">{{ msg }}</div>
</body>
</html>
"""
@app.route('/', methods=['GET', 'POST'])
def home():
message = ""
if not os.path.exists(FILE_PATH):
with open(FILE_PATH, 'w', encoding='utf-8') as f: f.write("")
if request.method == 'POST':
user_content = request.form.get('content', '')
with open(FILE_PATH, 'w', encoding='utf-8') as f:
f.write(user_content)
message = "✅ 保存成功!你可以关掉网页了。"
with open(FILE_PATH, 'r', encoding='utf-8') as f:
current_content = f.read()
return render_template_string(HTML_TEMPLATE, content=current_content, msg=message)
# 🌟 核心改动:删掉 if __name__ == "__main__",直接暴力运行!
app.run(host='0.0.0.0', port=8888)
EOF
python ~/web_ui.py静默启动
nohup python ~/web_ui.py > /dev/null 2>&1 &从电脑复制脚本
cp ~/storage/downloads/auto_sync.py ~/auto_sync.pypython ~/auto_sync.py
整理的静默启动脚本服务
# 1. 启动 OpenList (AList)
cd ~/openlist && nohup ./openlist server >/dev/null 2>&1 &
# 2. 启动 Cloudflare 隧道
nohup cloudflared tunnel --protocol http2 --config ~/.cloudflared/config.yml run emby_tunnel > ~/tunnel.log 2>&1 &
# 3. 启动 Aria2 下载器 (-D 参数就是让它在后台静默运行)
aria2c --conf-path=$HOME/.config/aria2/aria2.conf -D
# 4. 启动定时任务守护进程 (把 crontab -l 修正为了启动服务的 crond)
crond
# 5. 启动 TG 监控与入库通知脚本
nohup python ~/tg_bridge.py > /dev/null 2>&1 &
# 打印一条提示,确认执行完毕
echo "✅ 所有服务已成功在后台启动!"或者做一个“一键启动”脚本
在 Termux 里直接复制粘贴这段代码(它会自动帮你建好文件):
cat > ~/start.sh << 'EOF'
#!/bin/bash
echo "🚀 正在唤醒所有追更矩阵服务..."
cd ~/openlist && nohup ./openlist server >/dev/null 2>&1 &
echo "✅ OpenList 已启动"
nohup cloudflared tunnel --protocol http2 --config ~/.cloudflared/config.yml run emby_tunnel > ~/tunnel.log 2>&1 &
echo "✅ Cloudflare 隧道已启动"
aria2c --conf-path=$HOME/.config/aria2/aria2.conf -D
echo "✅ Aria2 已启动"
crond
echo "✅ 定时任务 (crond) 已启动"
nohup python ~/tg_bridge.py > /dev/null 2>&1 &
echo "✅ TG 监控通知已启动"
echo "🎉 矩阵全面复活!可以退出了。"
EOF给它运行权限(只需执行一次)
chmod +x ~/start.sh以后再遇到重启手机或者不小心划掉 Termux 的情况,你只需要打开 Termux,输入这一句命令就行了:
~/start.sh