├── .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 | ![打开第三方插件商店](https://github.com/Mattoids/nas-tools-plugin/raw/master/images/1.png) 82 | 83 | ### 第二步、进入商店设置页面 84 | ![进入商店设置页面](https://github.com/Mattoids/nas-tools-plugin/raw/master/images/2.png) 85 | 86 | ### 第三步、添加第三方插件源 87 | ![添加第三方插件源](https://github.com/Mattoids/nas-tools-plugin/raw/master/images/3.png) 88 | 89 | ``` 90 | 完成以上操作以后,关闭第三方插件页面,重新打开即可看到插件,安装即可 91 | ``` 92 | 93 | # 插件使用说明 94 | 95 | ### 添加站点规则 96 | ### `这里的【站点域名】需要和站点里的【站点地址】配置一致` 97 | ![站点设置](https://github.com/Mattoids/nas-tools-plugin/raw/master/images/site.png) 98 | ``` 99 | 填写 站点域名 + 原始域名 100 | 站点索引规则 为空 101 | ``` 102 | ![替换旧的站点域名](https://github.com/Mattoids/nas-tools-plugin/raw/master/images/indexer.png) 103 | 104 | ### 打开 json 格式化网址 105 | 106 | ~~~ 107 | 1. 复制上一步输入框的内容 108 | 2. 打开解析站点 109 | 3. 粘贴到网站中点【格式化校验】和【Unicode转中文】,修改name为你添加的站点的中文名(随便写也没关系) 110 | 4. 点击 【压缩】和【中文转Unicode】,然后复制站点里面的内容 111 | 5. 粘贴回上一步的框中(把原有的删除以后再粘贴) 112 | 6. 保存 113 | 7. 你可以去索引器里面找你的站点了 114 | ~~~ 115 | ![bejson](https://github.com/Mattoids/nas-tools-plugin/raw/master/images/bejson.png) 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 | 191 | 192 | 193 | 194 | {% endfor %} 195 | {% endif %} 196 | 197 |
运行开始时间运行消息是否连通
{{ Item.date }}{{ Item.msg }}{{ Item.flag }}
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 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | {% if HistoryCount > 0 %} 117 | {% for Item in IndexerHistory %} 118 | 119 | 122 | 125 | 130 | 133 | 154 | 155 | {% endfor %} 156 | {% else %} 157 | 158 | 159 | 160 | {% endif %} 161 | 162 |
ID站点索引规则添加时间操作
120 | {{ Item.id }} 121 | 123 | {{ Item.site }} 124 | 126 |
127 | {{ Item.indexer }} 128 |
129 |
131 | {{ Item.date or '' }} 132 | 134 | 153 |
没有数据
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 | 326 | 334 | 337 | 348 | 351 | 372 | 373 | {% endfor %} 374 | {% else %} 375 | 376 | 377 | 378 | {% endif %} 379 | 380 |
    标题类型状态添加时间
    322 | 325 | 327 |
    {{ Item.name }} ({{ Item.year }})
    328 | {% if Item.rating %} 329 |
    330 | 评份:{{ Item.rating }} 331 |
    332 | {% endif %} 333 |
    335 | {{ Item.type }} 336 | 338 | {% if Item.state == 'DOWNLOADED' %} 339 | 已下载 340 | {% elif Item.state == 'RSS' %} 341 | 已订阅 342 | {% elif Item.state == 'NEW' %} 343 | 新增 344 | {% else %} 345 | 处理中 346 | {% endif %} 347 | 349 | {{ Item.add_time or '' }} 350 | 352 | 371 |
    没有数据
    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 | 136 | 139 | 142 | 145 | 148 | 149 | {% endfor %} 150 | {% else %} 151 | 152 | 153 | 154 | {% endif %} 155 | 156 |
    站点同步类型状态描述同步时间
    134 |
    {{ Item.site }}
    135 |
    137 |
    {{ Item.type }}
    138 |
    140 | {{ Item.status }} 141 | 143 | {{ Item.message }} 144 | 146 | {{ Item.time }} 147 |
    没有数据
    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 | } --------------------------------------------------------------------------------