订阅式自动追番1.1 - Flexget + Aria2 + Emby

实现效果

截至发文仍在使用的追番模式,从部署流程来说还是比较繁琐的,当时也没怎么做功课,各个方面也存在很多优化空间,而且针对萌番组定制的,但鉴于是1.0方案和综合使用追了三四个季度番来说效果也不错,笔记记录一下部署流程。

1.1 稍微解耦了下

萌番组 (bangumi.moe)订阅番剧,然后就坐等服务器下载->刮削->Emby观看。

其余还实现了RSS番剧更新Aria2下载完成QQ消息推送,配合Emby弹幕观看,还是挺舒服的,本文先不讲这些。

意义

追番自由,蓝狗必备!

所需工具

运作载体:

云服务器:没有的话用自己电脑也行,问题不大。本文使用的是Ubuntu 18
云服务器的话可随时待机下载,随时随地不限设备登录 EMBY观看。

信息源

萌番组:提供bt种子以及番剧信息,用到RSS订阅

软件

Flexget:获取RSS更新触发下载器自动下载,配置下载规则
Aria2:下载器,只管下
Python脚本:维护番剧库,将下载的番剧整理成EMBY能正确匹配的样子。
EMBY:在线刮削,海报墙和在线播放

从工具上往下看,基本就知道运作流程了。

步骤

服务器这块就不说了,不讲究。

Step 1 - 萌番组配置

突然想起萌番组被墙了些时日了,不过我是境外服务器也不耽误,境内的话估计要么代理要么反向代理。

  1. 注册登录之后筛选订阅,一通订阅之后如下图,因为涉及到空间和带宽流量,筛选尽可能的要细。

看看我的

  1. 提取用户Cookie

说明一下,按照正常来说本应不需要提取用户cookie,直接用个人订阅RSS即可获取所有订阅,后边在Flexget发现如果要实现每部番剧精细化管理,还是用每部番剧对应的RSS订阅比较好。
比如https://bangumi.moe/search/**615bb91fd7f73dd4ed5c4405**+**58a9c1e6f5dc363606ab42ed**,通过链接里加粗体的标签字符串,获取番剧详细信息,此处代替了本应连接tmdb获取的信息。

如图所示,在查看自己订阅信息的时候会加载出my的信息,复制右下处Cookie值备用,不过哪的cookie应该都一样,随便了。

Step 2 - Flexget配置

安装这块参考 #教程#Aria2 rss订阅下载
以及文档 Flexget 安装&配置
注意要设置定时任务获取更新。
安装的坑 #有空再更新 ;;;

软件在这里用到的是动态配置文件,需要用脚本随时修改配置文件以跟上订阅项目更新。
更新配置文件的Python程序如下,写的有点糙,能跑就行,注释有空再说。后边如果有优化方案之后可能也不需要这个了

UpdateConfig.py(更新项目时被UpdateRSS.sh调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/user/bin/python3
## 更新flexget配置文件
import os
import requests
import json
import yaml

abpath = os.path.dirname(os.path.abspath(__file__))
with open(abpath+'/auto_config.yml', 'r', encoding='utf-8') as f:
file_content = f.read()
content = yaml.load(file_content, yaml.FullLoader)
url = content['bgm']['url']
cookie = content['bgm']['cookie']
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
'referer': url,
'Cookie': cookie
}

if content["bgm"]["mirror"]:
url = content["bgm"]["mirror"]

aria_path = content["aria2"]["path"]
aria_ip = content["aria2"]["ip"]
aria_port = content["aria2"]["port"]
aria_secret = content["aria2"]["secret"]
flexget_path = content["flexget"]["path"]
flexget_webui = content["flexget"]["webui"]
rej_reg = content["flexget"]["reject"]


def get_rss():

rssurl= url + 'api/user/subscribe/collections'
rss = requests.get(rssurl, headers=headers)
rsstr= json.loads(rss.text)
if not rsstr and content["QQBot"]["QQ"]:
bot_qq = content["QQBot"]["QQ"]
bot_url = content["QQBot"]["url"]
bot_path = content["QQBot"]["path"]
purl = bot_url+bot_path
data = {'id': str(bot_qq), 'content': 'cookie可能已失效!', 'from': '萌番组'}
r = requests.post(purl, json=data)
exit(0)
return rsstr




def get_name(rss):

name = ''
rssinfo= url + 'api/tag/fetch'
data = {"_ids": rss}
info = requests.post(rssinfo, json=data, headers=headers)
infostr = json.loads(info.text)
for i in range(0, len(infostr)):
if infostr[i]['type'] == 'bangumi':
names = infostr[i]['locale']
if 'zh_cn' in names.keys():
name = names['zh_cn']
elif 'zh_tw' in names.keys():
name = names['zh_tw']
else:
name = infostr[i]['name']
return name
return name

