├── .gitignore
├── .gitmodules
├── README.md
├── images
├── 1.png
├── 2.png
├── 3.png
├── bejson.png
├── indexer.png
└── site.png
├── logo
├── nas-tools-black.jpg
└── nas-tools-blue.jpg
├── package
├── install.sh
├── nas-tools-plugin.tar
├── nas-tools.tar
└── unload.sh
├── plugins
├── cookiecloud.py
├── custombrush.py
├── customindexer.py
├── dailylinkrunner.py
├── doubansync.py
├── editsignin.py
├── images
│ ├── actor.png
│ ├── brush.png
│ ├── customindexer.png
│ ├── douban.png
│ ├── editsignin.png
│ ├── invites.png
│ ├── iyuu.png
│ ├── jackett.png
│ ├── link.png
│ ├── prowlarr.png
│ ├── syncindexer.png
│ └── tag.png
├── invitessignin.py
├── iyuuautoseedenhance.py
├── jackett.py
├── personmeta.py
├── prowlarr.py
├── syncindexer.py
└── torrentmark.py
├── sites
├── api.m-team.cc.json
├── api.m-team.io.json
├── bin
│ ├── hh.user.sites.bin
│ ├── mp.user.sites.bin
│ └── user.sites.bin
├── brush
│ ├── api.m-team.cc.json
│ ├── api.m-team.io.json
│ ├── discfan.net.json
│ ├── fsm.name.json
│ ├── haidan.video.json
│ ├── hdarea.club.json
│ ├── hdfans.org.json
│ ├── hdfun.me.json
│ ├── hdvbits.com.json
│ ├── hhanclub.top.json
│ ├── open.cd.json
│ ├── pandapt.net.json
│ ├── ptcafe.club.json
│ ├── ptchdbits.co.json
│ ├── ptlsp.com.json
│ ├── rousi.zip.json
│ ├── shadowflow.org.json
│ ├── skyey2.com.json
│ ├── star-space.net.json
│ ├── ubits.club.json
│ └── xp.m-team.io.json
├── discfan.net.json
├── fsm.name.json
├── haidan.video.json
├── hdarea.club.json
├── hdfans.org.json
├── hdfun.me.json
├── hdvbits.com.json
├── hhanclub.top.json
├── open.cd.json
├── pandapt.net.json
├── ptcafe.club.json
├── ptchdbits.co.json
├── ptlsp.com.json
├── rousi.zip.json
├── shadowflow.org.json
├── skyey2.com.json
├── star-space.net.json
├── xp.m-team.io.json
└── 规则.md
└── source.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/**
2 | *.pyc
3 | *.c
4 | /test.py
5 | /setup.py
6 | /build_sites.py
7 | /web/backend/user.py
8 | /build/**
9 | /venv/**
10 | /cloudflarespeedtest/**
11 | */**/*.db
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "third_party/feapder"]
2 | path = third_party/feapder
3 | url = https://github.com/jxxghp/feapder
4 | [submodule "third_party/qbittorrent-api"]
5 | path = third_party/qbittorrent-api
6 | url = https://github.com/rmartin16/qbittorrent-api
7 | [submodule "third_party/anitopy"]
8 | path = third_party/anitopy
9 | url = https://github.com/igorcmoura/anitopy
10 | [submodule "third_party/plexapi"]
11 | path = third_party/plexapi
12 | url = https://github.com/pkkid/python-plexapi
13 | [submodule "third_party/transmission-rpc"]
14 | path = third_party/transmission-rpc
15 | url = https://github.com/trim21/transmission-rpc
16 | [submodule "third_party/slack_bolt"]
17 | path = third_party/slack_bolt
18 | url = https://github.com/slackapi/bolt-python
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 第三方插件商店拓展包
2 | - 后续更新将通过系统内部的更新检测功能从 [mattoids/nas-tools](https://github.com/Mattoids/nas-tools) 仓库拉取
3 | - 该仓库后续仅提供插件安装和更新服务,不在提供系统级别的更新
4 | - 若该仓库无法正常拉取,可使用 [gitee](https://gitee.com/Mattoid/nas-tools-plugin) 仓库进行拉取
5 |
6 | ## 目录介绍
7 | ~~~
8 | .
9 | ├── logo # nastool的logo
10 | ├── package
11 | │ ├── install.sh # 一键安装脚本
12 | │ └── nas-tools-plugin.tar # 拓展更新包
13 | ├── plugins # 新增插件
14 | │ ├── images # 插件对应的图片
15 | ├── sites # 站点规则目录(用于自定义索引器配置索引规则)
16 | │ ├── brush # 刷流规则(不配置的话刷流页面站点无法选择免费种子)
17 | ├── README.md # 说明文件
18 | └── source.json # 源地址配置
19 |
20 | ~~~
21 |
22 | ## 安装方法
23 |
24 | 根据系统执行对应的命令即可,`需要root权限`执行。
25 | 也可下载 `shell` 脚本,从脚本内提取安装包,自行替换 `nas-tools` 目录
26 |
27 | ### docker shell(容器内部执行)
28 | ~~~shell
29 | # 安装
30 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/install.sh) dockershell
31 |
32 | # 卸载
33 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/unload.sh) dockershell
34 | ~~~
35 |
36 | ### docker(容器外部执行)
37 | ~~~shell
38 | # 安装
39 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/install.sh) docker
40 |
41 | # 卸载
42 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/unload.sh) docker
43 | ~~~
44 |
45 | ### dsm7
46 | ~~~shell
47 | # 安装
48 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/install.sh) dsm7
49 |
50 | # 卸载
51 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/unload.sh) dsm7
52 | ~~~
53 |
54 | ### dsm6
55 | ~~~shell
56 | # 安装
57 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/install.sh) dsm6
58 |
59 | # 卸载
60 | bash <(curl -s https://github.com/Mattoids/nas-tools-plugin/raw/master/package/unload.sh) dsm6
61 | ~~~
62 |
63 | ### 脚本运行报错的时候尝试下面的命令
64 | ~~~
65 | # 安装
66 | curl -O https://github.com/Mattoids/nas-tools-plugin/raw/master/package/install.sh && chmod 655 install.sh && ./install.sh docker
67 |
68 | # 卸载
69 | curl -O https://github.com/Mattoids/nas-tools-plugin/raw/master/package/unload.sh && chmod 655 unload.sh && ./unload.sh docker
70 | ~~~
71 |
72 |
73 | ## 第三方源
74 | ~~~
75 | https://github.com/Mattoids/nas-tools-plugin/raw/master/source.json
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 | ### 打开 json 格式化网址
105 |
106 | ~~~
107 | 1. 复制上一步输入框的内容
108 | 2. 打开解析站点
109 | 3. 粘贴到网站中点【格式化校验】和【Unicode转中文】,修改name为你添加的站点的中文名(随便写也没关系)
110 | 4. 点击 【压缩】和【中文转Unicode】,然后复制站点里面的内容
111 | 5. 粘贴回上一步的框中(把原有的删除以后再粘贴)
112 | 6. 保存
113 | 7. 你可以去索引器里面找你的站点了
114 | ~~~
115 | 
116 |
--------------------------------------------------------------------------------
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/images/1.png
--------------------------------------------------------------------------------
/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/images/2.png
--------------------------------------------------------------------------------
/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/images/3.png
--------------------------------------------------------------------------------
/images/bejson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/images/bejson.png
--------------------------------------------------------------------------------
/images/indexer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/images/indexer.png
--------------------------------------------------------------------------------
/images/site.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/images/site.png
--------------------------------------------------------------------------------
/logo/nas-tools-black.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/logo/nas-tools-black.jpg
--------------------------------------------------------------------------------
/logo/nas-tools-blue.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/logo/nas-tools-blue.jpg
--------------------------------------------------------------------------------
/package/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 允许使用的版本
4 | VERSION="3.2.3"
5 | # 临时目录
6 | TMP_PATH=/tmp/nas-tools
7 | # 套件目录
8 | KIT_PATH=/var/packages/NASTool/target
9 | # 修复包的下载地址
10 | WODNLOAD_URL=https://gitee.com/Mattoid/nas-tools-plugin/raw/master/package/nas-tools-plugin.tar
11 |
12 | # 清理临时目录
13 | clone_tmp() {
14 | echo "清理临时目录"
15 | rm -rf $TMP_PATH
16 | }
17 |
18 | # 下载修复文件
19 | download_file() {
20 | # 创建临时目录
21 | mkdir -p $TMP_PATH
22 |
23 | echo "下载文件..."
24 | curl -o "$TMP_PATH/nas-tools-plugin.tar" $WODNLOAD_URL
25 | }
26 |
27 | # 解压文件
28 | unzip_file() {
29 | echo "解压文件到临时目录"
30 | tar -xf "$TMP_PATH/nas-tools-plugin.tar" -C $TMP_PATH
31 | if [ ! -d "$TMP_PATH/nas-tools-plugin" ]; then
32 | echo "解压失败,终止任务!"
33 | exit 1
34 | fi
35 | }
36 |
37 | kit_install() {
38 | echo "开始安装插件..."
39 | cp -R $TMP_PATH/nas-tools-plugin/nas-tools $KIT_PATH
40 | chmod -R 777 $KIT_PATH/nas-tools
41 | chown -R NASTool:NASTool $KIT_PATH/nas-tools
42 | echo "插件安装成功,请去套件中心重启套件!"
43 | }
44 |
45 | # Docker容器内部处理
46 | dockershell_install() {
47 | KIT_PATH=/
48 |
49 | # 获取 nas-tools 的版本信息
50 | if [ ! -e "/nas-tools/version.py" ]; then
51 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
52 | exit 1
53 | fi
54 | NASTOOL_VERSION=$(cat "/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
55 | if [ -z $NASTOOL_VERSION ]; then
56 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
57 | exit 1
58 | fi
59 |
60 | # 下载并解压修复包
61 | download_file
62 | unzip_file
63 |
64 | # 检查套件版本
65 | if [ -n $NASTOOL_VERSION ] && [ $NASTOOL_VERSION != $VERSION ]; then
66 | echo "套件版本不要符合安装要求,不支持安装!"
67 | exit 1
68 | fi
69 |
70 | echo "开始安装插件..."
71 | cp -R $TMP_PATH/nas-tools-plugin/nas-tools $KIT_PATH
72 | chmod -R 777 $KIT_PATH/nas-tools
73 | chown -R root:root $KIT_PATH/nas-tools
74 | echo "插件安装成功,请重启docker容器哦!"
75 | }
76 |
77 | # 处理Docker
78 | docker_install() {
79 | echo "开始处理 Docker 容器..."
80 | WAIT_DOCKER_IDS=()
81 |
82 | # 获取所有已启动的 nas-tools 镜像生成的容器
83 | NASTOOL_IDS=$(docker ps --format "{{.ID}} {{.Image}}" | egrep "nas-tools|nas-tool|nastools|nastool" | awk '{ print $1 }')
84 |
85 | for NASTOOL_ID in $NASTOOL_IDS
86 | do
87 | echo "检查容器 $NASTOOL_ID 是否合法"
88 | # 获取 nas-tools 的版本信息
89 | NASTOOL_VERSION=$(docker exec $NASTOOL_ID cat version.py | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
90 | if [ -z $NASTOOL_VERSION ]; then
91 | echo "容器 $NASTOOL_ID 不合法,请先停止该容器后再执行命令"
92 | exit 1
93 | fi
94 | # 获取版本大于3.0的容器ID
95 | if [ -n $NASTOOL_VERSION ] && [ "$NASTOOL_VERSION" = "$VERSION" ]; then
96 | WAIT_DOCKER_IDS[${#WAIT_DOCKER_IDS[*]}]=$NASTOOL_ID
97 | fi
98 | done
99 |
100 | # 是否有符合条件的 nas-tools 容器
101 | if [ -n WAIT_DOCKER_IDS ] && [ ${#WAIT_DOCKER_IDS[*]} -eq 0 ] ; then
102 | echo "没有找到 nas-tools 容器"
103 | exit 1
104 | fi
105 | if [ -n WAIT_DOCKER_IDS ] && [ ${#WAIT_DOCKER_IDS[*]} -gt 0 ]; then
106 | echo "找到 ${#WAIT_DOCKER_IDS[*]}个容器"
107 | fi
108 | echo "开始安装插件!"
109 |
110 | download_file
111 | unzip_file
112 |
113 | for DOCKER_ID in ${WAIT_DOCKER_IDS[*]}
114 | do
115 | echo "容器:$DOCKER_ID 开始处理..."
116 | docker cp -a "$TMP_PATH/nas-tools-plugin/nas-tools" $DOCKER_ID:/
117 | echo "容器:$DOCKER_ID 重启中..."
118 | docker stop $DOCKER_ID
119 | echo "容器:$DOCKER_ID 正在启动..."
120 | docker start $DOCKER_ID
121 | echo "容器:$DOCKER_ID 安装完成!"
122 | done
123 |
124 | }
125 |
126 | dsm7_install() {
127 | KIT_PATH=/var/packages/NASTool/target
128 |
129 | # 获取 nas-tools 的版本信息
130 | if [ ! -e "$KIT_PATH/nas-tools/version.py" ]; then
131 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
132 | exit 1
133 | fi
134 | NASTOOL_VERSION=$(cat "$KIT_PATH/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
135 | if [ -z $NASTOOL_VERSION ]; then
136 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
137 | exit 1
138 | fi
139 |
140 | # 下载并解压修复包
141 | download_file
142 | unzip_file
143 |
144 | chmod -R 777 $KIT_PATH/config/plugins
145 | chown -R NASTool:NASTool $KIT_PATH/config/plugins
146 |
147 | # 检查套件版本
148 | if [ -n $NASTOOL_VERSION ] && [ $NASTOOL_VERSION != $VERSION ]; then
149 | echo "套件版本不要符合安装要求,不支持安装!"
150 | exit 1
151 | fi
152 |
153 | kit_install
154 | }
155 |
156 | dsm6_install() {
157 | KIT_PATH=/var/packages/NASTool/target
158 |
159 | # 获取 nas-tools 的版本信息
160 | if [ ! -e "$KIT_PATH/nas-tools/version.py" ]; then
161 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
162 | exit 1
163 | fi
164 | NASTOOL_VERSION=$(cat "$KIT_PATH/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
165 | if [ -z $NASTOOL_VERSION ]; then
166 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
167 | exit 1
168 | fi
169 |
170 | # 下载并解压修复包
171 | download_file
172 | unzip_file
173 |
174 | chmod -R 777 $KIT_PATH/config/plugins
175 | chown -R NASTool:NASTool $KIT_PATH/config/plugins
176 |
177 | # 检查套件版本
178 | NASTOOL_VERSION=$(cat "$KIT_PATH/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
179 | if [ -z $NASTOOL_VERSION ] && [ $NASTOOL_VERSION != $VERSION ]; then
180 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
181 | exit 1
182 | fi
183 |
184 | kit_install
185 | }
186 |
187 |
188 |
189 | if [ -z "$1" ]; then
190 | echo "请输入要操作的类型"
191 | exit 1
192 | fi
193 |
194 | # 开始执行脚本
195 | eval "$1_install"
196 | clone_tmp
197 |
--------------------------------------------------------------------------------
/package/nas-tools.tar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/package/nas-tools.tar
--------------------------------------------------------------------------------
/package/unload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 临时目录
4 | TMP_PATH=/tmp/nas-tools
5 | # 套件目录
6 | KIT_PATH=/var/packages/NASTool/target
7 | # 修复包的下载地址
8 | WODNLOAD_URL='http://mattoid.cn:8888/?explorer/share/file&hash=e0faFLfozzNQ2CcJcVI2RjUaWt-1JaoMGdWO9LINj3FcN8M6rCtBtoHzRwrPDWTwHhs'
9 |
10 | # 清理临时目录
11 | clone_tmp() {
12 | echo "清理临时目录"
13 | rm -rf $TMP_PATH
14 | }
15 |
16 | # 下载修复文件
17 | download_file() {
18 | # 创建临时目录
19 | mkdir -p $TMP_PATH
20 |
21 | echo "下载文件..."
22 | curl -o "$TMP_PATH/nas-tools.tar" $WODNLOAD_URL
23 | }
24 |
25 | # 解压文件
26 | unzip_file() {
27 | echo "解压文件到临时目录"
28 | tar -xf "$TMP_PATH/nas-tools.tar" -C $TMP_PATH
29 | if [ ! -d "$TMP_PATH/nas-tools" ]; then
30 | echo "解压失败,终止任务!"
31 | exit 1
32 | fi
33 | }
34 |
35 | kit_unload() {
36 | echo "开始卸载插件..."
37 | cp -R $TMP_PATH/nas-tools $KIT_PATH
38 | chmod -R 777 $KIT_PATH/nas-tools
39 | chown -R NASTool:NASTool $KIT_PATH/nas-tools
40 | echo "插件卸载成功,请去套件中心重启套件!"
41 | }
42 |
43 | # Docker容器内部处理
44 | dockershell_unload() {
45 | KIT_PATH=/
46 |
47 | # 获取 nas-tools 的版本信息
48 | if [ ! -e "/nas-tools/version.py" ]; then
49 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
50 | exit 1
51 | fi
52 |
53 | # 下载并解压修复包
54 | download_file
55 | unzip_file
56 |
57 | echo "开始卸载插件..."
58 | cp -R $TMP_PATH/nas-tools $KIT_PATH
59 | chmod -R 777 $KIT_PATH/nas-tools
60 | chown -R root:root $KIT_PATH/nas-tools
61 | echo "插件卸载成功,请重启docker容器哦!"
62 | }
63 |
64 | # 处理Docker
65 | docker_unload() {
66 | echo "开始处理 Docker 容器..."
67 | WAIT_DOCKER_IDS=()
68 |
69 | # 获取所有已启动的 nas-tools 镜像生成的容器
70 | NASTOOL_IDS=$(docker ps --format "{{.ID}} {{.Image}}" | egrep "nas-tools|nas-tool|nastools|nastool" | awk '{ print $1 }')
71 |
72 | for NASTOOL_ID in $NASTOOL_IDS
73 | do
74 | echo "检查容器 $NASTOOL_ID 是否合法"
75 | # 获取 nas-tools 的版本信息
76 | NASTOOL_VERSION=$(docker exec $NASTOOL_ID cat version.py | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
77 | if [ -z $NASTOOL_VERSION ]; then
78 | echo "容器 $NASTOOL_ID 不合法,请先停止该容器后再执行命令"
79 | exit 1
80 | fi
81 | # 获取版本大于3.0的容器ID
82 | if [ -n $NASTOOL_VERSION ]; then
83 | WAIT_DOCKER_IDS[${#WAIT_DOCKER_IDS[*]}]=$NASTOOL_ID
84 | fi
85 | done
86 |
87 | # 是否有符合条件的 nas-tools 容器
88 | if [ -n WAIT_DOCKER_IDS ] && [ ${#WAIT_DOCKER_IDS[*]} -eq 0 ] ; then
89 | echo "没有找到 nas-tools 容器"
90 | exit 1
91 | fi
92 | if [ -n WAIT_DOCKER_IDS ] && [ ${#WAIT_DOCKER_IDS[*]} -gt 0 ]; then
93 | echo "找到 ${#WAIT_DOCKER_IDS[*]}个容器"
94 | fi
95 | echo "开始安装插件!"
96 |
97 | download_file
98 | unzip_file
99 |
100 | for DOCKER_ID in ${WAIT_DOCKER_IDS[*]}
101 | do
102 | echo "容器:$DOCKER_ID 开始处理..."
103 | docker cp -a "$TMP_PATH/nas-tools" $DOCKER_ID:/
104 | echo "容器:$DOCKER_ID 重启中..."
105 | docker stop $DOCKER_ID
106 | echo "容器:$DOCKER_ID 正在启动..."
107 | docker start $DOCKER_ID
108 | echo "容器:$DOCKER_ID 卸载完成!"
109 | done
110 |
111 | }
112 |
113 | dsm7_install() {
114 | KIT_PATH=/var/packages/NASTool/target
115 |
116 | # 获取 nas-tools 的版本信息
117 | if [ ! -e "$KIT_PATH/nas-tools/version.py" ]; then
118 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
119 | exit 1
120 | fi
121 | NASTOOL_VERSION=$(cat "$KIT_PATH/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
122 | if [ -z $NASTOOL_VERSION ]; then
123 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
124 | exit 1
125 | fi
126 |
127 | # 下载并解压修复包
128 | download_file
129 | unzip_file
130 |
131 | chmod -R 777 $KIT_PATH/config/plugins
132 | chown -R NASTool:NASTool $KIT_PATH/config/plugins
133 |
134 | # 检查套件版本
135 | if [ -n $NASTOOL_VERSION ]; then
136 | echo "套件版本不要符合安装要求,不支持安装!"
137 | exit 1
138 | fi
139 |
140 | kit_unload
141 | }
142 |
143 | dsm6_unload() {
144 | KIT_PATH=/var/packages/NASTool/target
145 |
146 | # 获取 nas-tools 的版本信息
147 | if [ ! -e "$KIT_PATH/nas-tools/version.py" ]; then
148 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
149 | exit 1
150 | fi
151 | NASTOOL_VERSION=$(cat "$KIT_PATH/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
152 | if [ -z $NASTOOL_VERSION ]; then
153 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
154 | exit 1
155 | fi
156 |
157 | # 下载并解压修复包
158 | download_file
159 | unzip_file
160 |
161 | chmod -R 777 $KIT_PATH/config/plugins
162 | chown -R NASTool:NASTool $KIT_PATH/config/plugins
163 |
164 | # 检查套件版本
165 | NASTOOL_VERSION=$(cat "$KIT_PATH/nas-tools/version.py" | awk '{ print $3 }' | sed "s/'//g" | sed 's/v//g')
166 | if [ -z $NASTOOL_VERSION ]; then
167 | echo "未找到您的套件,请确认套件已正确安装在您的DSM系统中!"
168 | exit 1
169 | fi
170 |
171 | kit_unload
172 | }
173 |
174 |
175 |
176 | if [ -z "$1" ]; then
177 | echo "请输入要操作的类型"
178 | exit 1
179 | fi
180 |
181 | # 开始执行脚本
182 | eval "$1_unload"
183 | clone_tmp
184 |
--------------------------------------------------------------------------------
/plugins/cookiecloud.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | from datetime import datetime, timedelta
3 | from threading import Event
4 | from datetime import datetime
5 | from jinja2 import Template
6 |
7 | import pytz
8 | from apscheduler.schedulers.background import BackgroundScheduler
9 | from apscheduler.triggers.cron import CronTrigger
10 |
11 | from app.helper import IndexerHelper
12 | from app.plugins.modules._base import _IPluginModule
13 | from app.sites import Sites
14 | from app.utils import RequestUtils, StringUtils
15 | from config import Config
16 |
17 | class CookieCloudRunResult:
18 |
19 | def __init__(self, date=None, flag=False, msg=""):
20 | self.date = date
21 | self.flag = flag
22 | self.msg = msg
23 |
24 | def __str__(self):
25 | return f"CookieCloudRunResult(date={self.date}, flag={self.flag}, msg={self.msg})"
26 |
27 | class CookieCloud(_IPluginModule):
28 | # 插件名称
29 | module_name = "CookieCloud同步"
30 | # 插件描述
31 | module_desc = "从CookieCloud云端同步数据,自动新增站点或更新已有站点Cookie。"
32 | # 插件图标
33 | module_icon = "cloud.png"
34 | # 主题色
35 | module_color = "#77B3D4"
36 | # 插件版本
37 | module_version = "1.1"
38 | # 插件作者
39 | module_author = "jxxghp"
40 | # 作者主页
41 | author_url = "https://github.com/jxxghp"
42 | # 插件配置项ID前缀
43 | module_config_prefix = "cookiecloud_"
44 | # 加载顺序
45 | module_order = 21
46 | # 可使用的用户级别
47 | auth_level = 2
48 |
49 | # 私有属性
50 | sites = None
51 | _scheduler = None
52 | _index_helper = None
53 | # 上次运行结果属性
54 | _last_run_results_list = None
55 | # 设置开关
56 | _req = None
57 | _server = None
58 | _key = None
59 | _password = None
60 | _enabled = False
61 | # 任务执行间隔
62 | _cron = None
63 | _onlyonce = False
64 | # 通知
65 | _notify = False
66 | # 退出事件
67 | _event = Event()
68 | # 需要忽略的Cookie
69 | _ignore_cookies = ['CookieAutoDeleteBrowsingDataCleanup']
70 |
71 | @staticmethod
72 | def get_fields():
73 | return [
74 | # 同一板块
75 | {
76 | 'type': 'div',
77 | 'content': [
78 | # 同一行
79 | [
80 | {
81 | 'title': '服务器地址',
82 | 'required': "required",
83 | 'tooltip': '参考https://github.com/easychen/CookieCloud搭建私有CookieCloud服务器;也可使用默认的公共服务器,公共服务器不会存储任何非加密用户数据,也不会存储用户KEY、端对端加密密码,但要注意千万不要对外泄露加密信息,否则Cookie数据也会被泄露!',
84 | 'type': 'text',
85 | 'content': [
86 | {
87 | 'id': 'server',
88 | 'placeholder': 'https://nastool.cn/cookiecloud'
89 | }
90 | ]
91 |
92 | },
93 | {
94 | 'title': '执行周期',
95 | 'required': "",
96 | 'tooltip': '设置自动同步时间周期,支持5位cron表达式',
97 | 'type': 'text',
98 | 'content': [
99 | {
100 | 'id': 'cron',
101 | 'placeholder': '0 0 0 ? *',
102 | }
103 | ]
104 | },
105 | ]
106 | ]
107 | },
108 | {
109 | 'type': 'div',
110 | 'content': [
111 | # 同一行
112 | [
113 | {
114 | 'title': '用户KEY',
115 | 'required': 'required',
116 | 'tooltip': '浏览器CookieCloud插件中获取,使用公共服务器时注意不要泄露该信息',
117 | 'type': 'text',
118 | 'content': [
119 | {
120 | 'id': 'key',
121 | 'placeholder': '',
122 | }
123 | ]
124 | },
125 | {
126 | 'title': '端对端加密密码',
127 | 'required': "",
128 | 'tooltip': '浏览器CookieCloud插件中获取,使用公共服务器时注意不要泄露该信息',
129 | 'type': 'text',
130 | 'content': [
131 | {
132 | 'id': 'password',
133 | 'placeholder': ''
134 | }
135 | ]
136 | }
137 | ]
138 | ]
139 | },
140 | {
141 | 'type': 'div',
142 | 'content': [
143 | # 同一行
144 | [
145 | {
146 | 'title': '运行时通知',
147 | 'required': "",
148 | 'tooltip': '运行任务后会发送通知(需要打开插件消息通知)',
149 | 'type': 'switch',
150 | 'id': 'notify',
151 | },
152 | {
153 | 'title': '立即运行一次',
154 | 'required': "",
155 | 'tooltip': '打开后立即运行一次(点击此对话框的确定按钮后即会运行,周期未设置也会运行),关闭后将仅按照定时周期运行(同时上次触发运行的任务如果在运行中也会停止)',
156 | 'type': 'switch',
157 | 'id': 'onlyonce',
158 | }
159 | ]
160 | ]
161 | }
162 | ]
163 |
164 | def get_page(self):
165 | """
166 | 插件的额外页面,返回页面标题和页面内容
167 | :return: 标题,页面内容,确定按钮响应函数
168 | """
169 | if not isinstance(self._last_run_results_list, list) or len(self._last_run_results_list) <= 0:
170 | self.info("未获取到上次运行结果")
171 | return None, None, None
172 |
173 | template = """
174 |
175 |
176 |
177 | {% if ResultsCount > 0 %}
178 |
179 | 运行开始时间 |
180 | 运行消息 |
181 | 是否连通 |
182 | |
183 |
184 | {% endif %}
185 |
186 |
187 | {% if ResultsCount > 0 %}
188 | {% for Item in Results %}
189 |
190 | {{ Item.date }} |
191 | {{ Item.msg }} |
192 | {{ Item.flag }} |
193 |
194 | {% endfor %}
195 | {% endif %}
196 |
197 |
198 |
199 | """
200 | return "同步记录", Template(template).render(ResultsCount=len(self._last_run_results_list), Results=self._last_run_results_list), None
201 |
202 | def init_config(self, config=None):
203 | self.sites = Sites()
204 | self._index_helper = IndexerHelper()
205 | self._last_run_results_list = []
206 |
207 | # 读取配置
208 | if config:
209 | self._server = config.get("server")
210 | self._cron = config.get("cron")
211 | self._key = config.get("key")
212 | self._password = config.get("password")
213 | self._notify = config.get("notify")
214 | self._onlyonce = config.get("onlyonce")
215 | self._req = RequestUtils(content_type="application/json")
216 | if self._server:
217 | if not self._server.startswith("http"):
218 | self._server = "http://%s" % self._server
219 | if self._server.endswith("/"):
220 | self._server = self._server[:-1]
221 |
222 | # 测试
223 | _, msg, flag = self.__download_data()
224 | _last_run_date = self.__get_current_date_str()
225 | _last_run_msg = msg if StringUtils.is_string_and_not_empty(msg) else "测试连通性成功"
226 | _result = CookieCloudRunResult(date=_last_run_date, flag=flag, msg=_last_run_msg)
227 | self._last_run_results_list.append(_result)
228 | if flag:
229 | self._enabled = True
230 | else:
231 | self._enabled = False
232 | self.info(msg)
233 |
234 | # 停止现有任务
235 | self.stop_service()
236 |
237 | # 启动服务
238 | if self._enabled:
239 | self._scheduler = BackgroundScheduler(timezone=Config().get_timezone())
240 |
241 | # 运行一次
242 | if self._onlyonce:
243 | self.info(f"同步服务启动,立即运行一次")
244 | self._scheduler.add_job(self.__cookie_sync, 'date',
245 | run_date=datetime.now(tz=pytz.timezone(Config().get_timezone())) + timedelta(
246 | seconds=3))
247 | # 关闭一次性开关
248 | self._onlyonce = False
249 | self.update_config({
250 | "server": self._server,
251 | "cron": self._cron,
252 | "key": self._key,
253 | "password": self._password,
254 | "notify": self._notify,
255 | "onlyonce": self._onlyonce,
256 | })
257 |
258 | # 周期运行
259 | if self._cron:
260 | self.info(f"同步服务启动,周期:{self._cron}")
261 | self._scheduler.add_job(self.__cookie_sync,
262 | CronTrigger.from_crontab(self._cron))
263 |
264 | # 启动任务
265 | if self._scheduler.get_jobs():
266 | self._scheduler.print_jobs()
267 | self._scheduler.start()
268 |
269 | def get_state(self):
270 | return self._enabled and self._cron
271 |
272 | def __get_current_date_str(self):
273 | """
274 | 获取当前日期字符串,格式为:2023-08-03 19:00:00
275 | """
276 | # 获取当前时间并添加 1 秒
277 | new_time = datetime.now(tz=pytz.timezone(Config().get_timezone())) + timedelta(seconds=1)
278 |
279 | # 将时间格式化为指定格式
280 | return new_time.strftime('%Y-%m-%d %H:%M:%S')
281 |
282 | def __download_data(self) -> [dict, str, bool]:
283 | """
284 | 从CookieCloud下载数据
285 | """
286 | if not self._server or not self._key or not self._password:
287 | return {}, "CookieCloud参数不正确", False
288 | req_url = "%s/get/%s" % (self._server, self._key)
289 | ret = self._req.post_res(url=req_url, json={"password": self._password})
290 | if ret and ret.status_code == 200:
291 | result = ret.json()
292 | if not result:
293 | return {}, "", True
294 | if result.get("cookie_data"):
295 | return result.get("cookie_data"), "", True
296 | return result, "", True
297 | elif ret:
298 | return {}, "同步CookieCloud失败,错误码:%s" % ret.status_code, False
299 | else:
300 | return {}, "CookieCloud请求失败,请检查服务器地址、用户KEY及加密密码是否正确", False
301 |
302 | def __cookie_sync(self):
303 | """
304 | 同步站点Cookie
305 | """
306 | # 同步数据
307 | self.info(f"同步服务开始 ...")
308 | # 最多显示50条同步数据
309 | if len(self._last_run_results_list) > 50:
310 | self._last_run_results_list = []
311 | _last_run_date = self.__get_current_date_str()
312 | contents, msg, flag = self.__download_data()
313 | if not flag:
314 | self.error(msg)
315 | self.__send_message(msg)
316 | _result = CookieCloudRunResult(date=_last_run_date, flag=flag, msg=msg)
317 | self._last_run_results_list.append(_result)
318 | return
319 | if not contents:
320 | self.info(f"未从CookieCloud获取到数据")
321 | self.__send_message(msg)
322 | _result = CookieCloudRunResult(date=_last_run_date, flag=flag, msg=msg)
323 | self._last_run_results_list.append(_result)
324 | return
325 | # 整理数据,使用domain域名的最后两级作为分组依据
326 | domain_groups = defaultdict(list)
327 | for site, cookies in contents.items():
328 | for cookie in cookies:
329 | domain_parts = cookie["domain"].split(".")[-2:]
330 | domain_key = tuple(domain_parts)
331 | domain_groups[domain_key].append(cookie)
332 | # 计数
333 | update_count = 0
334 | add_count = 0
335 | # 索引器
336 | for domain, content_list in domain_groups.items():
337 | if self._event.is_set():
338 | self.info(f"同步服务停止")
339 | _result = CookieCloudRunResult(date=_last_run_date, flag=flag, msg=msg)
340 | self._last_run_results_list.append(_result)
341 | return
342 | if not content_list:
343 | continue
344 | # 域名
345 | domain_url = ".".join(domain)
346 | # 只有cf的cookie过滤掉
347 | cloudflare_cookie = True
348 | for content in content_list:
349 | if content["name"] != "cf_clearance":
350 | cloudflare_cookie = False
351 | break
352 | if cloudflare_cookie:
353 | continue
354 | # Cookie
355 | cookie_str = ";".join(
356 | [f"{content.get('name')}={content.get('value')}"
357 | for content in content_list
358 | if content.get("name") and content.get("name") not in self._ignore_cookies]
359 | )
360 | # 查询站点
361 | site_info = self.sites.get_sites_by_suffix(domain_url)
362 | if site_info:
363 | # 检查站点连通性
364 | success, _, _ = self.sites.test_connection(site_id=site_info.get("id"))
365 | if not success:
366 | # 已存在且连通失败的站点更新Cookie
367 | self.sites.update_site_cookie(siteid=site_info.get("id"), cookie=cookie_str)
368 | update_count += 1
369 | else:
370 | # 查询是否在索引器范围
371 | indexer_info = self._index_helper.get_indexer_info(domain_url)
372 | if indexer_info:
373 | # 支持则新增站点
374 | site_pri = self.sites.get_max_site_pri() + 1
375 | self.sites.add_site(
376 | name=indexer_info.get("name"),
377 | site_pri=site_pri,
378 | signurl=indexer_info.get("domain"),
379 | cookie=cookie_str,
380 | rss_uses='T'
381 | )
382 | add_count += 1
383 | # 发送消息
384 | if update_count or add_count:
385 | msg = f"更新了 {update_count} 个站点的Cookie数据,新增了 {add_count} 个站点"
386 | else:
387 | msg = f"同步完成,但未更新任何站点数据!"
388 | self.info(msg)
389 | _result = CookieCloudRunResult(date=_last_run_date, flag=flag, msg=msg)
390 | self._last_run_results_list.append(_result)
391 | # 发送消息
392 | if self._notify:
393 | self.__send_message(msg)
394 |
395 | def __send_message(self, msg):
396 | """
397 | 发送通知
398 | """
399 | self.send_message(
400 | title="【CookieCloud同步任务执行完成】",
401 | text=f"{msg}"
402 | )
403 |
404 | def stop_service(self):
405 | """
406 | 退出插件
407 | """
408 | try:
409 | if self._scheduler:
410 | self._scheduler.remove_all_jobs()
411 | if self._scheduler.running:
412 | self._event.set()
413 | self._scheduler.shutdown()
414 | self._event.clear()
415 | self._scheduler = None
416 | except Exception as e:
417 | print(str(e))
418 |
--------------------------------------------------------------------------------
/plugins/custombrush.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import log
4 | from app.plugins import EventHandler
5 | from app.plugins.modules._base import _IPluginModule
6 | from app.utils.types import EventType
7 | from config import Config
8 |
9 |
10 | class CustomBrush(_IPluginModule):
11 | # 插件名称
12 | module_name = "自定义刷流规则"
13 | # 插件描述
14 | module_desc = "用于给自定义索引器添加的站点刷流配置免费资源。"
15 | # 插件图标
16 | module_icon = "brush.png"
17 | # 主题色
18 | module_color = "#02C4E0"
19 | # 插件版本
20 | module_version = "1.2"
21 | # 插件作者
22 | module_author = "mattoid"
23 | # 作者主页
24 | author_url = "https://github.com/Mattoids"
25 | # 插件配置项ID前缀
26 | module_config_prefix = "custombrush_"
27 | # 加载顺序
28 | module_order = 22
29 | # 可使用的用户级别
30 | auth_level = 1
31 |
32 | # 私有属性
33 | _enable = False
34 | _site_brush = {}
35 |
36 | @staticmethod
37 | def get_fields():
38 | return [
39 | # 同一板块
40 | {
41 | 'type': 'div',
42 | 'content': [
43 | # 同一行
44 | [
45 | {
46 | 'title': '刷流规则',
47 | 'required': False,
48 | 'tooltip': '会自动添加到config.yaml文件 laboratory.site_brush 属性中',
49 | 'type': 'textarea',
50 | 'content':
51 | {
52 | 'id': 'site_brush',
53 | 'placeholder': ''
54 | }
55 | }
56 | ]
57 | ]
58 | }
59 | ]
60 |
61 | def init_config(self, config=None):
62 | self._site_brush = self.get_config()
63 | # 读取配置
64 | if config:
65 | self._site_brush = config
66 | self.update_config(self._site_brush)
67 |
68 |
69 |
70 | @EventHandler.register(EventType.PluginReload)
71 | def reload(self, event):
72 | """
73 | 响应插件重载事件
74 | """
75 | plugin_id = event.event_data.get("plugin_id")
76 | if not plugin_id:
77 | return
78 | if plugin_id != self.__class__.__name__:
79 | return
80 | return self.init_config(self.get_config())
81 |
82 | def get_state(self):
83 | return self._enable or self._site_brush
84 |
85 | def stop_service(self):
86 | """
87 | 退出插件
88 | """
89 | pass
90 |
--------------------------------------------------------------------------------
/plugins/customindexer.py:
--------------------------------------------------------------------------------
1 | import re
2 | import json
3 |
4 | from app.plugins.modules._base import _IPluginModule
5 | from app.helper import DbHelper
6 | from app.utils import JsonUtils
7 | from jinja2 import Template
8 |
9 | from web.backend.user import User
10 |
11 | class Customindexer(_IPluginModule):
12 | # 插件名称
13 | module_name = "自定义索引器"
14 | # 插件描述
15 | module_desc = "用于自定义索引器规则,可达到支持更多站点的效果。"
16 | # 插件图标
17 | module_icon = "customindexer.png"
18 | # 主题色
19 | module_color = "#00ADEF"
20 | # 插件版本
21 | module_version = "1.3"
22 | # 插件作者
23 | module_author = "mattoid"
24 | # 作者主页
25 | author_url = "https://github.com/Mattoids"
26 | # 插件配置项ID前缀
27 | module_config_prefix = "customindexer_"
28 | # 加载顺序
29 | module_order = 17
30 | # 可使用的用户级别
31 | auth_level = 1
32 |
33 | # 私有属性
34 | _config = {}
35 |
36 | @staticmethod
37 | def get_fields():
38 | return [
39 | # 同一板块
40 | {
41 | 'type': 'div',
42 | 'content': [
43 | # 同一行
44 | [
45 | {
46 | 'title': '站点域名',
47 | 'required': "required",
48 | 'tooltip': "请填写需要定义的站点域名",
49 | 'type': 'text',
50 | 'content': [
51 | {
52 | 'id': 'site',
53 | 'placeholder': '需要索引的站点域名!',
54 | }
55 | ]
56 | },
57 | ],
58 | [
59 | {
60 | 'title': '原始站点',
61 | 'required': "",
62 | 'tooltip': "复制原有站点的索引规则,为空则不复制规则且站点索引规则不允许为空",
63 | 'type': 'text',
64 | 'content': [
65 | {
66 | 'id': 'old_site',
67 | 'placeholder': '要复制的站点域名',
68 | }
69 | ]
70 | },
71 | ],
72 | [
73 | {
74 | 'title': '站点索引规则',
75 | 'required': "填写索引器规则,若原始站点不为空,则该项允许不填写",
76 | 'tooltip': "设置自定义索引规则",
77 | 'type': 'textarea',
78 | 'content':
79 | {
80 | 'id': 'indexer',
81 | 'placeholder': '',
82 | }
83 | },
84 | ]
85 | ]
86 | }
87 | ]
88 |
89 | def get_page(self):
90 | """
91 | 插件的额外页面,返回页面标题和页面内容
92 | :return: 标题,页面内容,确定按钮响应函数
93 | """
94 | results = []
95 | for inexer in DbHelper().get_indexer_custom_site():
96 | results.append({
97 | "id": inexer.ID,
98 | "site": inexer.SITE,
99 | "indexer": inexer.INDEXER,
100 | "date": inexer.DATE
101 | })
102 |
103 | template = """
104 |
105 |
106 |
107 |
108 | ID |
109 | 站点 |
110 | 索引规则 |
111 | 添加时间 |
112 | 操作 |
113 |
114 |
115 |
116 | {% if HistoryCount > 0 %}
117 | {% for Item in IndexerHistory %}
118 |
119 |
120 | {{ Item.id }}
121 | |
122 |
123 | {{ Item.site }}
124 | |
125 |
126 |
127 | {{ Item.indexer }}
128 |
129 | |
130 |
131 | {{ Item.date or '' }}
132 | |
133 |
134 |
153 | |
154 |
155 | {% endfor %}
156 | {% else %}
157 |
158 | 没有数据 |
159 |
160 | {% endif %}
161 |
162 |
163 |
164 | """
165 | return "查看配置", Template(template).render(HistoryCount=len(results),
166 | IndexerHistory=results), None
167 |
168 | @staticmethod
169 | def get_script():
170 | """
171 | 返回插件额外的JS代码
172 | """
173 | return """
174 | function delete_site_indexer(id) {
175 | ajax_post("run_plugin_method", {"plugin_id": 'Customindexer', 'method': 'delete_site_indexer', 'id': id}, function (ret) {
176 | $("#indexer_history_" + id).remove();
177 | });
178 | }
179 |
180 | $(function(){
181 | $("#customindexer_site").blur(function() {
182 | var site = $(this).val();
183 | var old_site = $("#customindexer_old_site").val();
184 | if (site && old_site) {
185 | ajax_post("run_plugin_method", {"plugin_id": 'Customindexer', 'method': 'get_oid_indexer', 'site': site, 'old_site': old_site}, function (ret) {
186 | if (ret.code == 0) {
187 | $("#customindexer_indexer").val(ret.result)
188 | }
189 | });
190 | }
191 | })
192 |
193 | $("#customindexer_old_site").blur(function() {
194 | var old_site = $(this).val();
195 | var site = $("#customindexer_site").val();
196 | if (site && old_site) {
197 | ajax_post("run_plugin_method", {"plugin_id": 'Customindexer', 'method': 'get_oid_indexer', 'site': site, 'old_site': old_site}, function (ret) {
198 | if (ret.code == 0) {
199 | $("#customindexer_indexer").val(ret.result)
200 | }
201 | });
202 | }
203 | })
204 | });
205 | """
206 |
207 | def get_oid_indexer(self, old_site=None, site=None):
208 | indexer = User().get_indexer(url=old_site,
209 | public=False)
210 | pattern = re.compile(r'http[s]?://(.*?)\.')
211 | match_site = pattern.match(site)
212 | if match_site:
213 | site_id = match_site.group(1)
214 | else:
215 | site_id = match_site
216 |
217 | if indexer:
218 | indexer.id = site_id
219 | indexer.domain = site
220 | return json.dumps(JsonUtils.json_serializable(indexer))
221 | return ""
222 |
223 |
224 |
225 | def delete_site_indexer(self, id):
226 | return DbHelper().delete_indexer_custom_site(id)
227 |
228 |
229 | def init_config(self, config=None):
230 | self.info(f"初始化{config}")
231 | site = config.get("site")
232 | indexer = config.get("indexer")
233 |
234 | if indexer:
235 | DbHelper().insert_indexer_custom_site(site, indexer)
236 |
237 | self.update_config({
238 | "site": "",
239 | "old_site": "",
240 | "indexer": ""
241 | })
242 |
243 | def get_state(self):
244 | return True
245 |
246 | def stop_service(self):
247 | """
248 | 退出插件
249 | """
250 | pass
251 |
--------------------------------------------------------------------------------
/plugins/dailylinkrunner.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import datetime
3 | from app.plugins.modules._base import _IPluginModule
4 | from app.helper import DbHelper
5 | from jinja2 import Template
6 |
7 | class DailyLinkRunner(_IPluginModule):
8 | # 插件的基本信息
9 | module_name = "定时每日链接访问器"
10 | module_desc = "在每天的指定时间访问自定义链接"
11 | module_icon = "link.png"
12 | module_color = "#FF5733"
13 | module_version = "1.2"
14 | module_author = "maxgt"
15 | author_url = "https://github.com/GTian28"
16 | module_config_prefix = "dailylinkrunner_"
17 | module_order = 18
18 | auth_level = 1
19 |
20 | _config = {}
21 | _links = [] # 存放用户添加的链接
22 | _run_time = None
23 |
24 | @staticmethod
25 | def get_fields():
26 | return [
27 | {
28 | 'type': 'div',
29 | 'content': [
30 | [
31 | # "添加"按钮
32 | {
33 | 'title': '添加',
34 | 'type': 'button',
35 | 'value': '添加',
36 | 'onclick': 'add_link()'
37 | },
38 | # "立即访问一次"按钮
39 | {
40 | 'title': '立即访问一次',
41 | 'type': 'button',
42 | 'value': '立即访问一次',
43 | 'onclick': 'run_links_now()'
44 | }
45 | ]
46 | ]
47 | },
48 | {
49 | 'type': 'div',
50 | 'content': [
51 | [
52 | {
53 | 'title': '链接',
54 | 'required': "required",
55 | 'tooltip': "请填写要定时访问的链接",
56 | 'type': 'text',
57 | 'content': [
58 | {
59 | 'id': 'link',
60 | 'placeholder': '要访问的链接',
61 | }
62 | ]
63 | },
64 | ],
65 | [
66 | {
67 | 'title': '每日运行时间',
68 | 'required': "required",
69 | 'tooltip': "每天执行任务的时间",
70 | 'type': 'time',
71 | 'content': [
72 | {
73 | 'id': 'runtime',
74 | 'placeholder': '例如 12:35',
75 | }
76 | ]
77 | },
78 | ]
79 | ]
80 | }
81 | ]
82 |
83 | @staticmethod
84 | def get_script():
85 | return """
86 | var links = [];
87 |
88 | function add_link() {
89 | var link = $("#dailylinkrunner_link").val();
90 | var runtime = $("#dailylinkrunner_runtime").val();
91 |
92 | if (link) {
93 | links.push(link);
94 |
95 | // 添加到一个显示列表中,让用户知道他们添加了哪些链接
96 | var linkList = $("#dailylinkrunner_linkList");
97 | if (!linkList.length) {
98 | $('').insertAfter("#dailylinkrunner_link");
99 | }
100 | $("#dailylinkrunner_linkList").append("" + link + "");
101 |
102 | // 清空输入框,以便用户可以继续添加其他链接
103 | $("#dailylinkrunner_link").val("");
104 | }
105 | }
106 |
107 | function run_links_now() {
108 | if (links.length > 0) {
109 | links.forEach(function(link) {
110 | // 这里我们简单地使用GET请求,你可以根据需要调整
111 | $.get(link)
112 | .done(function() {
113 | console.log("成功访问链接: " + link);
114 | })
115 | .fail(function() {
116 | console.error("访问链接" + link + "时出错");
117 | });
118 | });
119 | } else {
120 | alert("请先添加至少一个链接!");
121 | }
122 | }
123 | """
124 |
125 | def init_config(self, config=None):
126 | self.info(f"初始化{config}")
127 |
128 | # 从配置中提取链接和运行时间
129 | links = config.get("links", [])
130 | runtime = config.get("runtime", "")
131 |
132 | # 如果存在链接和运行时间,将它们保存到数据库
133 | if links and runtime:
134 | # 假设DbHelper有一个方法来保存或更新我们的配置
135 | DbHelper().save_daily_link_runner_config(links, runtime)
136 |
137 | # 更新内部配置以反映这些变化
138 | self.update_config({
139 | "links": links,
140 | "runtime": runtime
141 | })
142 |
143 |
144 | def check_run_time(self):
145 | current_time = datetime.datetime.now().time()
146 | run_hour, run_minute = map(int, self._run_time.split(':'))
147 | run_time = datetime.time(run_hour, run_minute)
148 |
149 | # 检查是否为指定运行时间
150 | if current_time == run_time:
151 | self.run_links_now()
152 |
153 | def run_link(self, link):
154 | # 根据传入的链接运行
155 | if link:
156 | try:
157 | requests.get(link)
158 | self.info(f"成功访问链接: {link}")
159 | except requests.RequestException as e:
160 | self.error(f"访问链接{link}时出错: {str(e)}")
161 |
162 | def run_links_now(self):
163 | for link_info in self._links:
164 | self.run_link(link_info["link"])
165 |
166 | def get_state(self):
167 | return True
168 |
169 | def stop_service(self):
170 | pass
171 |
--------------------------------------------------------------------------------
/plugins/doubansync.py:
--------------------------------------------------------------------------------
1 | import random
2 | from datetime import datetime, timedelta
3 | from threading import Event, Lock
4 | from time import sleep
5 |
6 | import pytz
7 | from apscheduler.schedulers.background import BackgroundScheduler
8 | from jinja2 import Template
9 |
10 | from app.downloader import Downloader
11 | from app.media import DouBan
12 | from app.media.meta import MetaInfo
13 | from app.plugins import EventHandler
14 | from app.plugins.modules._base import _IPluginModule
15 | from app.searcher import Searcher
16 | from app.subscribe import Subscribe
17 | from app.utils import ExceptionUtils
18 | from app.utils.types import SearchType, RssType, EventType, MediaType
19 | from config import Config
20 | from web.backend.web_utils import WebUtils
21 |
22 | lock = Lock()
23 |
24 |
25 | class DoubanSync(_IPluginModule):
26 | # 插件名称
27 | module_name = "豆瓣同步"
28 | # 插件描述
29 | module_desc = "同步豆瓣在看、想看、看过记录,自动添加订阅或搜索下载。"
30 | # 插件图标
31 | module_icon = "douban.png"
32 | # 主题色
33 | module_color = "#05B711"
34 | # 插件版本
35 | module_version = "1.1"
36 | # 插件作者
37 | module_author = "jxxghp"
38 | # 作者主页
39 | author_url = "https://github.com/jxxghp"
40 | # 插件配置项ID前缀
41 | module_config_prefix = "doubansync_"
42 | # 加载顺序
43 | module_order = 17
44 | # 可使用的用户级别
45 | auth_level = 2
46 |
47 | # 退出事件
48 | _event = Event()
49 | # 私有属性
50 | douban = None
51 | searcher = None
52 | downloader = None
53 | subscribe = None
54 | _enable = False
55 | _onlyonce = False
56 | _sync_type = False
57 | _rss_interval = 0
58 | _interval = 0
59 | _auto_search = False
60 | _auto_rss = False
61 | _users = []
62 | _days = 0
63 | _types = []
64 | _cookie = None
65 | _scheduler = None
66 |
67 | def init_config(self, config: dict = None):
68 | self.douban = DouBan()
69 | self.searcher = Searcher()
70 | self.downloader = Downloader()
71 | self.subscribe = Subscribe()
72 | if config:
73 | self._enable = config.get("enable")
74 | self._onlyonce = config.get("onlyonce")
75 | self._sync_type = config.get("sync_type")
76 | if self._sync_type == '1':
77 | self._interval = 0
78 | rss_interval = config.get("rss_interval")
79 | if rss_interval and str(rss_interval).isdigit():
80 | self._rss_interval = int(rss_interval)
81 | if self._rss_interval < 300:
82 | self._rss_interval = 300
83 | else:
84 | self._rss_interval = 0
85 | else:
86 | self._rss_interval = 0
87 | interval = config.get("interval")
88 | if interval and str(interval).isdigit():
89 | self._interval = int(interval)
90 | else:
91 | self._interval = 0
92 | self._auto_search = config.get("auto_search")
93 | self._auto_rss = config.get("auto_rss")
94 | self._cookie = config.get("cookie")
95 | self._users = config.get("users") or []
96 | if self._users:
97 | if isinstance(self._users, str):
98 | self._users = self._users.split(',')
99 | self._days = config.get("days")
100 | if self._days and str(self._days).isdigit():
101 | self._days = int(self._days)
102 | else:
103 | self._days = 0
104 | self._types = config.get("types") or []
105 | if self._types:
106 | if isinstance(self._types, str):
107 | self._types = self._types.split(',')
108 |
109 | # 停止现有任务
110 | self.stop_service()
111 |
112 | # 启动服务
113 | if self.get_state() or self._onlyonce:
114 | self._scheduler = BackgroundScheduler(timezone=Config().get_timezone())
115 | if self._interval:
116 | self.info(f"豆瓣全量同步服务启动,周期:{self._interval} 小时,类型:{self._types},用户:{self._users}")
117 | self._scheduler.add_job(self.sync, 'interval',
118 | hours=self._interval)
119 | if self._rss_interval:
120 | self.info(f"豆瓣近期动态同步服务启动,周期:{self._rss_interval} 秒,类型:{self._types},用户:{self._users}")
121 | self._scheduler.add_job(self.sync, 'interval',
122 | seconds=self._rss_interval)
123 |
124 | if self._onlyonce:
125 | self.info("豆瓣同步服务启动,立即运行一次")
126 | self._scheduler.add_job(self.sync, 'date',
127 | run_date=datetime.now(tz=pytz.timezone(Config().get_timezone())) + timedelta(
128 | seconds=3))
129 |
130 | # 关闭一次性开关
131 | self._onlyonce = False
132 | self.update_config({
133 | "onlyonce": self._onlyonce,
134 | "enable": self._enable,
135 | "sync_type": self._sync_type,
136 | "interval": self._interval,
137 | "rss_interval": self._rss_interval,
138 | "auto_search": self._auto_search,
139 | "auto_rss": self._auto_rss,
140 | "cookie": self._cookie,
141 | "users": self._users,
142 | "days": self._days,
143 | "types": self._types
144 | })
145 | if self._scheduler.get_jobs():
146 | # 启动服务
147 | self._scheduler.print_jobs()
148 | self._scheduler.start()
149 |
150 | def get_state(self):
151 | return self._enable \
152 | and self._users \
153 | and self._types \
154 | and ((self._sync_type == '1' and self._rss_interval)
155 | or (self._sync_type != '1' and self._interval))
156 |
157 | @staticmethod
158 | def get_fields():
159 | return [
160 | # 同一板块
161 | {
162 | 'type': 'div',
163 | 'content': [
164 | # 同一行
165 | [
166 | {
167 | 'title': '开启豆瓣同步',
168 | 'required': "",
169 | 'tooltip': '开启后,定时同步豆瓣在看、想看、看过记录,有新内容时自动添加订阅或者搜索下载,支持全量同步及近期动态两种模式,分别设置同步间隔',
170 | 'type': 'switch',
171 | 'id': 'enable',
172 | }
173 | ],
174 | [
175 | {
176 | 'title': '立即运行一次',
177 | 'required': "",
178 | 'tooltip': '打开后立即运行一次(点击此对话框的确定按钮后即会运行,周期未设置也会运行),关闭后将仅按照刮削周期运行(同时上次触发运行的任务如果在运行中也会停止)',
179 | 'type': 'switch',
180 | 'id': 'onlyonce',
181 | }
182 | ],
183 | [
184 | {
185 | 'title': '豆瓣用户ID',
186 | 'required': "required",
187 | 'tooltip': '需要同步数据的豆瓣用户ID,在豆瓣个人主页地址栏/people/后面的数字;如有多个豆瓣用户ID,使用英文逗号,分隔',
188 | 'type': 'text',
189 | 'content': [
190 | {
191 | 'id': 'users',
192 | 'placeholder': '用户1,用户2,用户3',
193 | }
194 | ]
195 | },
196 | {
197 | 'title': '同步内容',
198 | 'required': "required",
199 | 'tooltip': '同步哪些类型的收藏数据:do 在看,wish 想看,collect 看过,用英文逗号,分隔配置',
200 | 'type': 'text',
201 | 'content': [
202 | {
203 | 'id': 'types',
204 | 'placeholder': 'do,wish,collect',
205 | }
206 | ]
207 | },
208 | {
209 | 'title': '同步方式',
210 | 'required': "required",
211 | 'tooltip': '选择使用哪种方式同步豆瓣数据:全量同步(根据同步范围全量同步所有数据)、近期动态(同步用户近期的10条动态数据)',
212 | 'type': 'select',
213 | 'content': [
214 | {
215 | 'id': 'sync_type',
216 | 'options': {
217 | '0': '全量同步',
218 | '1': '近期动态'
219 | },
220 | 'default': '0',
221 | 'onchange': 'DoubanSync_sync_rss_change(this)'
222 | }
223 | ]
224 | }
225 | ],
226 | [
227 | {
228 | 'title': '全量同步范围(天)',
229 | 'required': "required",
230 | 'tooltip': '同步多少天内的记录,0表示同步全部,仅适用于全量同步',
231 | 'type': 'text',
232 | 'content': [
233 | {
234 | 'id': 'days',
235 | 'placeholder': '30',
236 | }
237 | ]
238 | },
239 | {
240 | 'title': '全量同步间隔(小时)',
241 | 'required': "required",
242 | 'tooltip': '间隔多久同步一次时间范围内的用户标记的数据,为了避免被豆瓣封禁IP,应尽可能拉长间隔时间',
243 | 'type': 'text',
244 | 'content': [
245 | {
246 | 'id': 'interval',
247 | 'placeholder': '6',
248 | }
249 | ]
250 | },
251 | {
252 | 'title': '近期动态同步间隔(秒)',
253 | 'required': "required",
254 | 'tooltip': '豆瓣近期动态的同步时间间隔,最小300秒,可设置较小的间隔同步用户近期动态数据,但无法同步全部标记数据',
255 | 'type': 'text',
256 | 'content': [
257 | {
258 | 'id': 'rss_interval',
259 | 'placeholder': '300',
260 | }
261 | ]
262 | }
263 | ],
264 | [
265 | {
266 | 'title': '豆瓣Cookie',
267 | 'required': '',
268 | 'tooltip': '受豆瓣限制,部分电影需要配置Cookie才能同步到数据;通过浏览器抓取',
269 | 'type': 'textarea',
270 | 'content':
271 | {
272 | 'id': 'cookie',
273 | 'placeholder': '',
274 | 'rows': 5
275 | }
276 | }
277 | ],
278 | [
279 | {
280 | 'title': '自动搜索下载',
281 | 'required': "",
282 | 'tooltip': '开启后豆瓣同步的数据会自动进行站点聚合搜索下载',
283 | 'type': 'switch',
284 | 'id': 'auto_search',
285 | },
286 | {
287 | 'title': '自动添加订阅',
288 | 'required': "",
289 | 'tooltip': '开启后未进行搜索下载的或搜索下载不完整的将加入订阅',
290 | 'type': 'switch',
291 | 'id': 'auto_rss',
292 | }
293 | ],
294 | ]
295 | }
296 | ]
297 |
298 | def get_page(self):
299 | """
300 | 插件的额外页面,返回页面标题和页面内容
301 | :return: 标题,页面内容,确定按钮响应函数
302 | """
303 | results = self.get_history()
304 | template = """
305 |
306 |
307 |
308 |
309 | |
310 | 标题 |
311 | 类型 |
312 | 状态 |
313 | 添加时间 |
314 | |
315 |
316 |
317 |
318 | {% if HistoryCount > 0 %}
319 | {% for Item in DoubanHistory %}
320 |
321 |
322 |
325 | |
326 |
327 | {{ Item.name }} ({{ Item.year }})
328 | {% if Item.rating %}
329 |
330 | 评份:{{ Item.rating }}
331 |
332 | {% endif %}
333 | |
334 |
335 | {{ Item.type }}
336 | |
337 |
338 | {% if Item.state == 'DOWNLOADED' %}
339 | 已下载
340 | {% elif Item.state == 'RSS' %}
341 | 已订阅
342 | {% elif Item.state == 'NEW' %}
343 | 新增
344 | {% else %}
345 | 处理中
346 | {% endif %}
347 | |
348 |
349 | {{ Item.add_time or '' }}
350 | |
351 |
352 |
371 | |
372 |
373 | {% endfor %}
374 | {% else %}
375 |
376 | 没有数据 |
377 |
378 | {% endif %}
379 |
380 |
381 |
382 | """
383 | return "同步历史", Template(template).render(HistoryCount=len(results),
384 | DoubanHistory=results), None
385 |
386 | @staticmethod
387 | def get_script():
388 | """
389 | 删除豆瓣历史记录的JS脚本
390 | """
391 | return """
392 | // 删除豆瓣历史记录
393 | function DoubanSync_delete_douban_history(id){
394 | ajax_post("run_plugin_method", {"plugin_id": 'DoubanSync', 'method': 'delete_sync_history', 'douban_id': id}, function (ret) {
395 | $("#douban_history_" + id).remove();
396 | });
397 | }
398 |
399 | // 同步方式切换
400 | function DoubanSync_sync_rss_change(obj){
401 | if ($(obj).val() == '1') {
402 | $('#doubansync_rss_interval').parent().parent().show();
403 | $('#doubansync_interval').parent().parent().hide();
404 | $('#doubansync_days').parent().parent().hide();
405 | }else{
406 | $('#doubansync_rss_interval').parent().parent().hide();
407 | $('#doubansync_interval').parent().parent().show();
408 | $('#doubansync_days').parent().parent().show();
409 | }
410 | }
411 |
412 | // 初始化完成后执行的方法
413 | function DoubanSync_PluginInit(){
414 | DoubanSync_sync_rss_change('#doubansync_sync_type');
415 | }
416 | """
417 |
418 | @staticmethod
419 | def get_command():
420 | """
421 | 定义远程控制命令
422 | :return: 命令关键字、事件、描述、附带数据
423 | """
424 | return {
425 | "cmd": "/db",
426 | "event": EventType.DoubanSync,
427 | "desc": "豆瓣同步",
428 | "data": {}
429 | }
430 |
431 | def stop_service(self):
432 | """
433 | 停止服务
434 | """
435 | try:
436 | if self._scheduler:
437 | self._scheduler.remove_all_jobs()
438 | if self._scheduler.running:
439 | self._event.set()
440 | self._scheduler.shutdown()
441 | self._event.clear()
442 | self._scheduler = None
443 | except Exception as e:
444 | print(str(e))
445 |
446 | def delete_sync_history(self, douban_id):
447 | """
448 | 删除同步历史
449 | """
450 | return self.delete_history(key=douban_id)
451 |
452 | @EventHandler.register(EventType.DoubanSync)
453 | def sync(self, event=None):
454 | """
455 | 同步豆瓣数据
456 | """
457 | if not self._interval and not self._rss_interval:
458 | self.info("豆瓣配置:同步间隔未配置或配置不正确")
459 | return
460 | with lock:
461 | # 拉取豆瓣数据
462 | medias = self.__get_all_douban_movies()
463 | # 开始搜索
464 | for media in medias:
465 | if not media or not media.get_name():
466 | continue
467 | try:
468 | # 查询数据库状态
469 | history = self.get_history(media.douban_id)
470 | if not history or history.get("state") == "NEW":
471 | if self._auto_search:
472 | # 需要搜索
473 | media_info = WebUtils.get_mediainfo_from_id(mtype=media.type,
474 | mediaid=f"DB:{media.douban_id}",
475 | wait=True)
476 | # 不需要自动加订阅,则直接搜索
477 | if not media_info or not media_info.tmdb_info:
478 | self.warn("%s 未查询到媒体信息" % media.get_name())
479 | continue
480 | # 检查是否存在,电视剧返回不存在的集清单
481 | exist_flag, no_exists, _ = self.downloader.check_exists_medias(meta_info=media_info)
482 | # 已经存在
483 | if exist_flag:
484 | # 更新为已下载状态
485 | self.info("%s 已存在" % media_info.title)
486 | self.__update_history(media=media_info, state="DOWNLOADED")
487 | continue
488 | if not self._auto_rss:
489 | # 开始搜索
490 | search_result, no_exists, search_count, download_count = self.searcher.search_one_media(
491 | media_info=media_info,
492 | in_from=SearchType.DB,
493 | no_exists=no_exists,
494 | user_name=media_info.user_name)
495 | if search_result:
496 | # 下载全了更新为已下载,没下载全的下次同步再次搜索
497 | self.__update_history(media=media_info, state="DOWNLOADED")
498 | else:
499 | # 需要加订阅,则由订阅去搜索
500 | self.info(
501 | "%s %s 更新到%s订阅中..." % (media_info.title,
502 | media_info.year,
503 | media_info.type.value))
504 | code, msg, _ = self.subscribe.add_rss_subscribe(mtype=media_info.type,
505 | name=media_info.title,
506 | year=media_info.year,
507 | channel=RssType.Auto,
508 | mediaid=f"DB:{media_info.douban_id}",
509 | in_from=SearchType.DB)
510 | if code != 0:
511 | self.error("%s 添加订阅失败:%s" % (media_info.title, msg))
512 | # 订阅已存在
513 | if code == 9:
514 | self.__update_history(media=media_info, state="RSS")
515 | else:
516 | # 插入为已RSS状态
517 | self.__update_history(media=media_info, state="RSS")
518 | else:
519 | # 不需要搜索
520 | if self._auto_rss:
521 | # 加入订阅,使状态为R
522 | self.info("%s %s 更新到%s订阅中..." % (
523 | media.get_name(), media.year, media.type.value))
524 | code, msg, _ = self.subscribe.add_rss_subscribe(mtype=media.type,
525 | name=media.get_name(),
526 | year=media.year,
527 | mediaid=f"DB:{media.douban_id}",
528 | channel=RssType.Auto,
529 | state="R",
530 | in_from=SearchType.DB)
531 | if code != 0:
532 | self.error("%s 添加订阅失败:%s" % (media.get_name(), msg))
533 | # 订阅已存在
534 | if code == 9:
535 | self.__update_history(media=media, state="RSS")
536 | else:
537 | # 插入为已RSS状态
538 | self.__update_history(media=media, state="RSS")
539 | elif not history:
540 | self.info("%s %s 更新到%s列表中..." % (
541 | media.get_name(), media.year, media.type.value))
542 | self.__update_history(media=media, state="NEW")
543 |
544 | else:
545 | self.info(f"{media.douban_id} {media.get_name()} {media.year} 已处理过")
546 | except Exception as err:
547 | self.error(f"{media.douban_id} {media.get_name()} {media.year} 处理失败:{str(err)}")
548 | ExceptionUtils.exception_traceback(err)
549 | continue
550 | self.info("豆瓣数据同步完成")
551 |
552 | def __update_history(self, media, state):
553 | """
554 | 插入历史记录
555 | """
556 | value = {
557 | "id": media.douban_id,
558 | "name": media.title or media.get_name(),
559 | "year": media.year,
560 | "type": media.type.value,
561 | "rating": media.vote_average,
562 | "image": media.get_poster_image(),
563 | "state": state,
564 | "add_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
565 | }
566 | if self.get_history(key=media.douban_id):
567 | self.update_history(key=media.douban_id, value=value)
568 | else:
569 | self.history(key=media.douban_id, value=value)
570 |
571 | def __get_all_douban_movies(self):
572 | """
573 | 获取每一个用户的每一个类型的豆瓣标记
574 | :return: 搜索到的媒体信息列表(不含TMDB信息)
575 | """
576 | self.info(f"同步方式:{'近期动态' if self._sync_type == '1' else '全量同步'}")
577 |
578 | # 返回媒体列表
579 | media_list = []
580 | # 豆瓣ID列表
581 | douban_ids = {}
582 | # 每一个用户
583 | for user in self._users:
584 | if not user:
585 | continue
586 | # 查询用户名称
587 | user_name = ""
588 | userinfo = self.douban.get_user_info(userid=user)
589 | if userinfo:
590 | user_name = userinfo.get("name")
591 |
592 | if self._sync_type != "1":
593 | # 每页条数
594 | perpage_number = 15
595 | # 所有类型成功数量
596 | user_succnum = 0
597 | for mtype in self._types:
598 | if not mtype:
599 | continue
600 | self.info(f"开始获取 {user_name or user} 的 {mtype} 数据...")
601 | # 开始序号
602 | start_number = 0
603 | # 类型成功数量
604 | user_type_succnum = 0
605 | # 每一页
606 | while True:
607 | # 页数
608 | page_number = int(start_number / perpage_number + 1)
609 | # 当前页成功数量
610 | sucess_urlnum = 0
611 | # 是否继续下一页
612 | continue_next_page = True
613 | self.debug(f"开始解析第 {page_number} 页数据...")
614 | try:
615 | items = self.douban.get_douban_wish(dtype=mtype, userid=user, start=start_number, wait=True)
616 | if not items:
617 | self.warn(f"第 {page_number} 页未获取到数据")
618 | break
619 | # 解析豆瓣ID
620 | for item in items:
621 | # 时间范围
622 | date = item.get("date")
623 | if not date:
624 | continue_next_page = False
625 | break
626 | else:
627 | mark_date = datetime.strptime(date, '%Y-%m-%d')
628 | if self._days and not (datetime.now() - mark_date).days < int(self._days):
629 | continue_next_page = False
630 | break
631 | doubanid = item.get("id")
632 | if str(doubanid).isdigit():
633 | self.info("解析到媒体:%s" % doubanid)
634 | if doubanid not in douban_ids:
635 | douban_ids[doubanid] = {
636 | "user_name": user_name
637 | }
638 | sucess_urlnum += 1
639 | user_type_succnum += 1
640 | user_succnum += 1
641 | self.debug(
642 | f"{user_name or user} 第 {page_number} 页解析完成,共获取到 {sucess_urlnum} 个媒体")
643 | except Exception as err:
644 | ExceptionUtils.exception_traceback(err)
645 | self.error(f"{user_name or user} 第 {page_number} 页解析出错:%s" % str(err))
646 | break
647 | # 继续下一页
648 | if continue_next_page:
649 | start_number += perpage_number
650 | else:
651 | break
652 | # 当前类型解析结束
653 | self.debug(f"用户 {user_name or user} 的 {mtype} 解析完成,共获取到 {user_type_succnum} 个媒体")
654 | self.info(f"用户 {user_name or user} 解析完成,共获取到 {user_succnum} 个媒体")
655 | else:
656 | all_items = self.douban.get_latest_douban_interests(dtype='all', userid=user, wait=True)
657 | self.debug(f"开始解析 {user_name or user} 的数据...")
658 | self.debug(f"共获取到 {len(all_items)} 条数据")
659 | # 所有类型成功数量
660 | user_succnum = 0
661 | for mtype in self._types:
662 | # 类型成功数量
663 | user_type_succnum = 0
664 | items = list(filter(lambda x: x.get("type") == mtype, all_items))
665 | for item in items:
666 | # 时间范围
667 | date = item.get("date")
668 | if not date:
669 | continue
670 | else:
671 | mark_date = datetime.strptime(date, '%Y-%m-%d')
672 | if self._days and not (datetime.now() - mark_date).days < int(self._days):
673 | continue
674 | doubanid = item.get("id")
675 | if str(doubanid).isdigit():
676 | self.info("解析到媒体:%s" % doubanid)
677 | if doubanid not in douban_ids:
678 | douban_ids[doubanid] = {
679 | "user_name": user_name
680 | }
681 | user_type_succnum += 1
682 | user_succnum += 1
683 | self.debug(f"用户 {user_name or user} 的 {mtype} 解析完成,共获取到 {user_type_succnum} 个媒体")
684 | self.debug(f"用户 {user_name or user} 解析完成,共获取到 {user_succnum} 个媒体")
685 |
686 | self.info(f"所有用户解析完成,共获取到 {len(douban_ids)} 个媒体")
687 | # 查询豆瓣详情
688 | for doubanid, info in douban_ids.items():
689 | douban_info = self.douban.get_douban_detail(doubanid=doubanid, wait=True)
690 | # 组装媒体信息
691 | if not douban_info:
692 | self.warn("%s 未正确获取豆瓣详细信息,尝试使用网页获取" % doubanid)
693 | douban_info = self.douban.get_media_detail_from_web(doubanid)
694 | if not douban_info:
695 | self.warn("%s 无权限访问,需要配置豆瓣Cookie" % doubanid)
696 | # 随机休眠
697 | sleep(round(random.uniform(1, 5), 1))
698 | continue
699 | media_type = MediaType.TV if douban_info.get("episodes_count") else MediaType.MOVIE
700 | self.info("%s:%s %s".strip() % (media_type.value, douban_info.get("title"), douban_info.get("year")))
701 | meta_info = MetaInfo(title="%s %s" % (douban_info.get("title"), douban_info.get("year") or ""))
702 | meta_info.douban_id = doubanid
703 | meta_info.type = media_type
704 | meta_info.overview = douban_info.get("intro")
705 | meta_info.poster_path = douban_info.get("cover_url")
706 | rating = douban_info.get("rating", {}) or {}
707 | meta_info.vote_average = rating.get("value") or ""
708 | meta_info.imdb_id = douban_info.get("imdbid")
709 | meta_info.user_name = info.get("user_name")
710 | if meta_info not in media_list:
711 | media_list.append(meta_info)
712 | # 随机休眠
713 | sleep(round(random.uniform(1, 5), 1))
714 | return media_list
715 |
--------------------------------------------------------------------------------
/plugins/editsignin.py:
--------------------------------------------------------------------------------
1 | import importlib
2 |
3 | from app.plugins.modules._base import _IPluginModule
4 | from app.helper import SubmoduleHelper
5 | from app.utils import ExceptionUtils
6 |
7 |
8 | class Editsignin(_IPluginModule):
9 | # 插件名称
10 | module_name = "签到站点修改"
11 | # 插件描述
12 | module_desc = "用于解决特殊站点站点更换域名后无法签到的问题。"
13 | # 插件图标
14 | module_icon = "editsignin.png"
15 | # 主题色
16 | module_color = "bg-black"
17 | # 插件版本
18 | module_version = "1.1"
19 | # 插件作者
20 | module_author = "mattoid"
21 | # 作者主页
22 | author_url = "https://github.com/Mattoids"
23 | # 插件配置项ID前缀
24 | module_config_prefix = "editsignin_"
25 | # 加载顺序
26 | module_order = 18
27 | # 可使用的用户级别
28 | auth_level = 1
29 |
30 | # 私有属性
31 | _config = {}
32 | _site_url = {}
33 | _site_schema = []
34 | _site_schema_map = {}
35 |
36 | @staticmethod
37 | def get_fields():
38 | return [
39 | # 同一板块
40 | {
41 | 'type': 'div',
42 | 'content': [
43 | [
44 | {
45 | 'title': '站点',
46 | 'required': "required",
47 | 'tooltip': '需要替换的站点',
48 | 'type': 'select',
49 | 'content': [
50 | {
51 | 'id': 'site',
52 | 'options': Editsignin._site_url,
53 | 'default': '52pt.site'
54 | }
55 | ]
56 | },
57 | ],
58 | [
59 | {
60 | 'title': '新站域名',
61 | 'required': "required",
62 | 'tooltip': '不要 http:// 和结尾的 / ,仅填写域名部分,具体参考站点信息;注意:重启后生效',
63 | 'type': 'text',
64 | 'content': [
65 | {
66 | 'id': 'new_site',
67 | 'placeholder': 'ptchdbits.co #不要 http:// 和结尾的 /',
68 | }
69 | ]
70 | }
71 | ],
72 | [
73 | {
74 | 'type': 'text',
75 | 'content': [
76 | {
77 | 'id': 'tip',
78 | 'default': '当前插件修改完成后,需重启 NASTool 生效',
79 | 'placeholder': '当前插件修改完成后,需重启 NASTool 以后生效'
80 | }
81 | ]
82 | }
83 | ]
84 | ]
85 | }
86 | ]
87 |
88 | @staticmethod
89 | def get_script():
90 | """
91 | 返回插件额外的JS代码
92 | """
93 | return """
94 | $(function(){
95 | $("#editsignin_tip").css('color', 'red');
96 | $("#editsignin_tip").attr('readonly', true)
97 | })
98 | """
99 |
100 | def __build_class(self):
101 | for site_schema in self._site_schema:
102 | try:
103 | self._site_url[site_schema.site_url] = site_schema.site_url
104 | self._site_schema_map[site_schema.site_url] = site_schema
105 | except Exception as e:
106 | ExceptionUtils.exception_traceback(e)
107 | return None
108 |
109 | def init_config(self, config=None):
110 | site = config.get('site')
111 | new_site = config.get('new_site')
112 |
113 | self._site_schema = SubmoduleHelper.import_submodules('app.plugins.modules._autosignin',
114 | filter_func=lambda _, obj: hasattr(obj, 'match'))
115 |
116 | self.__build_class()
117 |
118 | # 获取文件签到组件的文件名
119 | if site and new_site:
120 | filename = f"{self._site_schema_map[site].__module__.split('.')[-1]}.py"
121 | file = importlib.resources.files('app.plugins.modules._autosignin').joinpath(filename)
122 |
123 | # 临时存储的文件内容
124 | file_data = ""
125 | # 查找所在文件的位置
126 | with open(file, "r", encoding="utf-8") as f:
127 | for line in f:
128 | if line.find(site) != -1:
129 | line = line.replace(site, new_site)
130 | file_data += line
131 | # 更新文件内容
132 | with open(file, "w", encoding="utf-8") as f:
133 | f.write(file_data)
134 |
135 | self.update_config({
136 | "site": "",
137 | "new_site": ""
138 | })
139 |
140 |
141 |
142 |
143 | def get_state(self):
144 | return False
145 |
146 | def stop_service(self):
147 | """
148 | 退出插件
149 | """
150 | pass
--------------------------------------------------------------------------------
/plugins/images/actor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/actor.png
--------------------------------------------------------------------------------
/plugins/images/brush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/brush.png
--------------------------------------------------------------------------------
/plugins/images/customindexer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/customindexer.png
--------------------------------------------------------------------------------
/plugins/images/douban.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/douban.png
--------------------------------------------------------------------------------
/plugins/images/editsignin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/editsignin.png
--------------------------------------------------------------------------------
/plugins/images/invites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/invites.png
--------------------------------------------------------------------------------
/plugins/images/iyuu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/iyuu.png
--------------------------------------------------------------------------------
/plugins/images/jackett.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/jackett.png
--------------------------------------------------------------------------------
/plugins/images/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/link.png
--------------------------------------------------------------------------------
/plugins/images/prowlarr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/prowlarr.png
--------------------------------------------------------------------------------
/plugins/images/syncindexer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/syncindexer.png
--------------------------------------------------------------------------------
/plugins/images/tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mattoids/nas-tools-plugin/db77342518e90b3d978a9a20d7c82957eae699d6/plugins/images/tag.png
--------------------------------------------------------------------------------
/plugins/invitessignin.py:
--------------------------------------------------------------------------------
1 | import random
2 | import json
3 | import re
4 | from datetime import datetime, timedelta
5 | from threading import Event, Lock
6 | from time import sleep
7 |
8 | import pytz
9 | from apscheduler.schedulers.background import BackgroundScheduler
10 | from apscheduler.triggers.cron import CronTrigger
11 | from jinja2 import Template
12 |
13 | from app.downloader import Downloader
14 | from app.media import DouBan
15 | from app.media.meta import MetaInfo
16 | from app.plugins import EventHandler
17 | from app.plugins.modules._base import _IPluginModule
18 | from app.searcher import Searcher
19 | from app.subscribe import Subscribe
20 | from app.utils import ExceptionUtils, RequestUtils
21 | from app.utils.types import SearchType, RssType, EventType, MediaType
22 | from config import Config
23 | from web.backend.web_utils import WebUtils
24 |
25 | lock = Lock()
26 |
27 | class InvitesSignin(_IPluginModule):
28 | # 插件名称
29 | module_name = "药丸签到"
30 | # 插件描述
31 | module_desc = "药丸论坛签到。"
32 | # 插件图标
33 | module_icon = "invites.png"
34 | # 主题色
35 | module_color = "#FFFFFF"
36 | # 插件版本
37 | module_version = "1.1"
38 | # 插件作者
39 | module_author = "thsrite"
40 | # 作者主页
41 | author_url = "https://github.com/thsrite"
42 | # 插件配置项ID前缀
43 | module_config_prefix = "invitessignin_"
44 | # 加载顺序
45 | module_order = 24
46 | # 可使用的用户级别
47 | auth_level = 2
48 |
49 | # 私有属性
50 | _scheduler = None
51 | # 开关属性
52 | _enabled = False
53 | # 任务执行间隔
54 | _cron = None
55 | _cookie = None
56 | _onlyonce = False
57 | _notify = False
58 |
59 |
60 | @staticmethod
61 | def get_fields():
62 | return [
63 | # 同一板块
64 | {
65 | 'type': 'div',
66 | 'content': [
67 | [
68 | {
69 | 'title': '开启插件',
70 | 'required': "",
71 | 'tooltip': '开启后,将定时对药丸论坛进行签到。',
72 | 'type': 'switch',
73 | 'id': 'enabled',
74 | },
75 | {
76 | 'title': '运行时通知',
77 | 'required': "",
78 | 'tooltip': '开启后,将通知签到结果。',
79 | 'type': 'switch',
80 | 'id': 'notify',
81 | },
82 | {
83 | 'title': '立即运行一次',
84 | 'required': "",
85 | 'tooltip': '开启后,将立即运行一次',
86 | 'type': 'switch',
87 | 'id': 'onlyonce',
88 | }
89 | ],
90 | [
91 | {
92 | 'title': '签到周期',
93 | 'required': "required",
94 | 'type': 'text',
95 | 'content': [
96 | {
97 | 'id': 'cron',
98 | 'placeholder': '0 0 0 ? *',
99 | }
100 | ]
101 | },
102 | {
103 | 'title': '药丸cookie',
104 | 'required': "required",
105 | 'type': 'text',
106 | 'content': [
107 | {
108 | 'id': 'cookie',
109 | 'placeholder': '药丸cookie',
110 | }
111 | ]
112 | }
113 | ],
114 | ]
115 | }
116 | ]
117 |
118 | def init_config(self, config=None):
119 | # 停止运行
120 | self.stop_service()
121 |
122 | if config:
123 | self._enabled = config.get("enabled")
124 | self._cron = config.get("cron")
125 | self._cookie = config.get("cookie")
126 | self._notify = config.get("notify")
127 | self._onlyonce = config.get("onlyonce")
128 |
129 | # 加载模块
130 | if self._enabled:
131 | # 定时服务
132 | self._scheduler = BackgroundScheduler(timezone=Config().get_timezone())
133 |
134 | if self._cron:
135 | try:
136 | self._scheduler.add_job(func=self.__signin,
137 | trigger=CronTrigger.from_crontab(self._cron),
138 | name="药丸签到")
139 | except Exception as err:
140 | self.error(f"定时任务配置错误:{str(err)}")
141 |
142 | if self._onlyonce:
143 | self.info(f"药丸签到服务启动,立即运行一次")
144 | self._scheduler.add_job(func=self.__signin, trigger='date',
145 | run_date=datetime.now(tz=pytz.timezone(Config().get_timezone())) + timedelta(seconds=3),
146 | name="药丸签到")
147 | # 关闭一次性开关
148 | self._onlyonce = False
149 | self.update_config({
150 | "onlyonce": False,
151 | "cron": self._cron,
152 | "enabled": self._enabled,
153 | "cookie": self._cookie,
154 | "notify": self._notify,
155 | })
156 |
157 | # 启动任务
158 | if self._scheduler.get_jobs():
159 | self._scheduler.print_jobs()
160 | self._scheduler.start()
161 |
162 | def get_state(self):
163 | return self._enabled
164 |
165 | def stop_service(self):
166 | """
167 | 停止插件
168 | """
169 | try:
170 | if self._scheduler:
171 | self._scheduler.remove_all_jobs()
172 | if self._scheduler.running:
173 | self._scheduler.shutdown()
174 | self._scheduler = None
175 | except Exception as e:
176 | self.error("退出插件失败:%s" % str(e))
177 |
178 | def __signin(self):
179 | """
180 | 药丸签到
181 | """
182 | res = RequestUtils(cookies=self._cookie).get_res(url="https://invites.fun")
183 | if not res or res.status_code != 200:
184 | self.error("请求药丸错误")
185 | return
186 |
187 | # 获取csrfToken
188 | pattern = r'"csrfToken":"(.*?)"'
189 | csrfToken = re.findall(pattern, res.text)
190 | if not csrfToken:
191 | self.error("请求csrfToken失败")
192 | return
193 |
194 | csrfToken = csrfToken[0]
195 | self.info(f"获取csrfToken成功 {csrfToken}")
196 |
197 | # 获取userid
198 | pattern = r'"userId":(\d+)'
199 | match = re.search(pattern, res.text)
200 |
201 | if match:
202 | userId = match.group(1)
203 | self.info(f"获取userid成功 {userId}")
204 | else:
205 | self.error("未找到userId")
206 | return
207 |
208 | headers = {
209 | "X-Csrf-Token": csrfToken,
210 | "X-Http-Method-Override": "PATCH",
211 | "Cookie": self._cookie
212 | }
213 |
214 | data = {
215 | "data": {
216 | "type": "users",
217 | "attributes": {
218 | "canCheckin": False,
219 | "totalContinuousCheckIn": 2
220 | },
221 | "id": userId
222 | }
223 | }
224 |
225 | # 开始签到
226 | res = RequestUtils(headers=headers).post_res(url=f"https://invites.fun/api/users/{userId}", json=data)
227 |
228 | if not res or res.status_code != 200:
229 | self.error("药丸签到失败")
230 | return
231 |
232 | sign_dict = json.loads(res.text)
233 | money = sign_dict['data']['attributes']['money']
234 | totalContinuousCheckIn = sign_dict['data']['attributes']['totalContinuousCheckIn']
235 |
236 | # 发送通知
237 | if self._notify:
238 | self.send_message(
239 | title="【药丸签到任务完成】",
240 | text=f"累计签到 {totalContinuousCheckIn} \n"
241 | f"剩余药丸 {money}")
--------------------------------------------------------------------------------
/plugins/jackett.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import xml.dom.minidom
3 |
4 | from app.utils import RequestUtils
5 | from app.helper import IndexerConf
6 | from app.utils import ExceptionUtils, DomUtils
7 |
8 | from app.helper import DbHelper
9 | from app.plugins.modules._base import _IPluginModule
10 | from config import Config
11 |
12 | class Jackett(_IPluginModule):
13 | # 插件名称
14 | module_name = "Jackett"
15 | # 插件描述
16 | module_desc = "让内荐索引器支持检索Jackett站点资源"
17 | # 插件图标
18 | module_icon = "jackett.png"
19 | # 主题色
20 | module_color = "#C90425"
21 | # 插件版本
22 | module_version = "1.1"
23 | # 插件作者
24 | module_author = "mattoid"
25 | # 作者主页
26 | author_url = "https://github.com/Mattoids"
27 | # 插件配置项ID前缀
28 | module_config_prefix = "jackett_"
29 | # 加载顺序
30 | module_order = 15
31 | # 可使用的用户级别
32 | auth_level = 1
33 |
34 | # 私有属性
35 | _enable = False
36 | _dbhelper = None
37 | _host = ""
38 | _api_key = ""
39 | _password = ""
40 | _show_more_sites = False
41 | _sites = []
42 |
43 | @staticmethod
44 | def get_fields():
45 | return [
46 | # 同一板块
47 | {
48 | 'type': 'div',
49 | 'content': [
50 | # 同一行
51 | [
52 | {
53 | 'title': 'Jackett地址',
54 | 'required': "required",
55 | 'tooltip': 'Jackett访问地址和端口,如为https需加https://前缀。注意需要先在Jackett中添加indexer,才能正常测试通过和使用',
56 | 'type': 'text',
57 | 'content': [
58 | {
59 | 'id': 'host',
60 | 'placeholder': 'http://127.0.0.1:9117',
61 | }
62 | ]
63 | },
64 | {
65 | 'title': 'Api Key',
66 | 'required': "required",
67 | 'tooltip': 'Jackett管理界面右上角复制API Key',
68 | 'type': 'text',
69 | 'content': [
70 | {
71 | 'id': 'api_key',
72 | 'placeholder': '',
73 | }
74 | ]
75 | }
76 | ],
77 | [
78 | {
79 | 'title': '密码',
80 | 'required': "required",
81 | 'tooltip': 'Jackett管理界面中配置的Admin password,如未配置可为空',
82 | 'type': 'password',
83 | 'content': [
84 | {
85 | 'id': 'password',
86 | 'placeholder': '',
87 | }
88 | ]
89 | }
90 | ],
91 | [
92 | {
93 | 'title': '开启内建站点',
94 | 'required': "",
95 | 'tooltip': '开启后会在内建索引器展示内置的公开站点,不开启则只显示jackett的站点',
96 | 'type': 'switch',
97 | 'id': 'show_more_sites',
98 | }
99 | ]
100 | ]
101 | }
102 | ]
103 |
104 | # def get_page(self):
105 | # """
106 | # 插件的额外页面,返回页面标题和页面内容
107 | # :return: 标题,页面内容,确定按钮响应函数
108 | # """
109 | # return "测试", None, None
110 |
111 | def get_status(self):
112 | """
113 | 检查连通性
114 | :return: True、False
115 | """
116 | if not self._api_key or not self._host:
117 | return False
118 | self._sites = self.get_indexers()
119 | return True if self._sites else False
120 |
121 | def init_config(self, config=None):
122 | self.info(f"初始化配置{config}")
123 |
124 | if not config:
125 | return
126 |
127 | self._dbhelper = DbHelper()
128 | if config:
129 | self._host = config.get("host")
130 | self._api_key = config.get("api_key")
131 | self._password = config.get("password")
132 | self._enable = self.get_status()
133 | self.__update_config(showMoreSites=config.get("show_more_sites"))
134 |
135 | def get_state(self):
136 | return self._enable
137 |
138 | def stop_service(self):
139 | """
140 | 退出插件
141 | """
142 | pass
143 |
144 | def __update_config(self, showMoreSites = False):
145 | show_more_sites = Config().get_config("laboratory").get('show_more_sites')
146 | if show_more_sites != showMoreSites:
147 | cfg = Config().get_config()
148 | cfg["laboratory"]["show_more_sites"] = showMoreSites
149 | Config().save_config(cfg)
150 |
151 | def get_indexers(self):
152 | """
153 | 获取配置的jackett indexer
154 | :return: indexer 信息 [(indexerId, indexerName, url)]
155 | """
156 | # 获取Cookie
157 | cookie = None
158 | session = requests.session()
159 | res = RequestUtils(session=session).post_res(url=f"{self._host}/UI/Dashboard", data={"password": self._password},
160 | params={"password": self._password})
161 | if res and session.cookies:
162 | cookie = session.cookies.get_dict()
163 | indexer_query_url = f"{self._host}/api/v2.0/indexers?configured=true"
164 | try:
165 | ret = RequestUtils(cookies=cookie).get_res(indexer_query_url)
166 | if not ret or not ret.json():
167 | return []
168 | return [IndexerConf({"id": f'{v["id"]}-{self.module_name}',
169 | "name": f'【{self.module_name}】{v["name"]}',
170 | "domain": f'{self._host}/api/v2.0/indexers/{v["id"]}/results/torznab/',
171 | "public": True if v['type'] == 'public' else False,
172 | "builtin": False,
173 | "proxy": True,
174 | "parser": self.module_name})
175 | for v in ret.json()]
176 | except Exception as e2:
177 | ExceptionUtils.exception_traceback(e2)
178 | return []
179 |
180 | def search(self, indexer,
181 | keyword,
182 | page):
183 | """
184 | 根据关键字多线程检索
185 | """
186 | if not indexer or not keyword:
187 | return None
188 | self.info(f"【{self.module_name}】开始检索Indexer:{indexer.name} ...")
189 | # 特殊符号处理
190 | api_url = f"{indexer.domain}?apikey={self._api_key}&t=search&q={keyword}"
191 |
192 | result_array = self.__parse_torznabxml(api_url)
193 |
194 | if len(result_array) == 0:
195 | self.warn(f"【{self.module_name}】{indexer.name} 未检索到数据")
196 | # self.progress.update(ptype='search', text=f"{indexer.name} 未检索到数据")
197 | return []
198 | else:
199 | self.warn(f"【{self.module_name}】{indexer.name} 返回数据:{len(result_array)}")
200 | return result_array
201 |
202 | @staticmethod
203 | def __parse_torznabxml(url):
204 | """
205 | 从torznab xml中解析种子信息
206 | :param url: URL地址
207 | :return: 解析出来的种子信息列表
208 | """
209 | if not url:
210 | return []
211 | try:
212 | ret = RequestUtils(timeout=10).get_res(url)
213 | except Exception as e2:
214 | ExceptionUtils.exception_traceback(e2)
215 | return []
216 | if not ret:
217 | return []
218 | xmls = ret.text
219 | if not xmls:
220 | return []
221 |
222 | torrents = []
223 | try:
224 | # 解析XML
225 | dom_tree = xml.dom.minidom.parseString(xmls)
226 | root_node = dom_tree.documentElement
227 | items = root_node.getElementsByTagName("item")
228 | for item in items:
229 | try:
230 | # indexer id
231 | indexer_id = DomUtils.tag_value(item, "jackettindexer", "id",
232 | default=DomUtils.tag_value(item, "prowlarrindexer", "id", ""))
233 | # indexer
234 | indexer = DomUtils.tag_value(item, "jackettindexer",
235 | default=DomUtils.tag_value(item, "prowlarrindexer", default=""))
236 |
237 | # 标题
238 | title = DomUtils.tag_value(item, "title", default="")
239 | if not title:
240 | continue
241 | # 种子链接
242 | enclosure = DomUtils.tag_value(item, "enclosure", "url", default="")
243 | if not enclosure:
244 | continue
245 | # 描述
246 | description = DomUtils.tag_value(item, "description", default="")
247 | # 种子大小
248 | size = DomUtils.tag_value(item, "size", default=0)
249 | # 种子页面
250 | page_url = DomUtils.tag_value(item, "comments", default="")
251 |
252 | # 做种数
253 | seeders = 0
254 | # 下载数
255 | peers = 0
256 | # 是否免费
257 | freeleech = False
258 | # 下载因子
259 | downloadvolumefactor = 1.0
260 | # 上传因子
261 | uploadvolumefactor = 1.0
262 | # imdbid
263 | imdbid = ""
264 |
265 | torznab_attrs = item.getElementsByTagName("torznab:attr")
266 | for torznab_attr in torznab_attrs:
267 | name = torznab_attr.getAttribute('name')
268 | value = torznab_attr.getAttribute('value')
269 | if name == "seeders":
270 | seeders = value
271 | if name == "peers":
272 | peers = value
273 | if name == "downloadvolumefactor":
274 | downloadvolumefactor = value
275 | if float(downloadvolumefactor) == 0:
276 | freeleech = True
277 | if name == "uploadvolumefactor":
278 | uploadvolumefactor = value
279 | if name == "imdbid":
280 | imdbid = value
281 |
282 | tmp_dict = {'indexer_id': indexer_id,
283 | 'indexer': indexer,
284 | 'title': title,
285 | 'enclosure': enclosure,
286 | 'description': description,
287 | 'size': size,
288 | 'seeders': seeders,
289 | 'peers': peers,
290 | 'freeleech': freeleech,
291 | 'downloadvolumefactor': downloadvolumefactor,
292 | 'uploadvolumefactor': uploadvolumefactor,
293 | 'page_url': page_url,
294 | 'imdbid': imdbid}
295 | torrents.append(tmp_dict)
296 | except Exception as e:
297 | ExceptionUtils.exception_traceback(e)
298 | continue
299 | except Exception as e2:
300 | ExceptionUtils.exception_traceback(e2)
301 | pass
302 |
303 | return torrents
--------------------------------------------------------------------------------
/plugins/prowlarr.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import xml.dom.minidom
3 |
4 | from app.utils import RequestUtils
5 | from app.helper import IndexerConf
6 | from app.utils import ExceptionUtils, DomUtils
7 |
8 | from app.helper import DbHelper
9 | from app.plugins.modules._base import _IPluginModule
10 | from config import Config
11 |
12 | class Prowlarr(_IPluginModule):
13 | # 插件名称
14 | module_name = "Prowlarr"
15 | # 插件描述
16 | module_desc = "让内荐索引器支持检索Prowlarr站点资源"
17 | # 插件图标
18 | module_icon = "prowlarr.png"
19 | # 主题色
20 | module_color = "#C90425"
21 | # 插件版本
22 | module_version = "1.1"
23 | # 插件作者
24 | module_author = "mattoid"
25 | # 作者主页
26 | author_url = "https://github.com/Mattoids"
27 | # 插件配置项ID前缀
28 | module_config_prefix = "prowlarr_"
29 | # 加载顺序
30 | module_order = 23
31 | # 可使用的用户级别
32 | auth_level = 1
33 |
34 | # 私有属性
35 | _enable = False
36 | _dbhelper = None
37 | _host = ""
38 | _api_key = ""
39 | _password = ""
40 | _show_more_sites = False
41 | _sites = []
42 |
43 | @staticmethod
44 | def get_fields():
45 | return [
46 | # 同一板块
47 | {
48 | 'type': 'div',
49 | 'content': [
50 | # 同一行
51 | [
52 | {
53 | 'title': 'Prowlarr地址',
54 | 'required': "required",
55 | 'tooltip': 'Prowlarr访问地址和端口,如为https需加https://前缀。注意需要先在Prowlarr中添加搜刮器,同时勾选所有搜刮器后搜索一次,才能正常测试通过和使用',
56 | 'type': 'text',
57 | 'content': [
58 | {
59 | 'id': 'host',
60 | 'placeholder': 'http://127.0.0.1:9696',
61 | }
62 | ]
63 | },
64 | {
65 | 'title': 'Api Key',
66 | 'required': "required",
67 | 'tooltip': '在Prowlarr->Settings->General->Security-> API Key中获取',
68 | 'type': 'text',
69 | 'content': [
70 | {
71 | 'id': 'api_key',
72 | 'placeholder': '',
73 | }
74 | ]
75 | }
76 | ],
77 | [
78 | {
79 | 'title': '开启内建站点',
80 | 'required': "",
81 | 'tooltip': '开启后会在内建索引器展示内置的公开站点,不开启则只显示prowlarr的站点',
82 | 'type': 'switch',
83 | 'id': 'show_more_sites',
84 | }
85 | ]
86 | ]
87 | }
88 | ]
89 |
90 | # def get_page(self):
91 | # """
92 | # 插件的额外页面,返回页面标题和页面内容
93 | # :return: 标题,页面内容,确定按钮响应函数
94 | # """
95 | # return "测试", None, None
96 |
97 | def get_status(self):
98 | """
99 | 检查连通性
100 | :return: True、False
101 | """
102 | if not self._api_key or not self._host:
103 | return False
104 | self._sites = self.get_indexers()
105 | return True if self._sites else False
106 |
107 | def init_config(self, config=None):
108 | self.info(f"初始化配置{config}")
109 |
110 | if not config:
111 | return
112 |
113 | self._dbhelper = DbHelper()
114 | if config:
115 | self._host = config.get("host")
116 | self._api_key = config.get("api_key")
117 | self._enable = self.get_status()
118 | self.__update_config(showMoreSites=config.get("show_more_sites"))
119 |
120 | def get_state(self):
121 | return self._enable
122 |
123 | def stop_service(self):
124 | """
125 | 退出插件
126 | """
127 | pass
128 |
129 | def __update_config(self, showMoreSites = False):
130 | show_more_sites = Config().get_config("laboratory").get('show_more_sites')
131 | if show_more_sites != showMoreSites:
132 | cfg = Config().get_config()
133 | cfg["laboratory"]["show_more_sites"] = showMoreSites
134 | Config().save_config(cfg)
135 |
136 | def get_indexers(self):
137 | """
138 | 获取配置的prowlarr indexer
139 | :return: indexer 信息 [(indexerId, indexerName, url)]
140 | """
141 | indexer_query_url = f"{self._host}/api/v1/indexerstats?apikey={self._api_key}"
142 | try:
143 | ret = RequestUtils().get_res(indexer_query_url)
144 | except Exception as e2:
145 | ExceptionUtils.exception_traceback(e2)
146 | return []
147 | if not ret:
148 | return []
149 | indexers = ret.json().get("indexers", [])
150 | return [IndexerConf({"id": f'{v["indexerName"]}-{self.module_name}',
151 | "name": f'【{self.module_name}】{v["indexerName"]}',
152 | "domain": f'{self._host}/{v["indexerId"]}/api',
153 | "builtin": False,
154 | "public": True,
155 | "proxy": True,
156 | "parser": self.module_name})
157 | for v in indexers]
158 |
159 | def search(self, indexer,
160 | keyword,
161 | page):
162 | """
163 | 根据关键字多线程检索
164 | """
165 | if not indexer or not keyword:
166 | return None
167 | self.info(f"【{self.module_name}】开始检索Indexer:{indexer.name} ...")
168 | # 特殊符号处理
169 | api_url = f"{indexer.domain}?apikey={self._api_key}&t=search&q={keyword}"
170 |
171 | result_array = self.__parse_torznabxml(api_url)
172 |
173 | if len(result_array) == 0:
174 | self.warn(f"【{self.module_name}】{indexer.name} 未检索到数据")
175 | # self.progress.update(ptype='search', text=f"{indexer.name} 未检索到数据")
176 | return []
177 | else:
178 | self.warn(f"【{self.module_name}】{indexer.name} 返回数据:{len(result_array)}")
179 | return result_array
180 |
181 | @staticmethod
182 | def __parse_torznabxml(url):
183 | """
184 | 从torznab xml中解析种子信息
185 | :param url: URL地址
186 | :return: 解析出来的种子信息列表
187 | """
188 | if not url:
189 | return []
190 | try:
191 | ret = RequestUtils(timeout=10).get_res(url)
192 | except Exception as e2:
193 | ExceptionUtils.exception_traceback(e2)
194 | return []
195 | if not ret:
196 | return []
197 | xmls = ret.text
198 | if not xmls:
199 | return []
200 |
201 | torrents = []
202 | try:
203 | # 解析XML
204 | dom_tree = xml.dom.minidom.parseString(xmls)
205 | root_node = dom_tree.documentElement
206 | items = root_node.getElementsByTagName("item")
207 | for item in items:
208 | try:
209 | # indexer id
210 | indexer_id = DomUtils.tag_value(item, "jackettindexer", "id",
211 | default=DomUtils.tag_value(item, "prowlarrindexer", "id", ""))
212 | # indexer
213 | indexer = DomUtils.tag_value(item, "jackettindexer",
214 | default=DomUtils.tag_value(item, "prowlarrindexer", default=""))
215 |
216 | # 标题
217 | title = DomUtils.tag_value(item, "title", default="")
218 | if not title:
219 | continue
220 | # 种子链接
221 | enclosure = DomUtils.tag_value(item, "enclosure", "url", default="")
222 | if not enclosure:
223 | continue
224 | # 描述
225 | description = DomUtils.tag_value(item, "description", default="")
226 | # 种子大小
227 | size = DomUtils.tag_value(item, "size", default=0)
228 | # 种子页面
229 | page_url = DomUtils.tag_value(item, "comments", default="")
230 |
231 | # 做种数
232 | seeders = 0
233 | # 下载数
234 | peers = 0
235 | # 是否免费
236 | freeleech = False
237 | # 下载因子
238 | downloadvolumefactor = 1.0
239 | # 上传因子
240 | uploadvolumefactor = 1.0
241 | # imdbid
242 | imdbid = ""
243 |
244 | torznab_attrs = item.getElementsByTagName("torznab:attr")
245 | for torznab_attr in torznab_attrs:
246 | name = torznab_attr.getAttribute('name')
247 | value = torznab_attr.getAttribute('value')
248 | if name == "seeders":
249 | seeders = value
250 | if name == "peers":
251 | peers = value
252 | if name == "downloadvolumefactor":
253 | downloadvolumefactor = value
254 | if float(downloadvolumefactor) == 0:
255 | freeleech = True
256 | if name == "uploadvolumefactor":
257 | uploadvolumefactor = value
258 | if name == "imdbid":
259 | imdbid = value
260 |
261 | tmp_dict = {'indexer_id': indexer_id,
262 | 'indexer': indexer,
263 | 'title': title,
264 | 'enclosure': enclosure,
265 | 'description': description,
266 | 'size': size,
267 | 'seeders': seeders,
268 | 'peers': peers,
269 | 'freeleech': freeleech,
270 | 'downloadvolumefactor': downloadvolumefactor,
271 | 'uploadvolumefactor': uploadvolumefactor,
272 | 'page_url': page_url,
273 | 'imdbid': imdbid}
274 | torrents.append(tmp_dict)
275 | except Exception as e:
276 | ExceptionUtils.exception_traceback(e)
277 | continue
278 | except Exception as e2:
279 | ExceptionUtils.exception_traceback(e2)
280 | pass
281 |
282 | return torrents
--------------------------------------------------------------------------------
/plugins/syncindexer.py:
--------------------------------------------------------------------------------
1 | import json
2 | import pytz
3 |
4 | from threading import Event
5 |
6 | import log
7 | from config import Config
8 | from app.helper import DbHelper
9 | from datetime import datetime, timedelta
10 | from app.plugins.modules._base import _IPluginModule
11 | from app.utils import RequestUtils, SchedulerUtils, StringUtils, JsonUtils
12 | from apscheduler.schedulers.background import BackgroundScheduler
13 | from app.utils.types import EventType
14 | from app.plugins import EventHandler
15 | from app.sites.sites import Sites
16 | from web.backend.user import User
17 | from jinja2 import Template
18 |
19 |
20 | class SyncIndexer(_IPluginModule):
21 | # 插件名称
22 | module_name = "同步索引规则"
23 | # 插件描述
24 | module_desc = "可根据配置站点自动获取已适配的索引规则和刷流规则。可以配合 '自定义索引器' 和 '自定义刷流规则' 插件使用。"
25 | # 插件图标
26 | module_icon = "syncindexer.png"
27 | # 主题色
28 | module_color = "#02C4E0"
29 | # 插件版本
30 | module_version = "1.9.1"
31 | # 插件作者
32 | module_author = "mattoid"
33 | # 作者主页
34 | author_url = "https://github.com/Mattoids"
35 | # 插件配置项ID前缀
36 | module_config_prefix = "syncindexer_"
37 | # 加载顺序
38 | module_order = 24
39 | # 可使用的用户级别
40 | auth_level = 1
41 | # 退出事件
42 | _event = Event()
43 |
44 | # 私有属性
45 | _scheduler = None
46 | _gitee_url = "https://gitee.com/Mattoid/nas-tools-plugin/raw/master"
47 | _github_url = "https://github.com/Mattoids/nas-tools-plugin/raw/master"
48 |
49 | _enable = False
50 | _gitee_switch = 0
51 | _refresh = False
52 | _onlyonce = False
53 | _cron = ""
54 |
55 | @staticmethod
56 | def get_fields():
57 | return [
58 | # 同一板块
59 | {
60 | 'type': 'div',
61 | 'content': [
62 | # 同一行
63 | [
64 | {
65 | 'title': '开启自动同步',
66 | 'required': "required",
67 | 'tooltip': '开启同步后将会从作者github仓库中同步站点的索引数据和站点的刷流数据,达到支持更多的站点的目的。',
68 | 'type': 'switch',
69 | 'id': 'enable'
70 | },
71 | {
72 | 'title': '使用gitee仓库',
73 | 'required': "required",
74 | 'tooltip': '如果因为网络原因无法使用github仓库同步的情况下,可以开启该选项从国内公开仓库获取数据。',
75 | 'type': 'switch',
76 | 'id': 'gitee_switch'
77 | },
78 | {
79 | 'title': '刷新所有站点',
80 | 'required': "required",
81 | 'tooltip': '强制刷新所有站点将会清除同步历史,然后对现有配置的站点进行重新同步。',
82 | 'type': 'switch',
83 | 'id': 'refresh'
84 | },
85 | {
86 | 'title': '立即运行一次',
87 | 'required': "required",
88 | 'tooltip': '开启将会直接运行一次同步任务,并且自动关闭该选项。',
89 | 'type': 'switch',
90 | 'id': 'onlyonce'
91 | }
92 | ],
93 | [
94 | {
95 | 'title': '同步周期',
96 | 'required': "",
97 | 'tooltip': '定时同步作者更新的站点索引和刷流信息',
98 | 'type': 'text',
99 | 'content': [
100 | {
101 | 'id': 'cron',
102 | 'placeholder': '0 0 0 ? *',
103 | }
104 | ]
105 | },
106 | ]
107 | ]
108 | }
109 | ]
110 |
111 | def get_page(self):
112 | """
113 | 插件的额外页面,返回页面标题和页面内容
114 | :return: 标题,页面内容,确定按钮响应函数
115 | """
116 | results = self.get_history()
117 | template = """
118 |
119 |
120 |
121 |
122 | 站点 |
123 | 同步类型 |
124 | 状态 |
125 | 描述 |
126 | 同步时间 |
127 |
128 |
129 |
130 | {% if HistoryCount > 0 %}
131 | {% for Item in SyncIndexerHistory %}
132 |
133 |
134 | {{ Item.site }}
135 | |
136 |
137 | {{ Item.type }}
138 | |
139 |
140 | {{ Item.status }}
141 | |
142 |
143 | {{ Item.message }}
144 | |
145 |
146 | {{ Item.time }}
147 | |
148 |
149 | {% endfor %}
150 | {% else %}
151 |
152 | 没有数据 |
153 |
154 | {% endif %}
155 |
156 |
157 |
158 | """
159 | return "同步历史", Template(template).render(HistoryCount=len(results),
160 | SyncIndexerHistory=results), None
161 |
162 | @staticmethod
163 | def get_script():
164 | pass
165 |
166 | def init_config(self, config=None):
167 | # 读取配置
168 | if config:
169 | self._enable = config.get("enable")
170 | self._gitee_switch = config.get("gitee_switch")
171 | self._refresh = config.get("refresh")
172 | self._onlyonce = config.get("onlyonce")
173 | self._cron = config.get("cron")
174 |
175 | if self._enable:
176 | # 定时服务
177 | self._scheduler = BackgroundScheduler(timezone=Config().get_timezone())
178 |
179 | if self._onlyonce:
180 | self._onlyonce = False
181 | self.info(f"索引同步,立即运行一次")
182 | self._scheduler.add_job(self.__update_site_indexer, 'date',
183 | run_date=datetime.now(tz=pytz.timezone(Config().get_timezone())) + timedelta(
184 | seconds=3))
185 |
186 | self.update_config({
187 | "enable": self._enable,
188 | "gitee_switch": self._gitee_switch,
189 | "refresh": self._refresh,
190 | "onlyonce": self._onlyonce,
191 | "cron": self._cron
192 | })
193 |
194 | # 周期运行
195 | if self._cron:
196 | self.info(f"自动同步索引规则,周期:{self._cron}")
197 | SchedulerUtils.start_job(scheduler=self._scheduler,
198 | func=self.__update_site_indexer,
199 | func_desc="同步索引推责",
200 | cron=str(self._cron))
201 |
202 | # 启动任务
203 | if self._scheduler.get_jobs():
204 | self._scheduler.print_jobs()
205 | self._scheduler.start()
206 |
207 |
208 | def get_state(self):
209 | return self._enable and self._cron
210 |
211 | def stop_service(self):
212 | """
213 | 退出插件
214 | """
215 | try:
216 | if self._scheduler:
217 | self._scheduler.remove_all_jobs()
218 | if self._scheduler.running:
219 | self._event.set()
220 | self._scheduler.shutdown()
221 | self._event.clear()
222 | self._scheduler = None
223 | except Exception as e:
224 | print(str(e))
225 |
226 | def __update_site_indexer(self):
227 | for site in Sites().get_sites():
228 | site_url = site.get("signurl")
229 | site_domain = StringUtils.get_url_domain(site_url)
230 |
231 | self.__update_indexer(site_url=site_url, site_domain=site_domain)
232 | self.__update_brush(site_url=site_url, site_domain=site_domain)
233 |
234 | return True
235 |
236 | def __update_indexer(self, site_url, site_domain):
237 | if not self._refresh and not User().get_indexer(url=site_url):
238 | self.__insert_history(site_domain, "indexer", False, "未开启刷新所有站点,本次不更新!")
239 | return True
240 |
241 | url = f"{self._gitee_url if self._gitee_switch else self._github_url}/sites/{site_domain}.json"
242 |
243 | result = RequestUtils(timeout=5, proxies=Config().get_proxies()).get_res(url)
244 |
245 | if result.status_code == 404:
246 | self.__insert_history(site_domain, "indexer", False, "站点索引规则不存在,请联系作者进行适配!")
247 | return False
248 |
249 | if result.status_code == 200:
250 | if not DbHelper().get_indexer_custom_site(site_url):
251 | DbHelper().insert_indexer_custom_site(site_url, json.dumps(result.json()))
252 | elif self._refresh:
253 | DbHelper().insert_indexer_custom_site(site_url, json.dumps(result.json()))
254 | else:
255 | self.__insert_history(site_domain, "indexer", False, result.status_code)
256 | return False
257 |
258 | self.__insert_history(site_domain, "indexer", True, "成功")
259 | return True
260 |
261 | def __update_brush(self, site_url, site_domain):
262 | if not self._refresh and site_domain not in User().get_brush_conf():
263 | self.__insert_history(site_domain, "brush", False, "未开启刷新所有站点,本次不更新!")
264 | return True
265 |
266 | url = f"{self._gitee_url if self._gitee_switch else self._github_url}/sites/brush/{site_domain}.json"
267 |
268 | result = RequestUtils(timeout=5, proxies=Config().get_proxies()).get_res(url)
269 |
270 | if result.status_code == 404:
271 | self.__insert_history(site_domain, "brush", False, "站点刷流规则不存在,请联系作者进行适配!")
272 | return False
273 |
274 | if result.status_code == 200:
275 | site_brush = json.loads(json.dumps(self.get_config("CustomBrush") or {}))
276 | if site_domain not in site_brush:
277 | site_brush[site_domain] = json.loads(result.content)
278 | elif self._refresh:
279 | site_brush[site_domain] = json.loads(result.content)
280 |
281 | self.update_config(site_brush, "CustomBrush")
282 | else:
283 | self.__insert_history(site_domain, "brush", False, result.status_code)
284 | return False
285 |
286 | self.__insert_history(site_domain, "brush", True, "成功")
287 | return True
288 |
289 | def __insert_history(self, site_url, type, status, message):
290 | value = {
291 | "site": site_url,
292 | "type": type,
293 | "status": "成功" if status else "失败",
294 | "message": message,
295 | "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
296 | }
297 | self.delete_history(site_url)
298 | self.history(key=site_url, value=value)
299 |
300 | @staticmethod
301 | def get_command():
302 | """
303 | 定义远程控制命令
304 | :return: 命令关键字、事件、描述、附带数据
305 | """
306 | return {
307 | "cmd": "/ptc",
308 | "event": EventType.SiteEdit,
309 | "desc": "同步站点信息",
310 | "category": "站点",
311 | "data": {}
312 | }
313 |
314 | @EventHandler.register(EventType.SiteEdit)
315 | def sync_cookiecloud(self, event=None):
316 | self.__update_site_indexer()
--------------------------------------------------------------------------------
/plugins/torrentmark.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from threading import Event
3 |
4 | import pytz
5 | from apscheduler.schedulers.background import BackgroundScheduler
6 | from apscheduler.triggers.cron import CronTrigger
7 |
8 | from app.downloader import Downloader
9 | from app.message import Message
10 | from app.plugins.modules._base import _IPluginModule
11 | from app.utils.types import DownloaderType
12 | from config import Config
13 |
14 |
15 | class TorrentMark(_IPluginModule):
16 | # 插件名称
17 | module_name = "种子标记"
18 | # 插件描述
19 | module_desc = "标记种子是否是PT。"
20 | # 插件图标
21 | module_icon = "tag.png"
22 | # 主题色
23 | module_color = "#4876b6"
24 | # 插件版本
25 | module_version = "1.0"
26 | # 插件作者
27 | module_author = "hsuyelin"
28 | # 作者主页
29 | author_url = "https://github.com/hsuyelin"
30 | # 插件配置项ID前缀
31 | module_config_prefix = "torrentmark_"
32 | # 加载顺序
33 | module_order = 10
34 | # 可使用的用户级别
35 | user_level = 1
36 |
37 | # 私有属性
38 | _scheduler = None
39 | downloader = None
40 | # 限速开关
41 | _enable = False
42 | _cron = None
43 | _onlyonce = False
44 | _downloaders = []
45 | _nolabels = None
46 | # 退出事件
47 | _event = Event()
48 |
49 | @staticmethod
50 | def get_fields():
51 | downloaders = {k: v for k, v in Downloader().get_downloader_conf_simple().items()
52 | if v.get("type") in ["qbittorrent", "transmission"] and v.get("enabled")}
53 | return [
54 | # 同一板块
55 | {
56 | 'type': 'div',
57 | 'content': [
58 | # 同一行
59 | [
60 | {
61 | 'title': '开启种子标记',
62 | 'required': "",
63 | 'tooltip': '开启后,自动监控下载器,对下载完成的任务根据执行周期标记。',
64 | 'type': 'switch',
65 | 'id': 'enable',
66 | }
67 | ],
68 | [
69 | {
70 | 'title': '执行周期',
71 | 'required': "required",
72 | 'tooltip': '标记任务执行的时间周期,支持5位cron表达式;应避免任务执行过于频繁',
73 | 'type': 'text',
74 | 'content': [
75 | {
76 | 'id': 'cron',
77 | 'placeholder': '0 0 0 ? *',
78 | }
79 | ]
80 | }
81 | ]
82 | ]
83 | },
84 | {
85 | 'type': 'details',
86 | 'summary': '下载器',
87 | 'tooltip': '只有选中的下载器才会执行标记',
88 | 'content': [
89 | # 同一行
90 | [
91 | {
92 | 'id': 'downloaders',
93 | 'type': 'form-selectgroup',
94 | 'content': downloaders
95 | },
96 | ]
97 | ]
98 | },
99 | {
100 | 'type': 'div',
101 | 'content': [
102 | # 同一行
103 | [
104 | {
105 | 'title': '立即运行一次',
106 | 'required': "",
107 | 'tooltip': '打开后立即运行一次(点击此对话框的确定按钮后即会运行,周期未设置也会运行),关闭后将仅按照刮削周期运行(同时上次触发运行的任务如果在运行中也会停止)',
108 | 'type': 'switch',
109 | 'id': 'onlyonce',
110 | }
111 | ]
112 | ]
113 | }
114 | ]
115 |
116 | def init_config(self, config=None):
117 | self.downloader = Downloader()
118 | self.message = Message()
119 | # 读取配置
120 | if config:
121 | self._enable = config.get("enable")
122 | self._onlyonce = config.get("onlyonce")
123 | self._cron = config.get("cron")
124 | self._downloaders = config.get("downloaders")
125 | # 停止现有任务
126 | self.stop_service()
127 |
128 | # 启动定时任务 & 立即运行一次
129 | if self.get_state() or self._onlyonce:
130 | self._scheduler = BackgroundScheduler(timezone=Config().get_timezone())
131 | if self._cron:
132 | self.info(f"标记服务启动,周期:{self._cron}")
133 | self._scheduler.add_job(self.auto_mark,
134 | CronTrigger.from_crontab(self._cron))
135 | if self._onlyonce:
136 | self.info(f"标记服务启动,立即运行一次")
137 | self._scheduler.add_job(self.auto_mark, 'date',
138 | run_date=datetime.now(tz=pytz.timezone(Config().get_timezone())))
139 | # 关闭一次性开关
140 | self._onlyonce = False
141 | self.update_config({
142 | "enable": self._enable,
143 | "onlyonce": self._onlyonce,
144 | "cron": self._cron,
145 | "downloaders": self._downloaders
146 | })
147 | if self._cron or self._onlyonce:
148 | # 启动服务
149 | self._scheduler.print_jobs()
150 | self._scheduler.start()
151 |
152 | def get_state(self):
153 | return True if self._enable and self._cron and self._downloaders else False
154 |
155 | def auto_mark(self):
156 | """
157 | 开始标记
158 | """
159 | if not self._enable or not self._downloaders:
160 | self.warn("标记服务未启用或未配置")
161 | return
162 | # 扫描下载器辅种
163 | for downloader in self._downloaders:
164 | self.info(f"开始扫描下载器:{downloader} ...")
165 | # 下载器类型
166 | downloader_type = self.downloader.get_downloader_type(downloader_id=downloader)
167 | # 获取下载器中已完成的种子
168 | torrents = self.downloader.get_completed_torrents(downloader_id=downloader)
169 | if torrents:
170 | self.info(f"下载器 {downloader} 已完成种子数:{len(torrents)}")
171 | else:
172 | self.info(f"下载器 {downloader} 没有已完成种子")
173 | continue
174 | for torrent in torrents:
175 | if self._event.is_set():
176 | self.info(f"标记服务停止")
177 | return
178 | # 获取种子hash
179 | hash_str = self.__get_hash(torrent, downloader_type)
180 | # 获取种子标签
181 | torrent_tags = set(self.__get_tag(torrent, downloader_type))
182 | pt_flag = self.__isPt(torrent, downloader_type)
183 | torrent_tags.discard("")
184 | if pt_flag is True:
185 | torrent_tags.discard("BT")
186 | torrent_tags.add("PT")
187 | self.downloader.set_torrents_tag(downloader_id=downloader, ids=hash_str, tags=list(torrent_tags))
188 | else:
189 | torrent_tags.add("BT")
190 | torrent_tags.discard("PT")
191 | self.downloader.set_torrents_tag(downloader_id=downloader, ids=hash_str, tags=list(torrent_tags))
192 | self.info("标记任务执行完成")
193 |
194 | @staticmethod
195 | def __get_hash(torrent, dl_type):
196 | """
197 | 获取种子hash
198 | """
199 | try:
200 | return torrent.get("hash") if dl_type == DownloaderType.QB else torrent.hashString
201 | except Exception as e:
202 | print(str(e))
203 | return ""
204 |
205 | @staticmethod
206 | def __get_tag(torrent, dl_type):
207 | """
208 | 获取种子标签
209 | """
210 | try:
211 | return list(map(lambda s: s.strip(), (torrent.get("tags") or "").split(","))) if dl_type == DownloaderType.QB else torrent.labels or []
212 | except Exception as e:
213 | print(str(e))
214 | return []
215 |
216 | @staticmethod
217 | def __isPt(torrent, dl_type):
218 | """
219 | 获取种子标签
220 | """
221 | try:
222 | tracker_list = list()
223 | if dl_type == DownloaderType.QB and torrent.trackers_count == 1:
224 | for tracker in torrent.trackers.data:
225 | if tracker['url'].find('http') != -1:
226 | tracker_list.append(tracker['url'])
227 | elif dl_type == DownloaderType.TR:
228 | tracker_list = list(map(lambda s: s['announce'], torrent.trackers or []))
229 | if len(tracker_list) == 1:
230 | if tracker_list[0].find("secure=") != -1 \
231 | or tracker_list[0].find("passkey=") != -1 \
232 | or tracker_list[0].find("totheglory") != -1:
233 | return True
234 | else:
235 | return False
236 | except Exception as e:
237 | print(str(e))
238 | return False
239 |
240 | def stop_service(self):
241 | """
242 | 退出插件
243 | """
244 | try:
245 | if self._scheduler:
246 | self._scheduler.remove_all_jobs()
247 | if self._scheduler.running:
248 | self._event.set()
249 | self._scheduler.shutdown()
250 | self._event.clear()
251 | self._scheduler = None
252 | except Exception as e:
253 | print(str(e))
254 |
--------------------------------------------------------------------------------
/sites/api.m-team.cc.json:
--------------------------------------------------------------------------------
1 | {"id":"api-mteam-xpcc","name":"馒头(API)","domain":"https://api.m-team.cc/","encoding":"UTF-8","parser":"MTSpider","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"}},"category":{"movie":[{"id":401,"cat":"Movies/SD","desc":"Movie(電影)/SD","default":true},{"id":419,"cat":"Movies/HD","desc":"Movie(電影)/HD","default":true},{"id":420,"cat":"Movies/DVD","desc":"Movie(電影)/DVDiSo","default":true},{"id":421,"cat":"Movies/BluRay","desc":"Movie(電影)/Blu-Ray","default":true},{"id":439,"cat":"Movies/Other","desc":"Movie(電影)/Remux","default":true}],"tv":[{"id":403,"cat":"TV/SD","desc":"TV Series(影劇/綜藝)/SD","default":true},{"id":402,"cat":"TV/HD","desc":"TV Series(影劇/綜藝)/HD","default":true},{"id":435,"cat":"TV/SD","desc":"TV Series(影劇/綜藝)/DVDiSo","default":true},{"id":438,"cat":"TV/HD","desc":"TV Series(影劇/綜藝)/BD","default":true},{"id":404,"cat":"TV/Documentary","desc":"紀錄教育","default":true},{"id":405,"cat":"TV/Anime","desc":"Anime(動畫)","default":true}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"] > b"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"poster":{"selector":"img[alt=\"torrent thumbnail\"]","attribute":"src","filters":[{"name":"replace","args":["pic/nopic.jpg",""]}]},"imdbid":{"selector":"a[href*=\"imdb.com/title/tt\"]","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"size":{"selector":"td.rowfollow:nth-last-child(6)"},"grabs":{"selector":"td.rowfollow:nth-last-child(3)"},"seeders":{"selector":"td.rowfollow:nth-last-child(5)"},"leechers":{"selector":"td.rowfollow:nth-last-child(4)"},"date_added":{"selector":"td.rowfollow:nth-last-child(7) > span","attribute":"title","optional":true},"date_elapsed":{"selector":"tr > td > span","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"span[style=\"font-weight:normal\"]","filters":[{"name":"re_search","args":["(?:限時:\\s*)((?:\\d+日)?(?:\\d+時)?(?:\\d+分)?)",1]},{"name":"date_elapsed_parse"}]},"description":{"selector":"table.torrentname > tr > td.embedded","contents":-1},"labels":{"selector":"table.torrentname > tr > td.embedded > img[class*=\"label_\"]","attribute":"alt"}}}}
--------------------------------------------------------------------------------
/sites/api.m-team.io.json:
--------------------------------------------------------------------------------
1 | {"id":"api-mteam-xpio","name":"馒头(API)","domain":"https://api.m-team.io/","encoding":"UTF-8","parser":"MTSpider","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"}},"category":{"movie":[{"id":401,"cat":"Movies/SD","desc":"Movie(電影)/SD","default":true},{"id":419,"cat":"Movies/HD","desc":"Movie(電影)/HD","default":true},{"id":420,"cat":"Movies/DVD","desc":"Movie(電影)/DVDiSo","default":true},{"id":421,"cat":"Movies/BluRay","desc":"Movie(電影)/Blu-Ray","default":true},{"id":439,"cat":"Movies/Other","desc":"Movie(電影)/Remux","default":true}],"tv":[{"id":403,"cat":"TV/SD","desc":"TV Series(影劇/綜藝)/SD","default":true},{"id":402,"cat":"TV/HD","desc":"TV Series(影劇/綜藝)/HD","default":true},{"id":435,"cat":"TV/SD","desc":"TV Series(影劇/綜藝)/DVDiSo","default":true},{"id":438,"cat":"TV/HD","desc":"TV Series(影劇/綜藝)/BD","default":true},{"id":404,"cat":"TV/Documentary","desc":"紀錄教育","default":true},{"id":405,"cat":"TV/Anime","desc":"Anime(動畫)","default":true}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"] > b"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"poster":{"selector":"img[alt=\"torrent thumbnail\"]","attribute":"src","filters":[{"name":"replace","args":["pic/nopic.jpg",""]}]},"imdbid":{"selector":"a[href*=\"imdb.com/title/tt\"]","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"size":{"selector":"td.rowfollow:nth-last-child(6)"},"grabs":{"selector":"td.rowfollow:nth-last-child(3)"},"seeders":{"selector":"td.rowfollow:nth-last-child(5)"},"leechers":{"selector":"td.rowfollow:nth-last-child(4)"},"date_added":{"selector":"td.rowfollow:nth-last-child(7) > span","attribute":"title","optional":true},"date_elapsed":{"selector":"tr > td > span","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"span[style=\"font-weight:normal\"]","filters":[{"name":"re_search","args":["(?:限時:\\s*)((?:\\d+日)?(?:\\d+時)?(?:\\d+分)?)",1]},{"name":"date_elapsed_parse"}]},"description":{"selector":"table.torrentname > tr > td.embedded","contents":-1},"labels":{"selector":"table.torrentname > tr > td.embedded > img[class*=\"label_\"]","attribute":"alt"}}}}
--------------------------------------------------------------------------------
/sites/brush/api.m-team.cc.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":[],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/api.m-team.io.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":[],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/discfan.net.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":[],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/fsm.name.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//span[@class='badge bg-primary']"],"2XFREE":[],"HR":[],"PEER_COUNT":[]}
--------------------------------------------------------------------------------
/sites/brush/haidan.video.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//div[@class='torrent']/div/div/div/div/div[@class='torrent_name_wrap']/div/div[@class='sp']/img[@class='pro_free']"],"2XFREE":[],"HR":[],"PEER_COUNT":[]}
--------------------------------------------------------------------------------
/sites/brush/hdarea.club.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/hdfans.org.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/hdfun.me.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/hdvbits.com.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/hhanclub.top.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//div[@class='whitespace-nowrap']/span[@class='promotion-tag promotion-tag-free']"],"2XFREE":["//div[@class='whitespace-nowrap']/span[@class='promotion-tag promotion-tag-2xfree']"],"HR":["//span[contains(text(), 'H&R')]"],"PEER_COUNT":["//div[@id='seeder-count']"]}
--------------------------------------------------------------------------------
/sites/brush/open.cd.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//div[@class='whitespace-nowrap']/span[@class='promotion-tag-free']"],"2XFREE":["//div[@class='whitespace-nowrap']/span[@class='promotion-tag-2xfree']"],"HR":["//span[contains(text(), 'H&R')]"],"PEER_COUNT":["//div[@id='seeder-count']"]}
--------------------------------------------------------------------------------
/sites/brush/pandapt.net.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/ptcafe.club.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='pro_free']"],"2XFREE":["//h1[@id='top']/b/font[@class='pro_free2up']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/ptchdbits.co.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/img[@class='pro_free']"],"2XFREE":[],"HR":["//b[contains(text(),'H&R:')]"],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/ptlsp.com.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":["//h1[@id='top']/img[@class='hitandrun']"],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/rousi.zip.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/shadowflow.org.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":["//h1[@id='top']/img[@class='hitandrun']"],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/skyey2.com.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//div[@class='pi']/b[contains(text(),'FREE')]"],"2XFREE":[],"HR":[],"PEER_COUNT":[]}
--------------------------------------------------------------------------------
/sites/brush/star-space.net.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":["//h1[@id='top']/img[@class='hitandrun']"],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/ubits.club.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":["//h1[@id='top']/b/font[@class='twoupfree']"],"HR":["//h1[@align='center']/img[@title='H&R']"],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/brush/xp.m-team.io.json:
--------------------------------------------------------------------------------
1 | {"FREE":["//h1[@id='top']/b/font[@class='free']"],"2XFREE":[],"HR":[],"PEER_COUNT":["//div[@id='peercount']/b[1]"]}
--------------------------------------------------------------------------------
/sites/discfan.net.json:
--------------------------------------------------------------------------------
1 | {"id":"discfan","name":"\u8776\u7c89","domain":"https://discfan.net/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"\u4e2d\u56fd\u5927\u9646(CHN)"},{"id":404,"cat":"Movies","desc":"\u4e2d\u56fd\u9999\u6e2f\u7279\u522b\u884c\u653f\u533a(HKG)"},{"id":405,"cat":"Movies","desc":"\u4e2d\u56fd\u53f0\u6e7e\u7701(TWN)"},{"id":402,"cat":"Movies","desc":"\u6cf0\u56fd(THA)"},{"id":403,"cat":"Movies","desc":"\u65e5\u672c(JPN)"},{"id":406,"cat":"Movies","desc":"\u97e9\u56fd(KOR)"},{"id":410,"cat":"Movies","desc":"\u4e16\u754c(World)"}],"tv":[{"id":411,"cat":"TV","desc":"\u5267\u96c6(Series)"},{"id":413,"cat":"TV/Documentary","desc":"\u8bb0\u5f55(Documentary)"},{"id":416,"cat":"TV","desc":"\u7efc\u827a(Variety Show)"},{"id":419,"cat":"TV/Anime","desc":"\u52a8\u6f2b(Animation)"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"replace","args":["?",""]},{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"imdbid":{"selector":"div.imdb_100 > a","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"date_elapsed":{"selector":"td:nth-child(4)","optional":true},"date_added":{"selector":"td:nth-child(4)","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"td:nth-child(5)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"grabs":{"selector":"td:nth-child(8)"},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"img.pro_free,img.pro_free2up","attribute":"onmouseover","filters":[{"name":"re_search","args":["\\d+-\\d+-\\d+ \\d+:\\d+:\\d+",0]},{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"td:nth-child(2) > table > tr > td:nth-child(2)","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table > tr > td:nth-child(2) > span","remove":"span,a,img,font,b"}}},"system_type":""}
--------------------------------------------------------------------------------
/sites/fsm.name.json:
--------------------------------------------------------------------------------
1 | {"id":"fsm","name":"\u98de\u5929\u62c9\u9762","builtin":true,"domain":"https://fsm.name/","search":{"paths":[{"path":"Torrents","method":"get"}],"params":{"keyword":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"batch":{"delimiter":" ","space_replace":"_"},"parser":null,"render":null,"browse":{},"torrents":{"list":{"selector":"div.table-responsive > table.table-bordered > tbody > tr"},"fields":{"id":{"selector":"a[href*=\"details?tid=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"td:nth-child(3) > div> a"},"title_optional":{"optional":true,"selector":"td:nth-child(3) > div> a","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'][0:80] }}{% else %}{{ fields['title_default'][0:80] }}{% endif %}"},"details":{"selector":"a[href*=\"details?tid=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download\"]","attribute":"href"},"size":{"selector":"td:nth-child(6)"},"grabs":{"selector":"td:nth-child(9)"},"seeders":{"selector":"td:nth-child(7)"},"leechers":{"selector":"td:nth-child(8)"},"date_added":{"selector":"td:nth-child(5)"},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"td[class=\"embedded\"] > b > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"div> a","index":-1},"labels":{"selector":"div > div.tags > a"},"minimumratio":{"text":1},"minimumseedtime":{"text":90000}}},"category":{},"siteid":null,"cookie":null,"ua":null,"rule":null,"public":false,"proxy":false,"language":null,"pri":0}
--------------------------------------------------------------------------------
/sites/haidan.video.json:
--------------------------------------------------------------------------------
1 | {"id":"haidan","name":"\u6d77\u80c6\u4e4b\u5bb6","domain":"https://www.haidan.video/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"batch":{"delimiter":" ","space_replace":"_"},"browse":{},"torrents":{"list":{"selector":"div.torrent_panel_inner > div.torrent_group"},"fields":{"id":{"selector":"a[href*=\"details.php?group_id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?group_id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?group_id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'][0:80] }}{% else %}{{ fields['title_default'][0:80] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"replace","args":["?",""]},{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?group_id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"imdbid":{"selector":"div.imdb_100 > a","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"date_elapsed":{"selector":"div.torrent_group > div.group_content > div.group_detail_wrap > div.group_detail > div.torrent_detail > div:nth-child(1) > div.torrent_item > div.time_col > span[title]","optional":true},"date_added":{"selector":"div.torrent_group > div.group_content > div.group_detail_wrap > div.group_detail > div.torrent_detail > div:nth-child(1) > div.torrent_item > div.time_col > span[title]","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"div.torrent_group > div.group_content > div.group_detail_wrap > div.group_detail > div.torrent_detail > div:nth-child(1) > div.torrent_item > div.video_size"},"seeders":{"selector":"div.torrent_group > div.group_content > div.group > div.seeder_col"},"leechers":{"selector":"div.torrent_group > div.group_content > div.group > div.leecher_col"},"grabs":{"selector":"div.torrent_group > div.group_content > div.group > div.snatched_col"},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"img.pro_free,img.pro_free2up","attribute":"onmouseover","filters":[{"name":"re_search","args":["\\d+-\\d+-\\d+ \\d+:\\d+:\\d+",0]},{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"div.torrent_group > div.group_content > div.group > div.table_cell > div.name > div.video_name > a","remove":"a,b,img,span","contents":-1},"labels":{"selector":"div.torrent_group > div.group_content > div.group > div.table_cell > div.name > div.video_name_extra > div"},"minimumratio":{"text":1},"minimumseedtime":{"text":90000}}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"\u7535\u5f71"}],"tv":[{"id":402,"cat":"TV/Series","desc":"\u7535\u89c6\u5267"},{"id":403,"cat":"TV/Shows","desc":"\u7efc\u827a"},{"id":404,"cat":"TV/Documentaries","desc":"\u7eaa\u5f55\u7247"},{"id":405,"cat":"TV/Animations","desc":"\u52a8\u6f2b"}]}}
--------------------------------------------------------------------------------
/sites/hdarea.club.json:
--------------------------------------------------------------------------------
1 | {"id":"gaoqingshijie","name":"\u9ad8\u6e05\u89c6\u754c","domain":"https://hdarea.club/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":300,"cat":"Movies/UHD","desc":"Movies UHD-4K"},{"id":401,"cat":"Movies/BluRay","desc":"Movies Blu-ray"},{"id":415,"cat":"Movies/HD","desc":"Movies REMUX"},{"id":416,"cat":"Movies/3D","desc":"Movies 3D"},{"id":410,"cat":"Movies/HD","desc":"Movies 1080p"},{"id":411,"cat":"Movies/HD","desc":"Movies 720p"},{"id":414,"cat":"Movies/DVD","desc":"Movies DVD"},{"id":412,"cat":"Movies/WEB-DL","desc":"Movies WEB-DL"},{"id":413,"cat":"Movies/HD","desc":"Movies HDTV"},{"id":417,"cat":"Movies/Other","desc":"Movies iPad"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries"},{"id":405,"cat":"TV/Anime","desc":"Animations"},{"id":402,"cat":"TV","desc":"TV Series"},{"id":403,"cat":"TV","desc":"TV Shows"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"replace","args":["?",""]},{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"imdbid":{"selector":"div.imdb_100 > a","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"td:nth-child(5)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"grabs":{"selector":"td:nth-child(8)"},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"img.pro_free,img.pro_free2up","attribute":"onmouseover","filters":[{"name":"re_search","args":["\\d+-\\d+-\\d+ \\d+:\\d+:\\d+",0]},{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"td:nth-child(2) > table > tr > td.embedded","contents":-1}}}}
--------------------------------------------------------------------------------
/sites/hdfans.org.json:
--------------------------------------------------------------------------------
1 | {"id":"hdfans","name":"\u7ea2\u8c46\u996d","domain":"https://hdfans.org/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/\u7535\u5f71"}],"tv":[{"id":402,"cat":"TV","desc":"TV Series/\u7535\u89c6\u5267"},{"id":403,"cat":"TV/Documentary","desc":"Documentaries/\u7eaa\u5f55\u7247"},{"id":416,"cat":"TV","desc":"TV Shows/\u7efc\u827a"},{"id":417,"cat":"TV/Anime","desc":"Animations/\u52a8\u6f2b"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td:nth-child(5)"},"grabs":{"selector":"td:nth-child(8)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"td[class=\"embedded\"] > font > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"td:nth-child(2) > table > tr > td","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table > tr > td > span","remove":"span,a,img,font,b"}}},"system_type":""}
--------------------------------------------------------------------------------
/sites/hdfun.me.json:
--------------------------------------------------------------------------------
1 | {"id":"gaoqingkongjian","name":"\u9ad8\u6e05\u7a7a\u95f4","domain":"http://hdfun.me/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":411,"cat":"Movies/SD","desc":"Movies SD"},{"id":412,"cat":"Movies","desc":"Movies IPad"},{"id":413,"cat":"Movies/HD","desc":"Movies 720p"},{"id":414,"cat":"Movies/HD","desc":"Movies 1080p"},{"id":415,"cat":"Movies","desc":"Movies REMUX"},{"id":450,"cat":"Movies/BluRay","desc":"Movies Bluray"},{"id":499,"cat":"Movies/UHD","desc":"Movies UHD Blu-ray"},{"id":416,"cat":"Movies/UHD","desc":"Movies 2160p"}],"tv":[{"id":417,"cat":"TV/Documentary","desc":"Doc SD"},{"id":418,"cat":"TV/Documentary","desc":"Doc IPad"},{"id":419,"cat":"TV/Documentary","desc":"Doc 720p"},{"id":420,"cat":"TV/Documentary","desc":"Doc 1080p"},{"id":421,"cat":"TV/Documentary","desc":"Doc REMUX"},{"id":451,"cat":"TV/Documentary","desc":"Doc Bluray"},{"id":500,"cat":"TV/Documentary","desc":"Doc UHD Blu-ray"},{"id":422,"cat":"TV/Documentary","desc":"Doc 2160p"},{"id":425,"cat":"TV/SD","desc":"TVShow SD"},{"id":426,"cat":"TV","desc":"TVShow IPad"},{"id":471,"cat":"TV","desc":"TVShow IPad"},{"id":427,"cat":"TV/HD","desc":"TVShow 720p"},{"id":472,"cat":"TV/HD","desc":"TVShow 720p"},{"id":428,"cat":"TV/HD","desc":"TVShow 1080i"},{"id":429,"cat":"TV/HD","desc":"TVShow 1080p"},{"id":430,"cat":"TV","desc":"TVShow REMUX"},{"id":452,"cat":"TV/HD","desc":"TVShow Bluray"},{"id":431,"cat":"TV/UHD","desc":"TVShow 2160p"},{"id":432,"cat":"TV/SD","desc":"TVSeries SD"},{"id":433,"cat":"TV","desc":"TVSeries IPad"},{"id":434,"cat":"TV/HD","desc":"TVSeries 720p"},{"id":435,"cat":"TV/HD","desc":"TVSeries 1080i"},{"id":436,"cat":"TV/HD","desc":"TVSeries 1080p"},{"id":437,"cat":"TV","desc":"TVSeries REMUX"},{"id":453,"cat":"TV/HD","desc":"TVSeries Bluray"},{"id":438,"cat":"TV/UHD","desc":"TVSeries 2160p"},{"id":444,"cat":"TV/Anime","desc":"Anime SD"},{"id":445,"cat":"TV/Anime","desc":"Anime IPad"},{"id":446,"cat":"TV/Anime","desc":"Anime 720p"},{"id":447,"cat":"TV/Anime","desc":"Anime 1080p"},{"id":448,"cat":"TV/Anime","desc":"Anime REMUX"},{"id":454,"cat":"TV/Anime","desc":"Anime Bluray"},{"id":449,"cat":"TV/Anime","desc":"Anime 2160p"},{"id":501,"cat":"TV/Anime","desc":"Anime UHD Blu-ray"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"replace","args":["?",""]},{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"imdbid":{"selector":"div.imdb_100 > a","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"td:nth-child(5)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"grabs":{"selector":"td:nth-child(8)"},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"img.pro_free,img.pro_free2up","attribute":"onmouseover","filters":[{"name":"re_search","args":["\\d+-\\d+-\\d+ \\d+:\\d+:\\d+",0]},{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"td:nth-child(2) > table > tr > td.embedded","contents":-1},"labels":{"selector":"td:nth-child(2) > table > tr > td.embedded > span"}}}}
--------------------------------------------------------------------------------
/sites/hdvbits.com.json:
--------------------------------------------------------------------------------
1 | {"id":"hdvbits","name":"hdvbits","builtin":true,"domain":"https://hdvbits.com/","search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"batch":{"delimiter":" ","space_replace":"_"},"parser":null,"render":null,"browse":{},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td:nth-child(5)"},"grabs":{"selector":"td:nth-child(8)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"td[class=\"embedded\"] > font > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"table.torrentname > tr > td:nth-child(2)","remove":"a,img,span","contents":-1},"labels":{"selector":"table.torrentname > tr > td:nth-child(2) > span"}}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/电影"}],"tv":[{"id":402,"cat":"TV","desc":"TV Series/电视剧"},{"id":403,"cat":"TV/Documentary","desc":"Documentaries/纪录片"},{"id":416,"cat":"TV","desc":"TV Shows/综艺"},{"id":417,"cat":"TV/Anime","desc":"Animations/动漫"}]},"siteid":null,"cookie":null,"ua":null,"rule":null,"public":false,"proxy":false,"language":null,"pri":0}
--------------------------------------------------------------------------------
/sites/hhanclub.top.json:
--------------------------------------------------------------------------------
1 | {"id":"hanhan","name":"\u61a8\u61a8","encoding":"UTF-8","public":false,"proxy":false,"domain":"https://hhanclub.top/","search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}","search_area":1},"batch":{"delimiter":" ","space_replace":"_"}},"batch":{"delimiter":" ","space_replace":"_"},"browse":{},"torrents":{"list":{"selector":"div.torrent-table-sub-info"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat[]=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"div.torrent-info-text-size"},"seeders":{"selector":"div.torrent-info-text-seeders > a[href*=\"#seeders\"]"},"leechers":{"selector":"div.torrent-info-text-leechers > a[href*=\"#leechers\"]"},"grabs":{"selector":"div.torrent-info-text-finished"},"date_elapsed":{"selector":"div.torrent-info-text-added > span","optional":true},"date_added":{"selector":"div.torrent-info-text-added > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"span.promotion-tag-free":0,"span.promotion-tag-free2up":0,"span.promotion-tag-50pctdown":0.5,"span.promotion-tag-50pctdown2up":0.5,"span.promotion-tag-30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"span.promotion-tag-50pctdown2up":2,"span.promotion-tag-free2up":2,"span.promotion-tag-2up":2,"*":1}},"description":{"selector":"div.torrent-info-text-small_name"},"labels":{"selector":"a[href*=\"?tag_id\"] > span.tag"}}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/\u7535\u5f71"},{"id":405,"cat":"Anime","desc":"Animations/\u52a8\u6f2b"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries/\u7eaa\u5f55\u7247"},{"id":405,"cat":"Anime","desc":"Animations/\u52a8\u6f2b"},{"id":402,"cat":"TV","desc":"TV Series/\u8fde\u7eed\u5267"},{"id":403,"cat":"TV","desc":"TV Shows/\u7efc\u827a"}]}}
--------------------------------------------------------------------------------
/sites/open.cd.json:
--------------------------------------------------------------------------------
1 | {"id":"open","name":"\u7687\u540e","builtin":true,"domain":"https://open.cd/","search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"batch":{"delimiter":" ","space_replace":"_"},"parser":null,"render":null,"browse":{},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td:nth-child(7)"},"grabs":{"selector":"td:nth-child(10)"},"seeders":{"selector":"td:nth-child(8)"},"leechers":{"selector":"td:nth-child(9)"},"date_elapsed":{"selector":"td:nth-child(6) > span","optional":true},"date_added":{"selector":"td:nth-child(6) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"td[class=\"embedded\"] > b > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"description":{"selector":"table.torrentname > tr > td.embedded > font","index":-1},"labels":{"selector":"table.torrentname > tr > td.embedded > i > a"}}},"category":{},"siteid":null,"cookie":null,"ua":null,"rule":null,"public":false,"proxy":false,"language":null,"pri":0}
--------------------------------------------------------------------------------
/sites/pandapt.net.json:
--------------------------------------------------------------------------------
1 | {"id":"pandapt","name":"\u718a\u732b","domain":"https://pandapt.net/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"replace","args":["?",""]},{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"imdbid":{"selector":"div.imdb_100 > a","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"td:nth-child(5)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"grabs":{"selector":"td:nth-child(8)"},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"img.pro_free,img.pro_free2up","attribute":"onmouseover","filters":[{"name":"re_search","args":["\\d+-\\d+-\\d+ \\d+:\\d+:\\d+",0]},{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"tags":{"selector":"div > a.torrents-tag"},"subject":{"selector":"td.embedded:nth-child(2) > div > div:nth-child(2) > span","contents":-1},"description":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(2)","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(2) > span[title=\"\"]"}}},"system_type":""}
--------------------------------------------------------------------------------
/sites/ptcafe.club.json:
--------------------------------------------------------------------------------
1 | {"id":"coffee","name":"\u5496\u5561","domain":"https://ptcafe.club","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/\u7535\u5f71"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries/\u7eaa\u5f55\u7247"},{"id":405,"cat":"TV/Anime","desc":"Animations/\u52a8\u6f2b"},{"id":402,"cat":"TV","desc":"TV Series/\u8fde\u7eed\u5267"},{"id":403,"cat":"TV","desc":"TV Shows/\u7efc\u827a"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"poster":{"selector":"img[data-orig]","attribute":"data-orig"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td.rowfollow:nth-child(5)"},"grabs":{"selector":"td.rowfollow:nth-child(8)"},"seeders":{"selector":"td.rowfollow:nth-child(6)"},"leechers":{"selector":"td.rowfollow:nth-child(7)"},"date_elapsed":{"selector":"td.rowfollow:nth-child(4) > span","optional":true},"date_added":{"selector":"td.rowfollow:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"div > b > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"tags":{"selector":"div > a.torrents-tag"},"subject":{"selector":"td.embedded:nth-child(2) > div > div:nth-child(2) > span","contents":-1},"description":{"selector":"td.embedded","remove":"span,a,img,font,b"},"labels":{"selector":"td.embedded > span[style]"}}}}
--------------------------------------------------------------------------------
/sites/ptchdbits.co.json:
--------------------------------------------------------------------------------
1 | {"id":"caihongdao","name":"\u5f69\u8679\u5c9b","domain":"https://ptchdbits.co/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries"},{"id":405,"cat":"TV/Anime","desc":"Animations"},{"id":402,"cat":"TV","desc":"TV Series"},{"id":403,"cat":"TV","desc":"TV Shows"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"table.torrentname > tr > td.embedded > a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"table.torrentname > tr > td.embedded > a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td:nth-child(5)"},"grabs":{"selector":"td:nth-child(8)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"td[class] > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"hr_days":{"defualt_value":0,"selector":"div.circle > div.circle-text","filters":[{"name":"re_search","args":["\\d",0]}]},"minimumratio":{"text":"{% if fields[ 'hr_days' ] %}999999{% else %}0{% endif %}"},"minimumseedtime":{"text":"{% if fields[ 'hr_days' ] %}{{ (fields[ 'hr_days' ]|int)*86400 }}{% else %}0{% endif %}"},"description":{"selector":"font.subtitle","remove":"div","contents":-1},"labels":{"selector":"font.subtitle > div[style] > div.tag"}}}}
--------------------------------------------------------------------------------
/sites/ptlsp.com.json:
--------------------------------------------------------------------------------
1 | {"id":"PTLSP","name":"PTLSP","domain":"https://www.ptlsp.com/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/\u7535\u5f71"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries/\u7eaa\u5f55\u7247"},{"id":405,"cat":"TV/Anime","desc":"Animations/\u52a8\u6f2b"},{"id":402,"cat":"TV","desc":"TV Series/\u8fde\u7eed\u5267"},{"id":403,"cat":"TV","desc":"TV Shows/\u7efc\u827a"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"poster":{"selector":"img[data-orig]","attribute":"data-orig"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td.rowfollow:nth-child(5)"},"grabs":{"selector":"td.rowfollow:nth-child(8)"},"seeders":{"selector":"td.rowfollow:nth-child(6)"},"leechers":{"selector":"td.rowfollow:nth-child(7)"},"date_elapsed":{"selector":"td.rowfollow:nth-child(4) > span","optional":true},"date_added":{"selector":"td.rowfollow:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"div > b > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"tags":{"selector":"div > a.torrents-tag"},"subject":{"selector":"td.embedded:nth-child(2) > div > div:nth-child(2) > span","contents":-1},"description":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(2)","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(2) > span[title=\"\"]"}}},"system_type":"nexus_php"}
--------------------------------------------------------------------------------
/sites/rousi.zip.json:
--------------------------------------------------------------------------------
1 | {"id":"rousi","name":"\u8089\u4e1d","domain":"https://rousi.zip/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"replace","args":["?",""]},{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"imdbid":{"selector":"div.imdb_100 > a","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"date_elapsed":{"selector":"td:nth-child(4) > span","optional":true},"date_added":{"selector":"td:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"td:nth-child(5)"},"seeders":{"selector":"td:nth-child(6)"},"leechers":{"selector":"td:nth-child(7)"},"grabs":{"selector":"td:nth-child(8)"},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"img.pro_free,img.pro_free2up","attribute":"onmouseover","filters":[{"name":"re_search","args":["\\d+-\\d+-\\d+ \\d+:\\d+:\\d+",0]},{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"tags":{"selector":"div > a.torrents-tag"},"subject":{"selector":"td.embedded:nth-child(2) > div > div:nth-child(2) > span","contents":-1},"description":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(2)","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(2) > span[title=\"\"]"}}},"system_type":""}
--------------------------------------------------------------------------------
/sites/shadowflow.org.json:
--------------------------------------------------------------------------------
1 | {"id":"ying","name":"\u5f71","domain":"https://shadowflow.org/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/\u7535\u5f71"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries/\u7eaa\u5f55\u7247"},{"id":405,"cat":"TV/Anime","desc":"Animations/\u52a8\u6f2b"},{"id":402,"cat":"TV","desc":"TV Series/\u8fde\u7eed\u5267"},{"id":403,"cat":"TV","desc":"TV Shows/\u7efc\u827a"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"poster":{"selector":"img[data-orig]","attribute":"data-orig"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td.rowfollow:nth-child(5)"},"grabs":{"selector":"td.rowfollow:nth-child(8)"},"seeders":{"selector":"td.rowfollow:nth-child(6)"},"leechers":{"selector":"td.rowfollow:nth-child(7)"},"date_elapsed":{"selector":"td.rowfollow:nth-child(4) > span","optional":true},"date_added":{"selector":"td.rowfollow:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"div > b > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"tags":{"selector":"div > a.torrents-tag"},"subject":{"selector":"td.embedded:nth-child(2) > div > div:nth-child(2) > span","contents":-1},"description":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(1)","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(1) > span[title=\"\"]"}}}}
--------------------------------------------------------------------------------
/sites/skyey2.com.json:
--------------------------------------------------------------------------------
1 | {"id":"skyey2","name":"\u5929\u96ea(skyey2)","domain":"https://www.skyey2.com/","encoding":"UTF-8","public":false,"proxy":false,"search":{"paths":[{"path":"forum.php?mod=torrents&cat=1&search={keyword}","method":"get"}]},"browse":{"path":"forum.php?mod=torrents&page={page}"},"torrents":{"list":{"selector":"table.torrents > tbody > tr:has(\"td.rowfollow\")"},"fields":{"id":{"selector":"a[href*=\"/download.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"table.torrentname > tbody > tr > td > a[title]","attribute":"title"},"title":{"text":"{{ fields['title_default'][0:80] }}"},"details":{"selector":"table.torrentname > tbody > tr > td > a[title]","attribute":"href"},"download":{"selector":"a[href*=\"/download.php?id=\"]","attribute":"href"},"date_added":{"selector":"td:nth-child(3)"},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"size":{"selector":"td:nth-child(4)"},"seeders":{"selector":"td:nth-child(7)"},"leechers":{"selector":"td:nth-child(6)"},"grabs":{"selector":"td:nth-child(5)"},"downloadvolumefactor":{"case":{"img.sp_4":0,"*":1}},"uploadvolumefactor":{"case":{"*":1}}}}}
--------------------------------------------------------------------------------
/sites/star-space.net.json:
--------------------------------------------------------------------------------
1 | {"id":"startspace","name":"\u661f\u7a7a","domain":"https://star-space.net/","encoding":"UTF-8","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"},"batch":{"delimiter":" ","space_replace":"_"}},"category":{"movie":[{"id":401,"cat":"Movies","desc":"Movies/\u7535\u5f71"}],"tv":[{"id":404,"cat":"TV/Documentary","desc":"Documentaries/\u7eaa\u5f55\u7247"},{"id":405,"cat":"TV/Anime","desc":"Animations/\u52a8\u6f2b"},{"id":402,"cat":"TV","desc":"TV Series/\u8fde\u7eed\u5267"},{"id":403,"cat":"TV","desc":"TV Shows/\u7efc\u827a"}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"title_default":{"selector":"a[href*=\"details.php?id=\"]"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"poster":{"selector":"img[data-orig]","attribute":"data-orig"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"size":{"selector":"td.rowfollow:nth-child(5)"},"grabs":{"selector":"td.rowfollow:nth-child(8)"},"seeders":{"selector":"td.rowfollow:nth-child(6)"},"leechers":{"selector":"td.rowfollow:nth-child(7)"},"date_elapsed":{"selector":"td.rowfollow:nth-child(4) > span","optional":true},"date_added":{"selector":"td.rowfollow:nth-child(4) > span","attribute":"title","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"div > b > span[title]","attribute":"title","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"tags":{"selector":"div > a.torrents-tag"},"subject":{"selector":"td.embedded:nth-child(2) > div > div:nth-child(2) > span","contents":-1},"description":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(1)","remove":"span,a,img,font,b","contents":-1},"labels":{"selector":"td:nth-child(2) > table.torrentname > tr > td:nth-child(1) > span[title=\"\"]"}}}}
--------------------------------------------------------------------------------
/sites/xp.m-team.io.json:
--------------------------------------------------------------------------------
1 | {"id":"mteam-xpio","name":"馒头(XPIO)","domain":"https://xp.m-team.io/","encoding":"UTF-8","parser":"MTSpider","public":false,"search":{"paths":[{"path":"torrents.php","method":"get"}],"params":{"search":"{keyword}"}},"category":{"movie":[{"id":401,"cat":"Movies/SD","desc":"Movie(電影)/SD","default":true},{"id":419,"cat":"Movies/HD","desc":"Movie(電影)/HD","default":true},{"id":420,"cat":"Movies/DVD","desc":"Movie(電影)/DVDiSo","default":true},{"id":421,"cat":"Movies/BluRay","desc":"Movie(電影)/Blu-Ray","default":true},{"id":439,"cat":"Movies/Other","desc":"Movie(電影)/Remux","default":true}],"tv":[{"id":403,"cat":"TV/SD","desc":"TV Series(影劇/綜藝)/SD","default":true},{"id":402,"cat":"TV/HD","desc":"TV Series(影劇/綜藝)/HD","default":true},{"id":435,"cat":"TV/SD","desc":"TV Series(影劇/綜藝)/DVDiSo","default":true},{"id":438,"cat":"TV/HD","desc":"TV Series(影劇/綜藝)/BD","default":true},{"id":404,"cat":"TV/Documentary","desc":"紀錄教育","default":true},{"id":405,"cat":"TV/Anime","desc":"Anime(動畫)","default":true}]},"torrents":{"list":{"selector":"table.torrents > tr:has(\"table.torrentname\")"},"fields":{"id":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href","filters":[{"name":"re_search","args":["\\d+",0]}]},"title_default":{"selector":"a[href*=\"details.php?id=\"] > b"},"title_optional":{"optional":true,"selector":"a[title][href*=\"details.php?id=\"]","attribute":"title"},"title":{"text":"{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"},"category":{"selector":"a[href*=\"?cat=\"]","attribute":"href","filters":[{"name":"querystring","args":"cat"}]},"details":{"selector":"a[href*=\"details.php?id=\"]","attribute":"href"},"download":{"selector":"a[href*=\"download.php?id=\"]","attribute":"href"},"poster":{"selector":"img[alt=\"torrent thumbnail\"]","attribute":"src","filters":[{"name":"replace","args":["pic/nopic.jpg",""]}]},"imdbid":{"selector":"a[href*=\"imdb.com/title/tt\"]","attribute":"href","filters":[{"name":"re_search","args":["tt\\d+",0]}]},"size":{"selector":"td.rowfollow:nth-last-child(6)"},"grabs":{"selector":"td.rowfollow:nth-last-child(3)"},"seeders":{"selector":"td.rowfollow:nth-last-child(5)"},"leechers":{"selector":"td.rowfollow:nth-last-child(4)"},"date_added":{"selector":"td.rowfollow:nth-last-child(7) > span","attribute":"title","optional":true},"date_elapsed":{"selector":"tr > td > span","optional":true},"date":{"text":"{% if fields['date_elapsed'] or fields['date_added'] %}{{ fields['date_elapsed'] if fields['date_elapsed'] else fields['date_added'] }}{% else %}now{% endif %}","filters":[{"name":"dateparse","args":"%Y-%m-%d %H:%M:%S"}]},"downloadvolumefactor":{"case":{"img.pro_free":0,"img.pro_free2up":0,"img.pro_50pctdown":0.5,"img.pro_50pctdown2up":0.5,"img.pro_30pctdown":0.3,"*":1}},"uploadvolumefactor":{"case":{"img.pro_50pctdown2up":2,"img.pro_free2up":2,"img.pro_2up":2,"*":1}},"free_deadline":{"default_value":"{% if fields['downloadvolumefactor']==0 %}{{max_time}}{% endif%}","default_value_format":"%Y-%m-%d %H:%M:%S.%f","selector":"span[style=\"font-weight:normal\"]","filters":[{"name":"re_search","args":["(?:限時:\\s*)((?:\\d+日)?(?:\\d+時)?(?:\\d+分)?)",1]},{"name":"date_elapsed_parse"}]},"description":{"selector":"table.torrentname > tr > td.embedded","contents":-1},"labels":{"selector":"table.torrentname > tr > td.embedded > img[class*=\"label_\"]","attribute":"alt"}}}}
--------------------------------------------------------------------------------
/sites/规则.md:
--------------------------------------------------------------------------------
1 | # 索引规则
2 |
3 | |序号|字段|描述|
4 | |:--------:|:-------------|:-------------:|
5 | |1|id|域名不包含 http:// 和 .com 的部分}|
6 | |2|domain|完整的域名|
7 | |3|name|站点名称|
8 | |4|builtin|内建索引器`默认:true`|
9 | |5|search|搜索规则|
10 | |6|search.paths|站点种子页面|
11 | |7|search.paths.path|页面地址`默认:torrents.php`|
12 | |8|search.paths.method|请求方式`get/post`|
13 | |9|params.search|搜索入参|
14 | |10|torrents|种子页面搜索规则|
15 | |11|torrents.list.selector|需要用选择器选中到一行|
16 | |12|torrents.fields|搜索结果详情选择器|
17 | |13|torrents.fields.description|副标题|
18 | |14|torrents.fields.labels|标签|
19 | |15|torrents.fields.grabs|完成人数|
20 | |16|torrents.fields.leechers|下载人数|
21 | |17|torrents.fields.seeders|上传人数|
22 | |18|torrents.fields.size|种子大小|
23 | |19|torrents.fields.date_added|发布时间|
24 | |20|torrents.fields.date_elapsed|存活时间(发布了多久)|
25 | |21|torrents.fields.download|下载地址|
26 | |22|torrents.fields.details|详情页面地址|
27 | |23|torrents.fields.title_optional|标题内容|
28 | |24|torrents.fields.title_default|默认标题,若`title_optional`为空则显示该标题|
--------------------------------------------------------------------------------
/source.json:
--------------------------------------------------------------------------------
1 | {
2 | "Customindexer": {
3 | "id": "Customindexer",
4 | "name": "自定义索引器",
5 | "desc": "用于自定义索引器规则,可达到支持更多站点的效果。",
6 | "version": "1.3",
7 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/customindexer.png",
8 | "color": "#00ADEF",
9 | "author": "mattoid",
10 | "author_url": "https://github.com/Mattoids",
11 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/customindexer.py"
12 | },
13 | "Editsignin": {
14 | "id": "Editsignin",
15 | "name": "签到站点修改",
16 | "desc": "用于解决特殊站点站点更换域名后无法签到的问题。",
17 | "version": "1.1",
18 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/editsignin.png",
19 | "color": "bg-black",
20 | "author": "mattoid",
21 | "author_url": "https://github.com/Mattoids",
22 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/editsignin.py"
23 | },
24 | "Jackett": {
25 | "id": "Jackett",
26 | "name": "Jackett",
27 | "desc": "让内荐索引器支持检索Jackett站点资源",
28 | "version": "1.1",
29 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/jackett.png",
30 | "color": "#C90425",
31 | "author": "mattoid",
32 | "author_url": "https://github.com/Mattoids",
33 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/jackett.py"
34 | },
35 | "DoubanSync": {
36 | "id": "DoubanSync",
37 | "name": "DoubanSync",
38 | "desc": "修复豆瓣想看全量同步失败的问题",
39 | "version": "1.1",
40 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/douban.png",
41 | "color": "#05B711",
42 | "author": "jxxghp",
43 | "author_url": "https://github.com/jxxghp",
44 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/doubansync.py"
45 | },
46 | "DailyLinkRunner": {
47 | "id": "DailyLinkRunner",
48 | "name": "每日定时链接访问器",
49 | "desc": "每天在指定时间访问自定义链接",
50 | "version": "1.2",
51 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/link.png",
52 | "color": "#C90425",
53 | "author": "maxgt",
54 | "author_url": "https://github.com/GTian28",
55 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/dailylinkrunner.py"
56 | },
57 | "IYUUAutoSeedEnhance": {
58 | "id": "IYUUAutoSeedEnhance",
59 | "name": "IYUU自动辅种(增强版)",
60 | "desc": "基于原版IYUU插件修改而来",
61 | "version": "1.0",
62 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/iyuu.png",
63 | "color": "#F3B70B",
64 | "author": "mattoid",
65 | "author_url": "https://github.com/Mattoids",
66 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/iyuuautoseedenhance.py"
67 | },
68 | "CustomBrush": {
69 | "id": "CustomBrush",
70 | "name": "自定义刷流规则",
71 | "desc": "用于给自定义索引器添加的站点刷流配置免费资源。",
72 | "version": "1.2",
73 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/brush.png",
74 | "color": "#02C4E0",
75 | "author": "mattoid",
76 | "author_url": "https://github.com/Mattoids",
77 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/custombrush.py"
78 | },
79 | "Prowlarr": {
80 | "id": "Prowlarr",
81 | "name": "Prowlarr",
82 | "desc": "让内荐索引器支持检索Prowlarr站点资源。",
83 | "version": "1.1",
84 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/prowlarr.png",
85 | "color": "#02C4E0",
86 | "author": "mattoid",
87 | "author_url": "https://github.com/Mattoids",
88 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/prowlarr.py"
89 | },
90 | "TorrentMark": {
91 | "id": "TorrentMark",
92 | "name": "种子标记",
93 | "desc": "标记种子是否是PT。",
94 | "version": "1.0",
95 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/tag.png",
96 | "color": "#4876b6",
97 | "author": "hsuyelin",
98 | "author_url": "https://github.com/hsuyelin",
99 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/torrentmark.py"
100 | },
101 | "SyncIndexer": {
102 | "id": "SyncIndexer",
103 | "name": "同步索引规则",
104 | "desc": "可根据配置站点自动获取已适配的索引规则和刷流规则。可以配合 '自定义索引器' 和 '自定义刷流规则' 插件使用。",
105 | "version": "1.9.1",
106 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/syncindexer.png",
107 | "color": "#02C4E0",
108 | "author": "mattoid",
109 | "author_url": "https://github.com/mattoid",
110 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/syncindexer.py"
111 | },
112 | "InvitesSignin": {
113 | "id": "InvitesSignin",
114 | "name": "药丸签到",
115 | "desc": "药丸论坛签到。",
116 | "version": "1.1",
117 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/invites.png",
118 | "color": "#FFFFFF",
119 | "author": "thsrite",
120 | "author_url": "https://github.com/thsrite",
121 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/invitessignin.py"
122 | },
123 | "PersonMeta": {
124 | "id": "PersonMeta",
125 | "name": "演职人员刮削",
126 | "desc": "刮削演职人员图片以及中文名称。",
127 | "version": "1.0",
128 | "icon": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/images/actor.png",
129 | "color": "#E66E72",
130 | "author": "jxxghp",
131 | "author_url": "https://github.com/jxxghp",
132 | "download_url": "https://gitee.com/Mattoid/nas-tools-plugin/raw/master/plugins/personmeta.py"
133 | }
134 | }
--------------------------------------------------------------------------------