Merge pull request #227 from EstrellaXD/2.6-dev

2.6.4
This commit is contained in:
Estrella Pan
2023-05-03 16:39:03 +08:00
committed by GitHub
41 changed files with 561 additions and 460 deletions

View File

@@ -3,7 +3,7 @@ name: Deploy To Dockerhub(dev)
on:
push:
tags:
- '\d+\.\d+\.\d+-beta\d+'
- '\d+\.\d+\.\d+-beta(?:\d+)?'
jobs:
latest:

View File

@@ -1,41 +0,0 @@
name: Build(Docker)
on:
push:
tags:
- '\d+\.\d+\.\d+'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
- name: Create Version info
working-directory: ./src
run: |
echo "VERSION = '$GITHUB_REF_NAME'" > module/__version__.py
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/arm
push: true
tags: |
estrellaxd/auto_bangumi:latest
estrellaxd/auto_bangumi:${{ github.ref_name }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,6 +1,7 @@
name: Build(Docker)
on:
pull_request:
push:
tags:
- '\d+\.\d+\.\d+'
@@ -9,33 +10,47 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@v3
- name: Create Version info
working-directory: ./src
run: |
echo "VERSION = '$GITHUB_REF_NAME'" > module/__version__.py
-
name: Set up QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
-
name: Login to DockerHub
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
estrellaxd/auto_bangumi
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
- name: Login to DockerHub
if: ${{ github.event_name == 'push' }}
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v3
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
estrellaxd/auto_bangumi:latest
estrellaxd/auto_bangumi:${{ github.ref_name }}
builder: ${{ steps.buildx.output.name }}
platforms: linux/amd64,linux/arm64,linux/arm
push: ${{ github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha, scope=${{ github.workflow }}
cache-to: type=gha, scope=${{ github.workflow }}

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "docs/wiki"]
path = docs/wiki
url = https://github.com/EstrellaXD/Auto_Bangumi.wiki.git

View File

@@ -1,25 +1,6 @@
# syntax=docker/dockerfile:1
FROM python:3.11-alpine AS Builder
WORKDIR /app
COPY requirements.txt .
RUN apk add --no-cache --virtual=build-dependencies \
libxml2-dev \
libxslt-dev \
gcc \
g++ \
linux-headers \
build-base && \
python3 -m pip install --upgrade pip && \
pip install cython && \
pip install --no-cache-dir -r requirements.txt && \
apk del --purge \
build-dependencies && \
rm -rf \
/root/.cache \
/tmp/*
FROM scratch AS APP
FROM python:3.11-alpine AS APP
ENV S6_SERVICES_GRACETIME=30000 \
S6_KILL_GRACETIME=60000 \
@@ -33,31 +14,30 @@ ENV S6_SERVICES_GRACETIME=30000 \
PGID=1000 \
UMASK=022
COPY --from=Builder / /
WORKDIR /app
COPY requirements.txt .
RUN apk add --no-cache \
curl \
wget \
jq \
shadow \
s6-overlay \
bash && \
python3 -m pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt && \
# Download WebUI
wget "https://github.com/Rewrite0/Auto_Bangumi_WebUI/releases/latest/download/dist.zip" -O /tmp/dist.zip && \
unzip -q -d /tmp /tmp/dist.zip && \
mv /tmp/dist /app/templates && \
curl -sL "https://github.com/Rewrite0/Auto_Bangumi_WebUI/releases/latest/download/dist.zip" | busybox unzip -q -d /app - && \
mv /app/dist /app/templates && \
# Add user
mkdir /ab && \
addgroup -S ab -g 911 && \
adduser -S ab -G ab -h /ab -s /bin/bash -u 911 && \
# Clear
rm -rf \
/root/.cache \
/tmp/*
COPY --chmod=755 src/. .
COPY --chmod=755 ./docker /
COPY --chmod=755 src/docker /
ENTRYPOINT [ "/init" ]

View File

@@ -1,22 +1,16 @@
<p align="center">
<img src="https://github.com/EstrellaXD/Auto_Bangumi/blob/main/docs/image/auto_bangumi_v2.png#gh-light-mode-only" width=50%/>
<img src="https://github.com/EstrellaXD/Auto_Bangumi/blob/main/docs/image/auto_bangumi_icon_v2-dark.png#gh-dark-mode-only" width=50%/>
<img src="https://github.com/EstrellaXD/Auto_Bangumi/blob/main/docs/image/auto_bangumi_v2.png#gh-light-mode-only" width=50%/ alt="">
<img src="https://github.com/EstrellaXD/Auto_Bangumi/blob/main/docs/image/auto_bangumi_icon_v2-dark.png#gh-dark-mode-only" width=50%/ alt="">
</p>
<p align="center">
<img title="docker build version" src="https://img.shields.io/docker/v/estrellaxd/auto_bangumi" alt="">
<img title="release date" src="https://img.shields.io/github/release-date/estrellaxd/auto_bangumi" alt="">
<img title="docker pull" src="https://img.shields.io/docker/pulls/estrellaxd/auto_bangumi" alt="">
<img title="python version" src="https://img.shields.io/badge/python-3.10-blue" alt="">
<img title="platform arch" src="https://img.shields.io/badge/arch-%20AMD64%20%2F%20ARM64-lightgrey" alt="">
<img title="python version" src="https://img.shields.io/badge/python-3.11-blue" alt="">
<img title="telegram" src="https://img.shields.io/badge/telegram-autobangumi_update-blue" herf="https://t.me/autobangumi_update" alt="">
</p>
# 如何开始
- **[部署说明 (Official)](https://github.com/EstrellaXD/Auto_Bangumi/wiki)**
- **[部署说明 (手把手)](https://www.himiku.com/archives/auto-bangumi.html)**
# 项目说明
<p align="center">
@@ -30,9 +24,10 @@
<img title="plex" src="https://tva1.sinaimg.cn/large/e6c9d24ely1h47zd0v04zj21a50u0gvr.jpg" alt="" width=50%><img title="infuse" src="https://tva1.sinaimg.cn/large/e6c9d24ely1h47zd0gqz3j21a50u0dqc.jpg" alt="" width=50%>
- 主项目地址[AutoBangumi](https://www.github.com/EstrellaXD/Auto_Bangumi)
- 项目资源仓库:[ab_resource](https://www.github.com/EstrellaXD/ab_resource)
- WebUI 仓库:[AutoBangumi_WebUI](https://github.com/Rewrite0/Auto_Bangumi_WebUI)
[主项目地址](https://www.github.com/EstrellaXD/Auto_Bangumi)
/ [WebUI 仓库](https://github.com/Rewrite0/Auto_Bangumi_WebUI)
/ [Wiki 说明](https://www.github.com/EstrellaXD/Auto_Bangumi/wiki)
## AutoBangumi 功能说明
@@ -66,6 +61,13 @@
- 高度可自定义的功能选项,可以针对不同媒体库软件微调
- 无需维护完全无感使用
- 内置 TDMB 解析器,可以直接生成完整的 TMDB 格式的文件以及番剧信息。
- 对于 Mikan RSS 的反代支持。
## 如何开始
- **[部署说明 (Official)](https://github.com/EstrellaXD/Auto_Bangumi/wiki)**
- **[2.6版本更新说明](https://github.com/EstrellaXD/Auto_Bangumi/wiki/2.6更新说明)**
- **[部署说明 (手把手)](https://www.himiku.com/archives/auto-bangumi.html)**
## 相关群组
@@ -75,18 +77,18 @@
## Roadmap
***开发中的功能:***
- Web UI [#57](https://github.com/EstrellaXD/Auto_Bangumi/issues/57)
- Web UI #57
- 文件统一整理,对单个规则或者文件微调文件夹可以自动调整所有对应的文件。
- 通知功能,可以通过 IFTTT 等方式通知用户番剧更新进度。
- 剧场版以及合集的支持。
- 通知功能,可以通过 IFTTT 等方式通知用户番剧更新进度。
- 剧场版以及合集的支持。
- 各类 API 接口。
***计划开发的功能:***
- 对其他站点种子的解析归类。
- 本地化番剧订阅方式。
- Transmission & Aria2 的支持。
- 更完善的 WebUI。
***未计划开发的功能:***
- Transmission & Aria2 的支持,可以转到 [RanKKI/Bangumi](https://github.com/RanKKI/Bangumi),该项目具有 Aria2/Tr 的支持
# 声明
## 致谢

View File

@@ -19,7 +19,7 @@ services:
networks:
- auto_bangumi
restart: unless-stopped
image: lscr.io/linuxserver/qbittorrent:4.4.3
image: superng6/qbittorrent:latest
auto_bangumi:
container_name: AutoBangumi
@@ -27,22 +27,12 @@ services:
- TZ=Asia/Shanghai
- PGID=${GID}
- PUID=${UID}
- AB_INTERVAL_TIME=1800
- AB_DOWNLOADER_HOST=qbittorrent:${QB_PORT}
- AB_DOWNLOADER_USERNAME=admin
- AB_DOWNLOADER_PASSWORD=adminadmin
- AB_METHOD=pn
- AB_GROUP_TAG=True
- AB_NOT_CONTAIN=720
- AB_DOWNLOAD_PATH=/downloads/Bangumi
- AB_RSS=${RSS}
- AB_DEBUG_MODE=False
- AB_EP_COMPLETE=False
- AB_SEASON_ONE=True
networks:
- auto_bangumi
volumes:
- auto_bangumi:/config
- /path/to/config:/app/config
- /path/to/data:/app/data
ports:
- 7892:7892
dns:
@@ -78,6 +68,4 @@ volumes:
external: false
plex_config:
external: false
auto_bangumi:
external: false

View File

@@ -6,17 +6,13 @@ services:
- TZ=Asia/Shanghai
- PGID=${GID}
- PUID=${UID}
- AB_DOWNLOADER_HOST=localhost:${QB_PORT}
- AB_DOWNLOADER_USERNAME=admin
- AB_DOWNLOADER_PASSWORD=adminadmin
- AB_DOWNLOAD_PATH=${DOWNLOAD_PATH}
- AB_RSS=${RSS}
networks:
- auto_bangumi
ports:
- '7892:7892'
volumes:
- auto_bangumi:/config
- ./config:/app/config
- ./data:/app/data
dns:
- 8.8.8.8
- 223.5.5.5

View File

@@ -28,13 +28,11 @@ services:
- PGID=${GID}
- PUID=${UID}
- AB_DOWNLOADER_HOST=qbittorrent:${QB_PORT}
- AB_DOWNLOADER_USERNAME=admin
- AB_DOWNLOADER_PASSWORD=adminadmin
- AB_RSS=${AB_RSS}
- AB_DOWNLOAD_PATH=/downloads/Bangumi
- AB_RSS=${RSS}
volumes:
- /path/to/config:/app/config
- /path/to/data:/app/data
- ./config:/app/config
- ./data:/app/data
networks:
- auto_bangumi
ports:

1
docs/wiki Submodule

Submodule docs/wiki added at a4f8c3b83b

View File

@@ -1,6 +1,5 @@
anyio
beautifulsoup4
lxml
certifi
charset-normalizer
click

View File

@@ -6,7 +6,7 @@ function __fixuser {
groupmod -o -g "${PGID}" ab
usermod -o -u "${PUID}" ab
chown ab:ab -R /app
chown ab:ab -R /app /ab
}

View File

@@ -16,7 +16,7 @@ from module.conf import VERSION, settings
logger = logging.getLogger(__name__)
main_process = multiprocessing.Process(target=app.run)
main_process = multiprocessing.Process(target=app.run, args=(settings,))
@router.get("/api/v1/restart", tags=["program"])
@@ -27,7 +27,8 @@ async def restart():
logger.info("Restarting...")
else:
logger.info("Starting...")
main_process = multiprocessing.Process(target=app.run)
settings.reload()
main_process = multiprocessing.Process(target=app.run, args=(settings,))
main_process.start()
logger.info("Restarted")
return {"status": "success"}
@@ -50,7 +51,8 @@ async def start():
if main_process.is_alive():
return {"status": "failed", "reason": "Already started"}
logger.info("Starting...")
main_process = multiprocessing.Process(target=app.run)
settings.reload()
main_process = multiprocessing.Process(target=app.run, args=(settings,))
main_process.start()
logger.info("Started")
return {"status": "success"}

View File

@@ -12,7 +12,7 @@ from module.models import Config
router = FastAPI()
api_func = APIProcess()
api_func = APIProcess(settings)
@router.on_event("startup")

View File

@@ -2,13 +2,13 @@ import os
import time
import logging
from module.conf import settings, setup_logger, LOG_PATH, DATA_PATH, RSSLink, VERSION
from module.conf import setup_logger, LOG_PATH, DATA_PATH, RSSLink, VERSION
from module.utils import load_program_data, save_program_data
from module.core import DownloadClient
from module.manager import Renamer, FullSeasonGet
from module.rss import RSSAnalyser
from module.models import ProgramData
from module.models import ProgramData, Config
logger = logging.getLogger(__name__)
@@ -19,10 +19,10 @@ def reset_log():
os.remove(LOG_PATH)
def load_data_file(rss_link: str) -> ProgramData:
def load_data_file(rss_link: str, data_version) -> ProgramData:
empty_data = ProgramData(
rss_link=rss_link,
data_version=settings.data_version,
data_version=data_version,
)
if not os.path.exists(DATA_PATH):
program_data = empty_data
@@ -30,28 +30,29 @@ def load_data_file(rss_link: str) -> ProgramData:
logger.info("Building data information...")
else:
program_data = load_program_data(DATA_PATH)
if program_data.rss_link != rss_link or program_data.data_version != settings.data_version:
if program_data.rss_link != rss_link or program_data.data_version != data_version:
program_data = empty_data
logger.info("Rebuilding data information...")
return program_data
def main_process(program_data: ProgramData, download_client: DownloadClient):
rename = Renamer(download_client)
rss_analyser = RSSAnalyser()
def main_process(program_data: ProgramData, download_client: DownloadClient, _settings: Config):
rename = Renamer(download_client, _settings)
rss_analyser = RSSAnalyser(_settings)
while True:
times = 0
if settings.rss_parser.enable:
rss_analyser.run(program_data.bangumi_info, download_client, program_data.rss_link)
if settings.bangumi_manage.eps_complete and program_data.bangumi_info != []:
FullSeasonGet().eps_complete(program_data.bangumi_info, download_client)
if _settings.rss_parser.enable:
rss_analyser.run(program_data.bangumi_info, program_data.rss_link)
download_client.add_rules(program_data.bangumi_info, program_data.rss_link)
if _settings.bangumi_manage.eps_complete and program_data.bangumi_info != []:
FullSeasonGet(settings=_settings).eps_complete(program_data.bangumi_info, download_client)
logger.info("Running....")
save_program_data(DATA_PATH, program_data)
while times < settings.program.rename_times:
if settings.bangumi_manage.enable:
while times < _settings.program.rename_times:
if _settings.bangumi_manage.enable:
rename.rename()
times += 1
time.sleep(settings.program.sleep_time / settings.program.rename_times)
time.sleep(_settings.program.sleep_time / _settings.program.rename_times)
def show_info():
@@ -63,9 +64,8 @@ def show_info():
logger.info("Starting AutoBangumi...")
def run():
def run(settings: Config):
# 初始化
settings.reload()
rss_link = RSSLink()
reset_log()
setup_logger()
@@ -73,10 +73,10 @@ def run():
if settings.rss_parser.token in ["", "token", None]:
logger.error("Please set your RSS token in config file.")
exit(1)
download_client = DownloadClient()
download_client = DownloadClient(settings)
download_client.auth()
download_client.init_downloader()
download_client.rss_feed(rss_link)
bangumi_data = load_data_file(rss_link)
bangumi_data = load_data_file(rss_link, settings.data_version)
# 主程序循环
main_process(bangumi_data, download_client)
main_process(bangumi_data, download_client, settings)

View File

@@ -11,3 +11,6 @@ class RSSLink(str):
if "://" not in settings.rss_parser.custom_url:
return f"https://{settings.rss_parser.custom_url}/RSS/MyBangumi?token={settings.rss_parser.token}"
return f"{settings.rss_parser.custom_url}/RSS/MyBangumi?token={settings.rss_parser.token}"
PLATFORM = "Windows" if "\\" in settings.downloader.path else "Unix"

View File

@@ -5,7 +5,7 @@ from module.core import DownloadClient
from module.manager import FullSeasonGet
from module.rss import RSSAnalyser
from module.utils import json_config
from module.conf import DATA_PATH, settings
from module.conf import DATA_PATH
from module.conf.config import save_config_to_file, CONFIG_PATH
from module.models import Config
from module.network import RequestContent
@@ -16,13 +16,14 @@ logger = logging.getLogger(__name__)
class APIProcess:
def __init__(self):
self._rss_analyser = RSSAnalyser()
self._client = DownloadClient()
self._full_season_get = FullSeasonGet()
def __init__(self, settings: Config):
self._rss_analyser = RSSAnalyser(settings)
self._client = DownloadClient(settings)
self._full_season_get = FullSeasonGet(settings)
self._custom_url = settings.rss_parser.custom_url
def link_process(self, link):
return self._rss_analyser.rss_to_data(link, filter=False)
return self._rss_analyser.rss_to_data(link, _filter=False)
@api_failed
def download_collection(self, link):
@@ -58,7 +59,6 @@ class APIProcess:
json_config.save(DATA_PATH, datas)
return "Success"
@staticmethod
def add_rule(title, season):
data = json_config.load(DATA_PATH)
@@ -85,10 +85,9 @@ class APIProcess:
def get_config() -> dict:
return json_config.load(CONFIG_PATH)
@staticmethod
def get_rss(full_path: str):
def get_rss(self, full_path: str):
url = f"https://mikanani.me/RSS/{full_path}"
custom_url = settings.rss_parser.custom_url
custom_url = self._custom_url
if "://" not in custom_url:
custom_url = f"https://{custom_url}"
with RequestContent() as request:

View File

@@ -3,25 +3,22 @@ import logging
import os
from module.downloader import getClient
from module.conf import settings
from module.models import BangumiData
from module.models import BangumiData, Config
logger = logging.getLogger(__name__)
class DownloadClient:
def __init__(self):
self.client = getClient()
def __init__(self, settings: Config):
self.client = getClient(settings)
self.authed = False
self.download_path = settings.downloader.path
self.group_tag = settings.bangumi_manage.group_tag
def auth(self):
host, username, password = settings.downloader.host, settings.downloader.username, settings.downloader.password
try:
self.client.auth(host, username, password)
self.authed = True
except Exception as e:
logger.error(f"Can't login {host} by {username}, {e}")
self.client.auth()
self.authed = True
def init_downloader(self):
prefs = {
@@ -36,16 +33,16 @@ class DownloadClient:
except Exception as e:
logger.warning("Cannot add new category, maybe already exists.")
logger.debug(e)
if settings.downloader.path == "":
if self.download_path == "":
prefs = self.client.get_app_prefs()
settings.downloader.path = os.path.join(prefs["save_path"], "Bangumi")
self.download_path = os.path.join(prefs["save_path"], "Bangumi")
def set_rule(self, info: BangumiData, rss_link):
official_name, raw_name, season, group = info.official_title, info.title_raw, info.season, info.group
rule = {
"enable": True,
"mustContain": raw_name,
"mustNotContain": "|".join(settings.rss_parser.filter),
"mustNotContain": "|".join(info.filter),
"useRegex": True,
"episodeFilter": "",
"smartFilter": False,
@@ -57,28 +54,23 @@ class DownloadClient:
"assignedCategory": "Bangumi",
"savePath": str(
os.path.join(
settings.downloader.path,
self.download_path,
re.sub(r"[:/.]", " ", official_name).strip(),
f"Season {season}",
)
),
}
rule_name = f"[{group}] {official_name}" if settings.bangumi_manage.group_tag else official_name
rule_name = f"[{group}] {official_name}" if self.group_tag else official_name
self.client.rss_set_rule(rule_name=f"{rule_name} S{season}", rule_def=rule)
logger.info(f"Add {official_name} Season {season}")
def rss_feed(self, rss_link, item_path="Mikan_RSS"):
# TODO: 定时刷新 RSS
if self.client.get_rss_info(rss_link):
logger.info("RSS Already exists.")
else:
logger.info("No feed exists, start adding feed.")
self.client.rss_add_feed(url=rss_link, item_path="Mikan_RSS")
logger.info("Add RSS Feed successfully.")
self.client.rss_add_feed(url=rss_link, item_path=item_path)
def add_collection_feed(self, rss_link, item_path):
self.client.rss_add_feed(url=rss_link, item_path=item_path)
logger.info("Add RSS Feed successfully.")
logger.info("Add Collection RSS Feed successfully.")
def add_rules(self, bangumi_info: list[BangumiData], rss_link: str):
logger.debug("Start adding rules.")

View File

@@ -1,7 +1,7 @@
import re
import logging
from bs4 import BeautifulSoup
import xml.etree.ElementTree
from typing import Tuple
from module.conf import settings
from module.utils import json_config
@@ -13,9 +13,9 @@ class RSSFilter:
def __init__(self):
self.filter_rule = json_config.load(settings.filter_rule)
def filter(self, item: BeautifulSoup):
title = item.title.string
torrent = item.find("enclosure")
def filter(self, item: xml.etree.ElementTree.Element) -> Tuple[bool, str]:
title = item.find('title').text
torrent = item.find("enclosure").attrib['url']
download = False
for rule in self.filter_rule:
if re.search(rule["include"], title):
@@ -23,4 +23,3 @@ class RSSFilter:
download = True
logger.debug(f"{title} added")
return download, torrent

View File

@@ -1,11 +1,15 @@
from module.conf import settings
from module.models import Config
def getClient():
def getClient(settings: Config):
# TODO 多下载器支持
# 从 settings 里读取下载器名称,然后返回对应 Client
if settings.downloader.type == "qbittorrent":
type = settings.downloader.type
host = settings.downloader.host
username = settings.downloader.username
password = settings.downloader.password
ssl = settings.downloader.ssl
if type == "qbittorrent":
from .qb_downloader import QbDownloader
return QbDownloader()
return QbDownloader(host, username, password, ssl)
else:
raise Exception(f"Unsupported downloader type: {settings.downloader.type}")
raise Exception(f"Unsupported downloader type: {type}")

View File

@@ -4,9 +4,9 @@ import time
from aria2p import Client, ClientException, API
from conf import settings
from module.conf import settings
from downloader.exceptions import ConflictError
from .exceptions import ConflictError
logger = logging.getLogger(__name__)

View File

@@ -1,2 +1,2 @@
class ConflictError(Exception):
pass
pass

View File

@@ -4,33 +4,32 @@ import time
from qbittorrentapi import Client, LoginFailed
from qbittorrentapi.exceptions import Conflict409Error
from module.conf import settings
from module.ab_decorator import qb_connect_failed_wait
from module.downloader.exceptions import ConflictError
logger = logging.getLogger(__name__)
class QbDownloader:
def __init__(self):
self._client: Client | None = None
@qb_connect_failed_wait
def auth(self, host, username, password):
self._client = Client(
def __init__(self, host: str, username: str, password: str, ssl: bool):
self._client: Client = Client(
host=host,
username=username,
password=password,
VERIFY_WEBUI_CERTIFICATE=settings.downloader.ssl
VERIFY_WEBUI_CERTIFICATE=ssl
)
self.host = host
self.username = username
@qb_connect_failed_wait
def auth(self):
while True:
try:
self._client.auth_log_in()
break
except LoginFailed:
logger.warning(
f"Can't login qBittorrent Server {host} by {username}, retry in {5} seconds."
logger.error(
f"Can't login qBittorrent Server {self.host} by {self.username}, retry in {5} seconds."
)
time.sleep(5)
@@ -66,21 +65,35 @@ class QbDownloader:
def torrents_rename_file(self, torrent_hash, old_path, new_path):
self._client.torrents_rename_file(torrent_hash=torrent_hash, old_path=old_path, new_path=new_path)
def get_rss_info(self, url) -> str | None:
def check_rss(self, url, item_path) -> tuple[str | None, bool]:
items = self._client.rss_items()
for item in items.items():
if item[1].url == url:
return item[0]
return None
for key, value in items.items():
rss_url = value.get("url")
if key == item_path:
if rss_url != url:
return key, False
return None, True
else:
if rss_url == url:
return key, True
return None, False
def rss_add_feed(self, url, item_path):
try:
path = self.get_rss_info(url)
if path:
self.rss_remove_item(path)
self._client.rss_add_feed(url, item_path)
except Conflict409Error:
logger.exception("RSS Exist.")
path, added = self.check_rss(url, item_path)
if path:
if not added:
logger.info("RSS Exist, Update URL.")
self._client.rss_remove_item(path)
self._client.rss_add_feed(url, item_path)
else:
logger.info("RSS Exist.")
else:
if added:
logger.info("RSS Exist.")
else:
logger.info("Add new RSS")
self._client.rss_add_feed(url, item_path)
logger.info("Successfully added RSS")
def rss_remove_item(self, item_path):
try:

View File

@@ -2,29 +2,27 @@ import os.path
import re
import logging
from module.conf import settings
from module.network import RequestContent
from module.core import DownloadClient
from module.models import BangumiData
from module.models import BangumiData, Config
logger = logging.getLogger(__name__)
SEARCH_KEY = ["group", "title_raw", "season_raw", "subtitle", "source", "dpi"]
CUSTOM_URL = "https://mikanani.me" if settings.rss_parser.custom_url == "" else settings.rss_parser.custom_url
if "://" not in CUSTOM_URL:
if re.match(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", CUSTOM_URL):
CUSTOM_URL = f"http://{CUSTOM_URL}"
CUSTOM_URL = f"https://{CUSTOM_URL}"
class FullSeasonGet:
def __init__(self):
pass
def __init__(self, settings: Config):
self.SEARCH_KEY = ["group", "title_raw", "season_raw", "subtitle", "source", "dpi"]
self.CUSTOM_URL = "https://mikanani.me" if settings.rss_parser.custom_url == "" else settings.rss_parser.custom_url
if "://" not in self.CUSTOM_URL:
if re.match(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", self.CUSTOM_URL):
self.CUSTOM_URL = f"http://{self.CUSTOM_URL}"
self.CUSTOM_URL = f"https://{self.CUSTOM_URL}"
self.save_path = settings.downloader.path
@staticmethod
def init_eps_complete_search_str(data: BangumiData):
def init_eps_complete_search_str(self, data: BangumiData):
test = []
for key in SEARCH_KEY:
for key in self.SEARCH_KEY:
data_dict = data.dict()
if data_dict[key] is not None:
test.append(data_dict[key])
@@ -35,17 +33,16 @@ class FullSeasonGet:
def get_season_torrents(self, data: BangumiData):
keyword = self.init_eps_complete_search_str(data)
with RequestContent() as req:
torrents = req.get_torrents(f"{CUSTOM_URL}/RSS/Search?searchstr={keyword}")
torrents = req.get_torrents(f"{self.CUSTOM_URL}/RSS/Search?searchstr={keyword}")
return [torrent for torrent in torrents if data.title_raw in torrent.name]
@staticmethod
def collect_season_torrents(data: BangumiData, torrents):
def collect_season_torrents(self, data: BangumiData, torrents):
downloads = []
for torrent in torrents:
download_info = {
"url": torrent.torrent_link,
"save_path": os.path.join(
settings.downloader.path,
self.save_path,
data.official_title,
f"Season {data.season}")
}

View File

@@ -5,18 +5,19 @@ from pathlib import PurePath, PureWindowsPath
from module.core.download_client import DownloadClient
from module.conf import settings
from module.parser import TitleParser
from module.network import PostNotification
from module.models import Config
logger = logging.getLogger(__name__)
class Renamer:
def __init__(self, download_client: DownloadClient):
self.client = download_client
def __init__(self, download_client: DownloadClient, settings: Config):
self._client = download_client
self._renamer = TitleParser()
self.notification = PostNotification()
self._notification = PostNotification()
self.settings = settings
@staticmethod
def print_result(torrent_count, rename_count):
@@ -25,78 +26,104 @@ class Renamer:
logger.debug(f"Checked {torrent_count} files")
def get_torrent_info(self, category="Bangumi"):
recent_info = self.client.get_torrent_info(category=category)
recent_info = self._client.get_torrent_info(category=category)
torrent_count = len(recent_info)
return recent_info, torrent_count
@staticmethod
def check_files(info, suffix_type: str = "media"):
if suffix_type == "subtitle":
suffix_list = [".ass", ".srt"]
else:
suffix_list = [".mp4", ".mkv"]
file_list = []
def check_files(info):
media_list = []
subtitle_list = []
for f in info.files:
file_name = f.name
suffix = os.path.splitext(file_name)[-1]
if suffix.lower() in suffix_list:
file_list.append(file_name)
return file_list
if suffix.lower() in [".mp4", ".mkv"]:
media_list.append(file_name)
elif suffix.lower() in [".ass", ".srt"]:
subtitle_list.append(file_name)
return media_list, subtitle_list
def rename_file(self, info, media_path):
old_name = info.name
def rename_file(self, info, media_path: str, rename_method: str, bangumi_name: str, season: int, remove_bad_torrents: bool):
torrent_name = info.name
suffix = os.path.splitext(media_path)[-1]
compare_name = media_path.split(os.path.sep)[-1]
folder_name, season = self.get_folder_and_season(info.save_path)
new_path = self._renamer.download_parser(old_name, folder_name, season, suffix)
new_path = self._renamer.torrent_parser(
torrent_name=torrent_name,
bangumi_name=bangumi_name,
season=season,
suffix=suffix,
method=rename_method
)
if compare_name != new_path:
try:
self.client.rename_torrent_file(_hash=info.hash, old_path=media_path, new_path=new_path)
self.notification.send_msg(folder_name, "update")
self._client.rename_torrent_file(_hash=info.hash, old_path=media_path, new_path=new_path)
self._notification.send_msg(bangumi_name, "最新剧集已经更新,已自动重命名。")
except Exception as e:
logger.warning(f"{old_name} rename failed")
logger.warning(f"Folder name: {folder_name}, Season: {season}, Suffix: {suffix}")
logger.warning(f"{torrent_name} rename failed")
logger.warning(f"Season name: {bangumi_name}, Season: {season}, Suffix: {suffix}")
logger.debug(e)
# Delete bad torrent
self.delete_bad_torrent(info)
self.delete_bad_torrent(info, remove_bad_torrents)
def rename_collection(self, info, media_list: list[str]):
folder_name, season = self.get_folder_and_season(info.save_path)
def rename_collection(self, info, media_list: list[str],bangumi_name: str, season: int, remove_bad_torrents: bool):
_hash = info.hash
for media_path in media_list:
path_len = len(media_path.split(os.path.sep))
if path_len <= 2:
suffix = os.path.splitext(media_path)[-1]
old_name = media_path.split(os.path.sep)[-1]
new_name = self._renamer.download_parser(old_name, folder_name, season, suffix)
if old_name != new_name:
torrent_name = media_path.split(os.path.sep)[-1]
new_name = self._renamer.torrent_parser(
torrent_name=torrent_name,
bangumi_name=bangumi_name,
season=season,
suffix=suffix,
method="pn"
)
if torrent_name != new_name:
try:
self.client.rename_torrent_file(_hash=_hash, old_path=media_path, new_path=new_name)
self._client.rename_torrent_file(_hash=_hash, old_path=media_path, new_path=new_name)
except Exception as e:
logger.warning(f"{old_name} rename failed")
logger.warning(f"Folder name: {folder_name}, Season: {season}, Suffix: {suffix}")
logger.warning(f"{torrent_name} rename failed")
logger.warning(f"Bangumi name: {bangumi_name}, Season: {season}, Suffix: {suffix}")
logger.debug(e)
# Delete bad torrent.
self.delete_bad_torrent(info)
self.client.set_category(category="BangumiCollection", hashes=_hash)
self.delete_bad_torrent(info, remove_bad_torrents)
self._client.set_category(category="BangumiCollection", hashes=_hash)
def rename_subtitles(self, subtitle_list: list[str], media_old_name, media_new_name, _hash):
for subtitle_file in subtitle_list:
if re.search(media_old_name, subtitle_file) is not None:
subtitle_lang = subtitle_file.split(".")[-2]
new_subtitle_name = f"{media_new_name}.{subtitle_lang}.ass"
self.client.rename_torrent_file(_hash, subtitle_file, new_subtitle_name)
logger.info(f"Rename subtitles for {media_old_name} to {media_new_name}")
def rename_subtitles(
self,
subtitle_list: list[str],
bangumi_name: str,
season: int,
_hash
):
for subtitle_path in subtitle_list:
suffix = os.path.splitext(subtitle_path)[-1]
old_name = subtitle_path.split(os.path.sep)[-1]
new_name = self._renamer.torrent_parser(
method="subtitle",
torrent_name=old_name,
bangumi_name=bangumi_name,
season=season,
suffix=suffix
)
if old_name != new_name:
try:
self._client.rename_torrent_file(_hash=_hash, old_path=subtitle_path, new_path=new_name)
except Exception as e:
logger.warning(f"{old_name} rename failed")
logger.warning(f"Suffix: {suffix}")
logger.debug(e)
def delete_bad_torrent(self, info):
if settings.bangumi_manage.remove_bad_torrent:
self.client.delete_torrent(info.hash)
def delete_bad_torrent(self, info, remove_bad_torrent: bool):
if remove_bad_torrent:
self._client.delete_torrent(info.hash)
logger.info(f"{info.name} have been deleted.")
@staticmethod
def get_folder_and_season(save_path: str):
def get_season_info(save_path: str, download_path: str):
# Remove default save path
save_path = save_path.replace(settings.downloader.path, "")
save_path = save_path.replace(download_path, "")
# Check windows or linux path
path_parts = PurePath(save_path).parts \
if PurePath(save_path).name != save_path \
@@ -117,24 +144,63 @@ class Renamer:
def rename(self):
# Get torrent info
download_path = self.settings.downloader.path
rename_method = self.settings.bangumi_manage.rename_method
remove_bad_torrents = self.settings.bangumi_manage.remove_bad_torrent
recent_info, torrent_count = self.get_torrent_info()
rename_count = 0
for info in recent_info:
media_list = self.check_files(info)
media_list, subtitle_list = self.check_files(info)
bangumi_name, season = self.get_season_info(info.save_path, download_path)
if len(media_list) == 1:
self.rename_file(info, media_list[0])
rename_count += 1
# TODO: Rename subtitles
self.rename_file(
info=info,
media_path=media_list[0],
rename_method=rename_method,
bangumi_name=bangumi_name,
season=season,
remove_bad_torrents=remove_bad_torrents
)
if len(subtitle_list) > 0:
self.rename_subtitles(
subtitle_list=subtitle_list,
bangumi_name=bangumi_name,
season=season,
_hash=info.hash
)
elif len(media_list) > 1:
logger.info("Start rename collection")
self.rename_collection(info, media_list)
rename_count += len(media_list)
self.rename_collection(
info,
media_list,
bangumi_name,
season,
remove_bad_torrents
)
if len(subtitle_list) > 0:
self.rename_subtitles(
subtitle_list=subtitle_list,
bangumi_name=bangumi_name,
season=season,
_hash=info.hash
)
else:
logger.warning(f"{info.name} has no media file")
if __name__ == '__main__':
client = DownloadClient()
rn = Renamer(client)
rn.rename()
from module.conf import settings, setup_logger
setup_logger()
client = DownloadClient(settings)
renamer = Renamer(client, settings)
info, _ = renamer.get_torrent_info(category="BangumiCollection")
for i in info:
_hash = i.hash
_, subtitle_list = renamer.check_files(i)
print(_hash)
bangumi_name, season = renamer.get_season_info(i.save_path, settings.downloader.path)
renamer.rename_subtitles(
subtitle_list,
bangumi_name=bangumi_name,
season=season,
_hash=_hash
)

View File

@@ -0,0 +1,12 @@
from pydantic import BaseModel, Field
class TorrentInfo(BaseModel):
name: str = Field(...)
link: str = Field(...)
class FileSet(BaseModel):
media_path: str = Field(...)
sc_subtitle: str | None = Field(None)
tc_subtitle: str | None = Field(None)

View File

@@ -42,6 +42,7 @@ class TelegramNotification:
}
with RequestContent() as req:
resp = req.post_data(self.notification_url, data)
logger.debug(f"Telegram notification: {resp.status_code}")
return resp.status_code == 200
@@ -58,4 +59,5 @@ class ServerChanNotification:
}
with RequestContent() as req:
resp = req.post_data(self.notification_url, data)
logger.debug(f"ServerChan notification: {resp.status_code}")
return resp.status_code == 200

View File

@@ -1,13 +1,10 @@
import re
import xml.etree.ElementTree
from dataclasses import dataclass
from bs4 import BeautifulSoup
from .request_url import RequestURL
from module.conf import settings
FILTER = "|".join(settings.rss_parser.filter)
@@ -19,27 +16,26 @@ class TorrentInfo:
class RequestContent(RequestURL):
# Mikanani RSS
def get_torrents(self, _url: str, filter: bool = True) -> [TorrentInfo]:
def get_torrents(self, _url: str, _filter: bool = True) -> [TorrentInfo]:
soup = self.get_xml(_url)
torrent_titles = [item.title.string for item in soup.find_all("item")]
torrent_urls = [item.get("url") for item in soup.find_all("enclosure")]
torrent_titles = []
torrent_urls = []
for item in soup.findall("./channel/item"):
torrent_titles.append(item.find("title").text)
torrent_urls.append(item.find("enclosure").attrib['url'])
torrents = []
for _title, torrent_url in zip(torrent_titles, torrent_urls):
if filter:
if _filter:
if re.search(FILTER, _title) is None:
torrents.append(TorrentInfo(_title, torrent_url))
else:
torrents.append(TorrentInfo(_title, torrent_url))
return torrents
def get_torrent(self, _url) -> TorrentInfo:
soup = self.get_xml(_url)
item = soup.find("item")
enclosure = item.find("enclosure")
return TorrentInfo(item.title.string, enclosure["url"])
def get_xml(self, _url):
return BeautifulSoup(self.get_url(_url).text, "xml")
def get_xml(self, _url) -> xml.etree.ElementTree.ElementTree:
return xml.etree.ElementTree.fromstring(self.get_url(_url).text)
# API JSON
def get_json(self, _url) -> dict:

View File

@@ -27,7 +27,8 @@ class RequestURL:
except requests.RequestException as e:
logger.debug(f"URL: {url}")
logger.debug(e)
logger.warning("ERROR with Connection.Please check DNS/Connection settings")
logger.warning(f"Cannot connect to {url}. Wait for 5 seconds.")
logger.warning("Please check DNS/Connection settings")
time.sleep(5)
try_time += 1
except Exception as e:

View File

@@ -1,4 +1,4 @@
from .raw_parser import raw_parser
from .rename_parser import DownloadParser
from .torrent_parser import torrent_parser
from .tmdb_parser import TMDBMatcher

View File

@@ -1,116 +0,0 @@
import re
import logging
from dataclasses import dataclass
logger = logging.getLogger(__name__)
@dataclass
class DownloadInfo:
name: str
season: int
suffix: str
file_name: str
folder_name: str
RULES = [
r"(.*) - (\d{1,4}|\d{1,4}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?(.*)",
r"(.*)[\[ E](\d{1,3}|\d{1,3}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?[\] ](.*)",
r"(.*)\[第(\d*\.*\d*)话(?:END)?\](.*)",
r"(.*)\[第(\d*\.*\d*)話(?:END)?\](.*)",
r"(.*)第(\d*\.*\d*)话(?:END)?(.*)",
r"(.*)第(\d*\.*\d*)話(?:END)?(.*)",
r"(.*)E(\d{1,3})(.*)",
]
class DownloadParser:
def __init__(self):
self.method_dict = {
"normal": self.rename_normal,
"pn": self.rename_pn,
"advance": self.rename_advance,
"no_season_pn": self.rename_no_season_pn,
"none": self.rename_none
}
@staticmethod
def rename_init(name, folder_name, season, suffix) -> DownloadInfo:
n = re.split(r"[\[\]()【】()]", name)
suffix = suffix if suffix else n[-1]
if len(n) > 1:
file_name = name.replace(f"[{n[1]}]", "")
else:
file_name = name
if season < 10:
season = f"0{season}"
return DownloadInfo(name, season, suffix, file_name, folder_name)
def rename_normal(self, info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
new_name = f"{title} S{info.season}E{match_obj.group(2)}{match_obj.group(3)}"
return new_name
def rename_pn(self, info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
title = title if title != "" else info.folder_name
new_name = re.sub(
r"[\[\]]",
"",
f"{title} S{info.season}E{match_obj.group(2)}{info.suffix}",
)
return new_name
def rename_advance(self, info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
new_name = re.sub(
r"[\[\]]",
"",
f"{info.folder_name} S{info.season}E{match_obj.group(2)}{info.suffix}",
)
return new_name
def rename_no_season_pn(self, info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = match_obj.group(1).strip()
new_name = re.sub(
r"[\[\]]",
"",
f"{title} E{match_obj.group(2)}{info.suffix}",
)
return new_name
@staticmethod
def rename_none(info: DownloadInfo):
return info.name
def download_rename(
self,
name: str,
folder_name,
season,
suffix,
method
):
rename_info = self.rename_init(name, folder_name, season, suffix)
return self.method_dict[method.lower()](rename_info)
if __name__ == "__main__":
name = "[Lilith-Raws] Tate no Yuusha no Nariagari S02 - 02 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4]"
rename = DownloadParser()
new_name = rename.download_rename(name, "异世界舅舅2022", 1, ".mp4", "normal")
print(new_name)

View File

@@ -0,0 +1,138 @@
import re
import logging
from dataclasses import dataclass
logger = logging.getLogger(__name__)
@dataclass
class DownloadInfo:
name: str
season: int
suffix: str
file_name: str
folder_name: str
RULES = [
r"(.*) - (\d{1,4}|\d{1,4}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?(.*)",
r"(.*)[\[ E](\d{1,3}|\d{1,3}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?[\] ](.*)",
r"(.*)\[(?:第)?(\d*\.*\d*)[话集話](?:END)?\](.*)",
r"(.*)第(\d*\.*\d*)[话話集](?:END)?(.*)",
r"(.*)E(\d{1,4})(.*)",
]
SUBTITLE_LANG = {
"zh-tw": ["TC", "CHT", "", "zh-tw"],
"zh": ["SC", "CHS", "", "zh"],
}
def rename_init(name, folder_name, season, suffix) -> DownloadInfo:
n = re.split(r"[\[\]()【】()]", name)
suffix = suffix if suffix else n[-1]
if len(n) > 1:
file_name = name.replace(f"[{n[1]}]", "")
else:
file_name = name
if season < 10:
season = f"0{season}"
return DownloadInfo(name, season, suffix, file_name, folder_name)
def rename_normal(info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
new_name = f"{title} S{info.season}E{match_obj.group(2)}{match_obj.group(3)}"
return new_name
def rename_pn(info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
title = title if title != "" else info.folder_name
new_name = re.sub(
r"[\[\]]",
"",
f"{title} S{info.season}E{match_obj.group(2)}{info.suffix}",
)
return new_name
def rename_advance(info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
new_name = re.sub(
r"[\[\]]",
"",
f"{info.folder_name} S{info.season}E{match_obj.group(2)}{info.suffix}",
)
return new_name
def rename_no_season_pn(info: DownloadInfo):
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = match_obj.group(1).strip()
new_name = re.sub(
r"[\[\]]",
"",
f"{title} E{match_obj.group(2)}{info.suffix}",
)
return new_name
def rename_none(info: DownloadInfo):
return info.name
def rename_subtitle(info: DownloadInfo):
subtitle_lang = "zh"
break_flag = False
for key, value in SUBTITLE_LANG.items():
for lang in value:
if lang in info.name:
subtitle_lang = key
break_flag = True
break
if break_flag:
break
for rule in RULES:
match_obj = re.match(rule, info.file_name, re.I)
if match_obj is not None:
title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip()
title = title if title != "" else info.folder_name
new_name = re.sub(
r"[\[\]]",
"",
f"{title} S{info.season}E{match_obj.group(2)}.{subtitle_lang}{info.suffix}",
)
return new_name
METHODS = {
"normal": rename_normal,
"pn": rename_pn,
"advance": rename_advance,
"no_season_pn": rename_no_season_pn,
"none": rename_none,
"subtitle": rename_subtitle,
}
def torrent_parser(
file_name: str,
folder_name: str,
season: int,
suffix: str,
method: str = "pn",
):
info = rename_init(file_name, folder_name, season, suffix)
return METHODS[method.lower()](info)

View File

@@ -1,31 +1,27 @@
import logging
from .analyser import raw_parser, DownloadParser, TMDBMatcher
from .analyser import raw_parser, torrent_parser, TMDBMatcher
from module.conf import settings
from module.models import BangumiData
from module.models import BangumiData, Config
logger = logging.getLogger(__name__)
LANGUAGE = settings.rss_parser.language
class TitleParser:
def __init__(self):
self._download_parser = DownloadParser()
self._tmdb_parser = TMDBMatcher()
def download_parser(
self,
download_raw: str,
folder_name: str | None = None,
@staticmethod
def torrent_parser(
method: str,
torrent_name: str,
bangumi_name: str | None = None,
season: int | None = None,
suffix: str | None = None,
method: str = settings.bangumi_manage.rename_method
):
return self._download_parser.download_rename(download_raw, folder_name, season, suffix, method)
def tmdb_parser(self, title: str, season: int):
return torrent_parser(torrent_name, bangumi_name, season, suffix, method)
def tmdb_parser(self, title: str, season: int, language: str):
official_title, tmdb_season = None, None
try:
tmdb_info = self._tmdb_parser.tmdb_search(title)
@@ -35,15 +31,21 @@ class TitleParser:
logger.warning(f"{title} can not Matched with TMDB")
logger.info("Please change the bangumi info in webui")
return title, season
if LANGUAGE == "zh":
if language == "zh":
official_title = f"{tmdb_info.title_zh} ({tmdb_info.year_number})"
elif LANGUAGE == "jp":
elif language == "jp":
official_title = f"{tmdb_info.title_jp} ({tmdb_info.year_number})"
tmdb_season = tmdb_info.last_season if tmdb_info.last_season else season
official_title = official_title if official_title else title
return official_title, tmdb_season
def raw_parser(self, raw: str, _id: int | None = None) -> BangumiData:
def raw_parser(
self,
raw: str,
settings: Config,
_id: int | None = None
) -> BangumiData:
language = settings.rss_parser.language
try:
episode = raw_parser(raw)
titles = {
@@ -54,9 +56,13 @@ class TitleParser:
title_search = episode.title_zh if episode.title_zh else episode.title_en
title_raw = episode.title_en if episode.title_en else episode.title_zh
if settings.rss_parser.enable_tmdb:
official_title, _season = self.tmdb_parser(title_search, episode.season)
official_title, _season = self.tmdb_parser(
title_search,
episode.season,
language
)
else:
official_title = titles[LANGUAGE] if titles[LANGUAGE] else titles["zh"]
official_title = titles[language] if titles[language] else titles["zh"]
_season = episode.season
data = BangumiData(
id=_id,

View File

@@ -1 +1 @@
from .rss_analyser import RSSAnalyser
from .rss_analyser import RSSAnalyser

View File

@@ -4,14 +4,15 @@ import logging
from module.network import RequestContent
from module.parser import TitleParser
from module.core import DownloadClient
from module.models import BangumiData
from module.models import BangumiData, Config
logger = logging.getLogger(__name__)
class RSSAnalyser:
def __init__(self):
def __init__(self, settings: Config):
self._title_analyser = TitleParser()
self.settings = settings
@staticmethod
def find_id(bangumi_info: list[BangumiData]) -> int:
@@ -37,26 +38,31 @@ class RSSAnalyser:
break
if extra_add:
_id += 1
data = self._title_analyser.raw_parser(raw_title, _id)
data = self._title_analyser.raw_parser(
raw=raw_title,
_id=_id,
settings=self.settings)
if data is not None and data.official_title not in bangumi_info:
bangumi_info.append(data)
return bangumi_info
def rss_to_data(self, url, filter: bool = True) -> BangumiData:
def rss_to_data(self, url, _filter: bool = True) -> BangumiData:
with RequestContent() as req:
rss_torrents = req.get_torrents(url, filter)
rss_torrents = req.get_torrents(url, _filter)
for torrent in rss_torrents:
try:
data = self._title_analyser.raw_parser(torrent.name)
data = self._title_analyser.raw_parser(
torrent.name,
settings=self.settings
)
return data
except Exception as e:
logger.debug(e)
def run(self, bangumi_info: list[BangumiData], download_client: DownloadClient, rss_link: str):
def run(self, bangumi_info: list[BangumiData], rss_link: str):
logger.info("Start collecting RSS info.")
try:
self.rss_to_datas(bangumi_info, rss_link)
download_client.add_rules(bangumi_info, rss_link=rss_link)
except Exception as e:
logger.debug(e)
logger.info("Finished")

View File

@@ -0,0 +1,14 @@
from module.models import Config
from module.core.api_func import RSSAnalyser
def test_rss_analyser():
settings = Config()
rss_analyser = RSSAnalyser(settings)
url = "https://mikanani.me/RSS/Bangumi?bangumiId=2966&subgroupid=552"
data = rss_analyser.rss_to_data(url=url)
assert data.title_raw == "Yamada-kun to Lv999 no Koi wo Suru"
assert data.official_title == "和山田谈场 Lv999 的恋爱"
assert data.season == 1

View File

@@ -0,0 +1,26 @@
from module.parser.analyser import torrent_parser
def test_torrent_parser():
file_name = "[Lilith-Raws] Boku no Kokoro no Yabai Yatsu - 01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4].mp4"
folder_name = "我内心的糟糕念头(2023)"
season = 1
suffix = ".mp4"
assert torrent_parser(file_name, folder_name, season, suffix, "pn") == "Boku no Kokoro no Yabai Yatsu S01E01.mp4"
assert torrent_parser(file_name, folder_name, season, suffix, "advance") == "我内心的糟糕念头(2023) S01E01.mp4"
assert torrent_parser(file_name, folder_name, season, suffix, "none") == "[Lilith-Raws] Boku no Kokoro no Yabai Yatsu - 01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4].mp4"
file_name = "[Sakurato] Tonikaku Kawaii S2 [01][AVC-8bit 1080p AAC][CHS].mp4"
folder_name = "总之就是非常可爱(2021)"
season = 2
suffix = ".mp4"
assert torrent_parser(file_name, folder_name, season, suffix, "pn") == "Tonikaku Kawaii S02E01.mp4"
assert torrent_parser(file_name, folder_name, season, suffix, "advance") == "总之就是非常可爱(2021) S02E01.mp4"
file_name = "[SweetSub&LoliHouse] Heavenly Delusion - 01 [WebRip 1080p HEVC-10bit AAC ASSx2].mkv"
folder_name = "天国大魔境(2023)"
season = 1
suffix = ".mkv"
assert torrent_parser(file_name, folder_name, season, suffix, "pn") == "Heavenly Delusion S01E01.mkv"
assert torrent_parser(file_name, folder_name, season, suffix, "advance") == "天国大魔境(2023) S01E01.mkv"