def make_yml(rsstr, name):
in_aria2 = ' server: '+aria_ip+'\n port: '+str(aria_port)+'\n secret: "'+aria_secret+'"\n path: "'+aria_path+name+'"\n\n'
rss = url + 'rss/tags/' + '+'.join(rsstr)
f = open(flexget_path+'config.yml', 'a+', encoding = 'utf-8')
f.writelines(' '+name+':\n')
f.writelines(' rss: '+rss+'\n')
f.writelines(' if:\n')
for i in rej_reg:
f.writelines(' - "\'' + i + '\' in title": reject\n')
f.writelines(' - "\'[\'": accept\n')
# f.writelines(' accept_all: yes\n')
f.writelines(' aria2:\n')
f.writelines(in_aria2)
f.close()


def main():
temp = 'templates:\n gener:\n free_space:\n path: /root/aria2\n space: 1024000\n'
f = open(flexget_path+'config.yml', 'w', encoding = 'utf-8')

f.writelines('tasks: \n')
f.close()
rsstr = get_rss()
for i in range(0, len(rsstr)):
name = get_name(rsstr[i])
if len(name) == 0 or len(rsstr) == 0:
print("noname")
continue
print(name)
make_yml(rsstr[i], name)
f = open(flexget_path+'config.yml', 'a+', encoding = 'utf-8')
f.writelines('web_server:\n')
f.writelines(' port: '+str(flexget_webui)+'\n')
f.writelines(' web_ui: yes\n')
f.writelines('schedules: no\n')
f.close()




if __name__ == '__main__':
try:
main()
except Exception as e:
print(e)

由于flexget的一些特性,需要手动删除lock文件才能使用动态配置文件。故有下面脚本,更新配置直接运行下方脚本就行,不过也不用手动,后边要配置定时任务执行此脚本。

UpdateRSS.sh

1
2
3
4
5
6
#! /bin/bash
#自行修改路径,此处py文件为上方py脚本
python3 /root/UpdateConfig.py
#flexget默认安装路径
rm -f /root/.flexget/.config-lock
/usr/local/bin/flexget execute

添加crontab任务

1
crontab -e      #选择编辑器之后添加定时内容

内容参考

1
2
*/5 * * * * /usr/local/bin/flexget --cron execute
0 */6 * * * /root/autobgm/Updaterss.sh

意思
5分钟检查一次RSS更新 (RSS检测频率,争对已有订阅)
6个小时刷新一下配置文件(例如添加了新的订阅)

Step 3 - Aria2安装

建议一键脚本,直接一步到位。
P3TERX/aria2.sh: Aria2 一键安装管理脚本 增强版 (github.com)
直接 wget -N git.io/aria2.sh && chmod +x aria2.sh
再运行脚本 ./aria2.sh

安装且配置好密钥之后需要在aria2安装目录自带的clean.sh脚本添加东西,该脚本会默认在下载任务结束后运行。

clean.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
CHECK_CORE_FILE() {
CORE_FILE="$(dirname $0)/core"
if [[ -f "${CORE_FILE}" ]]; then
. "${CORE_FILE}"
else
echo "!!! core file does not exist !!!"
exit 1
fi
}
#自定义
PUSHTOQQ(){

if ! echo "${TASK_FILE_NAME}" | grep -q -E '\.torrent$';then

/usr/bin/python3 /root/autobgm/QQPush.py "${DOWNLOAD_DIR}" "${TASK_FILE_NAME}";fi

}

CHECK_CORE_FILE "$@"
CHECK_PARAMETER "$@"
CHECK_FILE_NUM
CHECK_SCRIPT_CONF
GET_TASK_INFO
GET_DOWNLOAD_DIR
CONVERSION_PATH
#运行自定义
PUSHTOQQ
CLEAN_UP
exit 0

到这里基本就行了。

可添加下载完删种脚本,以及下载完推送。

Step 4 - py脚本维护番剧库

如果一切顺利的话,此时番剧库已经可以接受更新并分类下载进各自的文件夹了。此时为了能匹配EMBY所以还得写个脚本维护此库。
注释再说,能跑就行。。。

bgminfo.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/user/bin/python3
import os
import re
import sys
import json
import shutil
import yaml


abpath = os.path.dirname(os.path.abspath(__file__))
with open(abpath+'/auto_config.yml', 'r', encoding='utf-8') as f:
file_content = f.read()
content = yaml.load(file_content, yaml.FullLoader)
path = content['aria2']['path']

pattern0 = re.compile(r'Season (\d+)')
pattern1 = re.compile(r'\[(\d+)\]')
pattern2 = re.compile(r'\[(\d+)v')
pattern5 = re.compile(r'\[(\d+)集')
pattern3 = re.compile(r'第(\d+)話')
pattern4 = re.compile(r'S\d+E(\d+)')
pattern6 = re.compile(r'- (\d+) ')
patternlist = [pattern1, pattern2, pattern3, pattern4, pattern5, pattern6]

def find_ep(file):
for pattern in patternlist:
ep = re.findall(pattern, file)
if len(ep) == 1:
return int(ep[0])

return -1


def find_maxep(path3):
maxep = -1
for root, dirs, files in os.walk(path3):
for file in files:

ep = find_ep(file)
if ep > maxep:
maxep = ep

maxep = str(maxep).zfill(2)
return maxep

def find_season(path):
maxss = 1
for dir in os.listdir(path):
if os.path.isfile(path+dir):
continue
seasonn = re.findall(pattern0, dir)
if len(seasonn) == 1 and seasonn[0].isdigit() and int(seasonn[0]) > maxss:
maxss = int(seasonn[0])
season = str(maxss).zfill(2)
return season



def rename(title, bgminfo):
list1 = ['.mkv', '.MKV', '.mp4']
season = bgminfo['season'].zfill(2)
path2 = path + title + '/Season ' + season
if not os.path.exists(path2):
os.mkdir(path2)
for file in os.listdir(path+title):
if os.path.isdir(path+title+file):
continue
ext = os.path.splitext(file)[1]
if ext in list1:
ep = find_ep(file)
if not ep == -1:
desfile = title + ' - S' + season + 'E' + str(ep).zfill(2) +ext

src = path+title+'/'+file
des = path2+'/'+desfile
try:
os.remove(des)
except OSError:
pass

print(src+ '------------>'+ des)
shutil.move(src, des)


def main():

bgm_info = {}
data = json.loads(json.dumps(bgm_info))
for dir in os.listdir(path):
if os.path.isfile(path+dir):
continue

maxep = find_maxep(path+dir)
info = {'title': dir, 'year': '', 'maxep': maxep, 'season': ''}
if os.path.exists(path+'bgminfo.json'):
with open(path+'bgminfo.json','r') as g:
bginfo = json.load(g)
if not dir in bginfo:
season = find_season(path+dir)
info['season'] = season
data[dir] = info
continue


info['season'] = bginfo[dir]['season']

if not bginfo[dir]['year'] == '':
info['year'] = bginfo[dir]['year']
# if not dir == newtitle:
# src = os.path.join(path, dir)
# des = os.path.join(path, newtitle)
# os.rename(src,des)
# flag = 1
# data[newtitle] = info
# if not flag == 1:
data[dir] = info
result = json.dumps(data, ensure_ascii=False, indent=4)
with open(path+'bgminfo.json', 'w', newline='\n') as f:
f.write(result)
if len(sys.argv) == 1:
for title in data:
rename(title, data[title])
else:
title = sys.argv[1]
rename(title,data[title])



if __name__ == '__main__':
main()

Step 5 - 配置文件

autobgm.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bgm: #萌番组
  url: https://bangumi.moe/
  mirror:  #镜像url
  cookie: #yourcookie
flexget:
  path: /root/.flexget/  #配置目录
  webui: 8666      #webui端口
  reject:           #过滤规则
    - '[cht'
    - '720p'
  secret: #webui登录密码好像也没用到
aria2:
  ip: 127.0.0.1            #本机
  port: 6800               #端口
  path: /root/aria2/bgmoe/ #番剧库路径
  secret:        #密钥
QQBot: #推送用的,不填
  QQ:
  url:
  path:

没说明可不填皆必填,

启动UpdateRSS.sh

将以上所有文件放入同个文件夹中,如本文是在(/root/autobgm/),运行UpdateRSS.sh即可。

不出意外的话这是你的文件目录,脚本会自动生成json记录更新番剧信息。

以及

Step 6 - EMBY配置

ε=(´ο`*)))唉,默认会了吧,安装之后把你的番剧库添加进媒体库就行了,其他等着自动扫描就行了,最多遇到点网络问题连不上tmdb,那个改改host就行。
上效果图吧

缺点

  1. 部署麻烦,逻辑也挺乱
  2. 萌番组的cookie几个月会过期一次得手动更新
  3. 萌番组有些字幕组的番剧标签上的有点随便,几个季度混在一起
  4. 默认第一季,若不是第一季的话得手动去bgminfo.json修改季度信息
  5. 绑定了萌番组