- 区分 Consts 和 Settings,取代 Env
- 独立 downloader module,为便于后续提供多下载器支持
- 加入命令行选项,提供 debug 模式
- 其他细节
This commit is contained in:
Sean
2022-06-02 15:13:05 +08:00
parent e2ca5ede76
commit 2e3e75bb0b
24 changed files with 455 additions and 306 deletions

14
.vscode/launch.json vendored
View File

@@ -4,11 +4,23 @@
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: 当前文件",
"type": "python",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}/AutoBangumi/app",
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Python: docker_main",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/AutoBangumi/app/docker_main.py",
"program": "${workspaceFolder}/AutoBangumi/app/app.py",
"args": [
"-d"
],
"cwd": "${workspaceFolder}/AutoBangumi/app",
"console": "integratedTerminal",
"justMyCode": true

View File

@@ -18,4 +18,4 @@ ENV RULE_DEBUG=False
COPY ./app /app
COPY ./config /config
CMD [ "python3", "/app/docker_main.py"]
CMD [ "python3", "/app/app.py"]

View File

@@ -2,13 +2,11 @@ import re
import json
import zhconv
import logging
from RssFilter.fliter_base import *
from fliter_base import *
logger = logging.getLogger(__name__)
handler = logging.FileHandler(
filename="RssFilter/rename_log.txt",
mode="w",
encoding="utf-8"
filename="RssFilter/rename_log.txt", mode="w", encoding="utf-8"
)
handler.setFormatter(
logging.Formatter(
@@ -18,36 +16,8 @@ handler.setFormatter(
logger.level = logging.WARNING
logger.addHandler(handler)
class RSSInfoCleaner:
class Name:
raw = None
conv = None
zh = None
en = None
jp = None
clean = None
class Info:
group = None
season = None
episode = None
vision = None
class Tag:
dpi = None
ass = None
lang = None
type = None
code = None
source = None
def __init__(self, file_name):
self.file_name = file_name
self.Name.raw = file_name # 接收文件名参数
self.clean() # 清理广告等杂质
# 匹配特征等
self.group_character = [
const = {
"group_character":[
"字幕社",
"字幕组",
"字幕屋",
@@ -78,8 +48,8 @@ class RSSInfoCleaner:
"百合组",
"慕留人",
"行动组",
]
self.group_char = [
],
"group_char" : [
"dmhy",
"澄空学园",
"c.c动漫",
@@ -109,25 +79,62 @@ class RSSInfoCleaner:
"cxraw",
"witex.io",
]
with open("../config/clean_rule.json", encoding="utf-8") as file_obj:
}
const["all_charactor"] = const["group_character"] + const["group_char"]
class RSSInfoCleaner:
class Name:
def __init__(self) -> None:
self.raw = None
self.conv = None
self.zh = None
self.en = None
self.jp = None
self.clean = None
class Info:
def __init__(self) -> None:
self.group = None
self.season = None
self.episode = None
self.vision = None
class Tag:
def __init__(self) -> None:
self.dpi = None
self.ass = None
self.lang = None
self.type = None
self.code = None
self.source = None
def __init__(self, file_name):
self.name = RSSInfoCleaner.Name()
self.info = RSSInfoCleaner.Info()
self.tag = RSSInfoCleaner.Tag()
self.file_name = file_name
self.name.raw = file_name # 接收文件名参数
self.clean() # 清理广告等杂质
# 匹配特征等
with open("RssFilter/clean_rule.json", encoding="utf-8") as file_obj:
rule_json = json.load(file_obj)[0]["group_name"]
self.group_rule = [zhconv.convert(x, "zh-cn") for x in rule_json]
const["group_rule"] = [zhconv.convert(x, "zh-cn") for x in rule_json]
self.file_info = {}
self.pre_analyse = None
# 匹配字幕组特征
self.recognize_group()
self.Info.group = self.get_group()
self.Tag.dpi = self.get_dpi()
self.Info.season = self.get_season()
self.Info.episode = self.get_episode()
self.Info.vision = self.get_vision()
self.Tag.lang = self.get_language()
self.Tag.ass = self.get_ass()
self.Tag.type = self.get_type()
self.Tag.code = self.get_code()
self.Tag.source = self.get_source()
self.Name.clean = self.get_clean_name()
self.info.group = self.get_group()
self.tag.dpi = self.get_dpi()
self.info.season = self.get_season()
self.info.episode = self.get_episode()
self.info.vision = self.get_vision()
self.tag.lang = self.get_language()
self.tag.ass = self.get_ass()
self.tag.type = self.get_type()
self.tag.code = self.get_code()
self.tag.source = self.get_source()
self.name.clean = self.get_clean_name()
self.zh_list = []
self.jp_list = []
self.en_list = []
@@ -136,7 +143,7 @@ class RSSInfoCleaner:
# 清理原链接(中文字符替换为英文)
def clean(self):
file_name = zhconv.convert(self.Name.raw, "zh-cn")
file_name = zhconv.convert(self.name.raw, "zh-cn")
# 去广告
file_name = re.sub(
"[(\[【]?(字幕)?[\u4e00-\u9fa5、]{0,3}(新人|招募?新?)[\u4e00-\u9fa5、]{0,8}[)\]】]?",
@@ -198,7 +205,7 @@ class RSSInfoCleaner:
f_res.group(1), "%s/" % f_res.group(1).strip(".")
)
self.Name.raw = (
self.name.raw = (
str(file_name)
.replace("", ":")
.replace("", "[")
@@ -215,11 +222,9 @@ class RSSInfoCleaner:
# 检索字幕组特征
def recognize_group(self):
character = self.group_character
group = self.group_char
rule = self.group_rule
rule = const["group_rule"]
# 字幕组(特例)特征优先级大于通用特征
character = group + character
character = const["all_charactor"]
# !强规则,人工录入标准名,区分大小写,优先匹配
for char in rule:
if ("&" + char) in self.file_name or (char + "&") in self.file_name:
@@ -237,8 +242,8 @@ class RSSInfoCleaner:
self.pre_analyse = char.lower()
return "enforce"
# 如果文件名以 [字幕组名] 开头
if self.Name.raw[0] == "[":
str_split = self.Name.raw.lower().split("]")
if self.name.raw[0] == "[":
str_split = self.name.raw.lower().split("]")
# 检索特征值是否位于文件名第1、2、最后一段
for char in character:
if (
@@ -259,16 +264,16 @@ class RSSInfoCleaner:
self.pre_analyse = None
return False
# 文件名以 -字幕组名 结尾
elif "-" in self.Name.raw:
elif "-" in self.name.raw:
for char in character:
if char in self.Name.raw.lower().split("-")[-1]:
self.pre_analyse = self.Name.raw.lower().split("-")[-1]
if char in self.name.raw.lower().split("-")[-1]:
self.pre_analyse = self.name.raw.lower().split("-")[-1]
return "reserve"
self.pre_analyse = None
return False
# 文件名以空格分隔 字幕组名为第一段
else:
first_str = self.Name.raw.lower().split(" ")[0]
first_str = self.name.raw.lower().split(" ")[0]
for char in character:
if char in first_str:
self.pre_analyse = first_str
@@ -289,16 +294,16 @@ class RSSInfoCleaner:
# 大部分情况
elif status == "success":
# 如果是 [字幕组名] 这么标准的格式直接else送走吧剩下的匹配一下
if "[%s]" % res_char not in self.Name.raw.lower():
if self.Name.raw[0] == "[":
if "[%s]" % res_char not in self.name.raw.lower():
if self.name.raw[0] == "[":
try:
# 以特征值为中心,匹配最近的中括号,八成就这个了
gp = get_gp(res_char, self.Name.raw.lower())
gp = get_gp(res_char, self.name.raw.lower())
return gp
except Exception as e:
logger.warning(
"bug -- res_char:%s,%s,%s"
% (res_char, self.Name.raw.lower(), e)
% (res_char, self.name.raw.lower(), e)
)
else:
return res_char
@@ -307,7 +312,7 @@ class RSSInfoCleaner:
# 扒了6W数据硬找的参数没啥说的
def get_dpi(self):
file_name = self.Name.raw
file_name = self.name.raw
dpi_list = [
"4k",
"2160p",
@@ -356,7 +361,7 @@ class RSSInfoCleaner:
# 获取语种
def get_language(self):
file_name = self.Name.raw
file_name = self.name.raw
lang = []
# 中文标示
try:
@@ -394,7 +399,7 @@ class RSSInfoCleaner:
# 文件种类
def get_type(self):
file_name = self.Name.raw
file_name = self.name.raw
type_list = []
# 英文标示
try:
@@ -415,7 +420,7 @@ class RSSInfoCleaner:
# 编码格式
def get_code(self):
file_name = self.Name.raw
file_name = self.name.raw
code = []
# 视频编码
try:
@@ -447,7 +452,7 @@ class RSSInfoCleaner:
# 来源
def get_source(self):
file_name = str(self.Name.raw).lower()
file_name = str(self.name.raw).lower()
type_list = []
# 英文标示
for _ in range(3):
@@ -474,7 +479,7 @@ class RSSInfoCleaner:
# 获取季度
def get_season(self):
file_name = self.Name.raw.lower()
file_name = self.name.raw.lower()
season = []
# 中文标示
try:
@@ -504,7 +509,7 @@ class RSSInfoCleaner:
# 获取集数
def get_episode(self):
file_name = self.Name.raw.lower()
file_name = self.name.raw.lower()
episode = []
# _集国漫
try:
@@ -562,7 +567,7 @@ class RSSInfoCleaner:
# 获取版本
def get_vision(self):
file_name = self.Name.raw.lower()
file_name = self.name.raw.lower()
vision = []
# 中文
try:
@@ -596,7 +601,7 @@ class RSSInfoCleaner:
# 获取字幕类型
def get_ass(self):
file_name = self.Name.raw.lower()
file_name = self.name.raw.lower()
ass = []
# 中文标示
try:
@@ -639,7 +644,7 @@ class RSSInfoCleaner:
elif has_zh(k_i) and has_en(k_i):
# 如果还是同时包含中英文的情况,递龟一下
if " " not in k_i:
res = re.search(k_i, self.Name.raw.lower())
res = re.search(k_i, self.name.raw.lower())
if res is not None:
zh_list.append(res.group())
else:
@@ -658,7 +663,7 @@ class RSSInfoCleaner:
elif has_en(k_i) is False:
zh_list.append(k_i.strip(" "))
elif has_zh(k_i) and has_en(k_i):
res = re.search(k_i, self.Name.raw.lower())
res = re.search(k_i, self.name.raw.lower())
if res is not None:
zh_list.append(res.group())
@@ -678,19 +683,19 @@ class RSSInfoCleaner:
def get_clean_name(self):
# 获取到的信息
info = {
"group": self.Info.group,
"dpi": self.Tag.dpi,
"season": self.Info.season,
"episode": self.Info.episode,
"vision": self.Info.vision,
"lang": self.Tag.lang,
"ass": self.Tag.ass,
"type": self.Tag.type,
"code": self.Tag.code,
"source": self.Tag.source,
"group": self.info.group,
"dpi": self.tag.dpi,
"season": self.info.season,
"episode": self.info.episode,
"vision": self.info.vision,
"lang": self.tag.lang,
"ass": self.tag.ass,
"type": self.tag.type,
"code": self.tag.code,
"source": self.tag.source,
}
# 字母全部小写
clean_name = self.Name.raw.lower()
clean_name = self.name.raw.lower()
# 去除拿到的有效信息
for k, v in info.items():
@@ -743,108 +748,108 @@ class RSSInfoCleaner:
# 提取标题
def get_title(self):
self.Name.zh, self.Name.en, self.Name.jp = None, None, None
self.name.zh, self.name.en, self.name.jp = None, None, None
# 国漫筛选
if "国漫" in self.Name.raw:
if "国漫" in self.name.raw:
zh = re.search(
"-?([\u4e00-\u9fa5]{2,10})_?", self.Name.raw.replace("[国漫]", "")
"-?([\u4e00-\u9fa5]{2,10})_?", self.name.raw.replace("[国漫]", "")
)
if zh is not None:
self.Name.zh = clean_list([zh.group()])
self.name.zh = clean_list([zh.group()])
return
if "/" not in self.Name.clean:
if has_jp(self.Name.clean) is False:
if has_zh(self.Name.clean) is False:
en = re.search(self.Name.clean, self.Name.raw.lower())
if "/" not in self.name.clean:
if has_jp(self.name.clean) is False:
if has_zh(self.name.clean) is False:
en = re.search(self.name.clean, self.name.raw.lower())
if en is not None:
self.Name.en = clean_list([en.group()])
self.name.en = clean_list([en.group()])
return
elif (
re.search(
"(^[\u4e00-\u9fa5\u3040-\u31ff\d:\-·??、.。,!]{1,20}[a-z\d]{,3} ??)([a-z\d:\-.。,! ]* ?)",
self.Name.clean,
self.name.clean,
)
is not None
):
res = re.search(
"(^[\u4e00-\u9fa5\u3040-\u31ff\d:\-·??、.。,!]{1,20}[a-z\d]{,3} ??)[._&]?([a-z\d:\-.。,! ]* ?)",
self.Name.clean,
self.name.clean,
)
zh = res.group(1)
en = res.group(2)
zh = re.search(zh, self.Name.raw.lower())
zh = re.search(zh, self.name.raw.lower())
if zh is not None:
self.Name.zh = clean_list([zh.group()])
en = re.search(en, self.Name.raw.lower())
self.name.zh = clean_list([zh.group()])
en = re.search(en, self.name.raw.lower())
if en is not None:
self.Name.en = clean_list([en.group()])
self.name.en = clean_list([en.group()])
return
# 英中
elif (
re.search(
"(^([a-z\d:\-_.。,! ]* ?) ?)[._&]?([\u4e00-\u9fa5\u3040-\u31ffa-z\d:\-_·??、.。,! ]{1,20})",
self.Name.clean,
self.name.clean,
)
is not None
):
res = re.search(
"(^([a-z\d:\-_.。,! ]* ?) ?)[._&]?([\u4e00-\u9fa5\u3040-\u31ffa-z\d:\-_·??、.。,! ]{1,20})",
self.Name.clean,
self.name.clean,
)
zh = res.group(3)
en = res.group(1)
zh = re.search(zh, self.Name.raw.lower())
zh = re.search(zh, self.name.raw.lower())
if zh is not None:
self.Name.zh = clean_list([zh.group()])
en = re.search(en, self.Name.raw.lower())
self.name.zh = clean_list([zh.group()])
en = re.search(en, self.name.raw.lower())
if en is not None:
self.Name.en = clean_list([en.group()])
self.name.en = clean_list([en.group()])
return
elif len(re.findall("[a-zA-Z]", self.Name.clean.lower())) < 10:
zh = re.search(self.Name.clean, self.Name.raw.lower())
elif len(re.findall("[a-zA-Z]", self.name.clean.lower())) < 10:
zh = re.search(self.name.clean, self.name.raw.lower())
if zh is not None:
self.Name.zh = clean_list([zh.group()])
self.name.zh = clean_list([zh.group()])
return
if debug > 0:
print("初筛:\r\n%s\r\n%s\r\n%s" % (self.zh_list, self.en_list, self.jp_list))
if (has_zh(self.Name.clean) or has_jp(self.Name.clean)) and has_en(
self.Name.clean
if (has_zh(self.name.clean) or has_jp(self.name.clean)) and has_en(
self.name.clean
):
self.Name.clean = add_separator(self.Name.clean)
self.easy_split(self.Name.clean, self.zh_list, self.en_list, self.jp_list)
self.name.clean = add_separator(self.name.clean)
self.easy_split(self.name.clean, self.zh_list, self.en_list, self.jp_list)
if debug > 0:
print("二筛:\r\n%s\r\n%s\r\n%s" % (self.zh_list, self.en_list, self.jp_list))
# 结果反代入原名验证
self.all_verity([self.Name.raw, self.Name.clean])
self.all_verity([self.name.raw, self.name.clean])
# 去除正确结果后,重新识别其他部分
if self.jp_list:
temp_name = del_rules(self.Name.clean, self.jp_list)
temp_name = del_rules(self.name.clean, self.jp_list)
self.easy_split(temp_name, self.zh_list, self.en_list, self.jp_list)
if self.zh_list and self.en_list == []:
temp_name = del_rules(self.Name.clean, self.zh_list)
temp_name = del_rules(self.name.clean, self.zh_list)
self.easy_split(temp_name, self.zh_list, self.en_list, self.jp_list)
elif self.zh_list == [] and self.en_list:
temp_name = del_rules(self.Name.clean, self.en_list)
temp_name = del_rules(self.name.clean, self.en_list)
self.easy_split(temp_name, self.zh_list, self.en_list, self.jp_list)
while "" in self.en_list:
self.en_list.remove("")
if debug > 0:
print("三筛:\r\n%s\r\n%s\r\n%s" % (self.zh_list, self.en_list, self.jp_list))
# 一步一验
self.all_verity([self.Name.raw, self.Name.clean])
self.all_verity([self.name.raw, self.name.clean])
for _ in range(5):
# 拼合碎片
splicing(self.zh_list, self.zh_list, self.Name.clean)
splicing(self.en_list, self.en_list, self.Name.clean)
splicing(self.jp_list, self.jp_list, self.Name.clean)
splicing(self.zh_list, self.zh_list, self.name.clean)
splicing(self.en_list, self.en_list, self.name.clean)
splicing(self.jp_list, self.jp_list, self.name.clean)
try:
# 拼合中英文碎片
for i in self.en_list:
for j in self.zh_list:
res = re.search("%s +%s" % (i, j), self.Name.raw.lower())
res = re.search("%s +%s" % (i, j), self.name.raw.lower())
if res is not None:
self.en_list.remove(i)
self.zh_list.append(res.group())
@@ -853,16 +858,16 @@ class RSSInfoCleaner:
if debug > 0:
print("拼合:\r\n%s\r\n%s\r\n%s" % (self.zh_list, self.en_list, self.jp_list))
# 再次验证这里只能验raw名
self.all_verity(self.Name.raw)
self.all_verity(self.name.raw)
# 灌装
self.Name.zh = clean_list(self.zh_list)
self.name.zh = clean_list(self.zh_list)
bug_list = ["不白吃话山海经"]
for i in bug_list:
if i in self.Name.raw.lower():
if i in self.name.raw.lower():
if has_zh(i):
self.Name.zh = [i]
self.Name.en = clean_list(self.en_list)
self.Name.jp = clean_list(self.jp_list)
self.name.zh = [i]
self.name.en = clean_list(self.en_list)
self.name.jp = clean_list(self.jp_list)
if __name__ == "__main__":
@@ -873,7 +878,8 @@ if __name__ == "__main__":
row = 1 if debug else 200
name_list = read_data("mikan", num, row)
for i in range(0, len(name_list)):
title = RSSInfoCleaner(name_list[i]).Name
info = RSSInfoCleaner(name_list[i])
title = info.Name
print("%s:%s" % (num + i, name_list[i]))
print("raw_name:%s" % title.raw)
print("clean_name:%s" % title.clean)

View File

View File

View File

@@ -1,35 +1,30 @@
import os
import time
import json
import logging
from collect_info import CollectRSS
from set_rule import SetRule
from rename_qb import qBittorrentRename
from env import EnvInfo
def setup_logger():
DATE_FORMAT = "%Y-%m-%d %X"
LOGGING_FORMAT = "%(asctime)s %(levelname)s: %(message)s"
logging.basicConfig(
level=logging.DEBUG,
datefmt=DATE_FORMAT,
format=LOGGING_FORMAT,
encoding="utf-8",
)
from conf import settings
from argument_parser import parse
from log import setup_logger
from utils import json_config
def create_data_file():
if not os.path.exists(EnvInfo.info_path):
if not os.path.exists(settings.info_path):
bangumi_info = {"rss_link": "", "bangumi_info": []}
with open(EnvInfo.info_path, "w") as i:
json.dump(
bangumi_info, i, indent=4, separators=(",", ": "), ensure_ascii=False
)
json_config.save(settings.info_path, bangumi_info)
if __name__ == "__main__":
args = parse()
if args.debug:
from const_dev import DEV_SETTINGS
settings.init(DEV_SETTINGS)
else:
settings.init()
setup_logger()
create_data_file()
SetRule().rss_feed()
@@ -37,4 +32,4 @@ if __name__ == "__main__":
CollectRSS().run()
SetRule().run()
qBittorrentRename().run()
time.sleep(EnvInfo.sleep_time)
time.sleep(settings.sleep_time)

View File

@@ -0,0 +1,15 @@
import argparse
def parse():
parser = argparse.ArgumentParser(
prog="Auto Bangumi",
description="""
本项目是基于 Mikan Project、qBittorrent 的全自动追番整理下载工具。
只需要在 Mikan Project 上订阅番剧,就可以全自动追番。
并且整理完成的名称和目录可以直接被 Plex、Jellyfin 等媒体库软件识别,
无需二次刮削。""",
)
parser.add_argument("-d", "--debug",action="store_true", help="debug mode")
return parser.parse_args()

View File

@@ -5,8 +5,10 @@ import requests
from bs4 import BeautifulSoup
import json
import re
from env import EnvInfo, BColors
from RSSFilter import RSSInfoCleaner as Filter
from conf import settings
from utils import json_config
# from RssFilter.RSSFilter import RSSInfoCleaner as Filter
logger = logging.getLogger(__name__)
@@ -23,32 +25,27 @@ class MatchRule:
class CollectRSS:
def __init__(self):
self.bangumi_list = []
with open(EnvInfo.rule_path, encoding="utf-8") as r:
self.rules = json.load(r)
self.rules = json_config.load(settings.rule_path)
try:
self.rules = requests.get(EnvInfo.rule_url).json()
with open(EnvInfo.rule_path, "w", encoding="utf-8") as f:
json.dump(
self.rules, f, indent=4, separators=(",", ": "), ensure_ascii=False
)
except:
with open(EnvInfo.rule_path, encoding="utf-8") as r:
self.rules = json.load(r)
self.rules = requests.get(settings.rule_url).json()
except Exception as e:
logger.exception(e)
json_config.save(settings.rule_path, self.rules)
try:
rss = requests.get(EnvInfo.rss_link, "utf-8")
except:
logger.debug("ERROR with DNS/Connection.")
rss = requests.get(settings.rss_link, "utf-8")
except Exception as e:
logger.exception(e)
logger.error("ERROR with DNS/Connection.")
quit()
soup = BeautifulSoup(rss.text, "xml")
self.items = soup.find_all("item")
with open(EnvInfo.info_path, encoding="utf-8") as i:
self.info = json.load(i)
self.info = json_config.load(settings.info_path)
def get_info_list(self):
for item in self.items:
name = item.title.string
# debug 用
if EnvInfo.get_rule_debug:
if settings.get_rule_debug:
logger.debug(f"Raw {name}")
exit_flag = False
for rule in self.rules:
@@ -64,8 +61,7 @@ class CollectRSS:
bangumi_title = n[rule["name_position"]].strip()
except IndexError:
continue
sub_title = re.sub(
MatchRule.sub_title, "", bangumi_title)
sub_title = re.sub(MatchRule.sub_title, "", bangumi_title)
b = re.split(r"\/|\_", sub_title)
while "" in b:
b.remove("")
@@ -78,8 +74,7 @@ class CollectRSS:
)
if match_obj is not None:
bangumi_title = match_obj.group(1).strip()
match_obj = re.match(
MatchRule.match_rule, bangumi_title, re.I)
match_obj = re.match(MatchRule.match_rule, bangumi_title, re.I)
if match_obj is not None:
bangumi_title = match_obj.group(2).strip()
if bangumi_title not in self.bangumi_list:
@@ -97,11 +92,11 @@ class CollectRSS:
def put_info_json(self):
had_data = []
if self.info["rss_link"] == EnvInfo.rss_link:
if self.info["rss_link"] == settings.rss_link:
for data in self.info["bangumi_info"]:
had_data.append(data["title"])
else:
self.info = {"rss_link": EnvInfo.rss_link, "bangumi_info": []}
self.info = {"rss_link": settings.rss_link, "bangumi_info": []}
for item in self.bangumi_list:
title = item["title"]
match_title_season = re.match(MatchRule.season_match, title, re.I)
@@ -134,10 +129,7 @@ class CollectRSS:
)
had_data.append(json_title)
logger.debug("add {json_title} {json_season}")
with open(EnvInfo.info_path, "w", encoding="utf8") as f:
json.dump(
self.info, f, indent=4, separators=(",", ": "), ensure_ascii=False
)
json_config.save(settings.info_path, self.info)
def run(self):
self.get_info_list()
@@ -145,14 +137,15 @@ class CollectRSS:
if __name__ == "__main__":
# rss = requests.get(EnvInfo.rss_link, 'utf-8')
# from const import BCOLORS
# rss = requests.get(settings.rss_link, 'utf-8')
# soup = BeautifulSoup(rss.text, 'xml')
# items = soup.find_all('item')
# for item in items:
# name = item.title.string
# pn = Filter(name).Name
# print(BColors.HEADER + name)
# print(BColors.OKGREEN + str(pn.zh))
# print(BCOLORS.HEADER + name)
# print(BCOLORS.OKGREEN + str(pn.zh))
# print(str(pn.en))
print(__file__)
print(os.path.dirname(__file__))

35
AutoBangumi/app/conf.py Normal file
View File

@@ -0,0 +1,35 @@
import os
import const
class Settings(dict):
def __getattr__(self, item):
return self.get(item)
def __setattr__(self, key, value):
self[key] = value
def init(self, args=None):
self.update(self._settings_from_env())
if args:
self.update(args)
def _val_from_env(self, env, attr):
"""Transforms env-strings to python."""
val = os.environ[env]
if isinstance(attr, tuple):
conv_func = attr[1]
val = conv_func(val)
return val
def _settings_from_env(self):
"""Loads settings from env."""
return {
attr: self._val_from_env(env, attr)
for env, attr in const.ENV_TO_ATTR.items()
if env in os.environ
}
settings = Settings(const.DEFAULT_SETTINGS)

51
AutoBangumi/app/const.py Normal file
View File

@@ -0,0 +1,51 @@
# -*- encoding: utf-8 -*-
from math import fabs
DEFAULT_SETTINGS = {
"host_ip": "localhost:8080",
"sleep_time": 1800,
"user_name": "admin",
"password": "adminadmin",
"rss_link": "https://mikanani.me/RSS/classic",
"download_path": "/downloads/Bangumi",
"method": "pn",
"enable_group_tag": True,
"info_path": "config/bangumi.json",
"rule_path": "config/rule.json",
"not_contain": "720",
"get_rule_debug": False,
"rule_url": "https://raw.githubusercontent.com/EstrellaXD/Bangumi_Auto_Collector/main/AutoBangumi/config/rule.json",
"rule_name_re": r"\:|\/|\.",
"connect_retry_interval": 5,
"enable_eps_complete": False,
}
ENV_TO_ATTR = {
"HOST": "host_ip",
"TIME": ("sleep_time", lambda e: float(e)),
"USER": "user_name",
"PASSWORD": "password",
"RSS": "rss_link",
"DOWNLOAD_PATH": "download_path",
"METHOD": "method",
"GROUP_TAG": ("enable_group_tag", lambda e: e.lower() in ("true", "1", "t")),
"NOT_CONTAIN": "not_contain",
"RULE_DEBUG": ("get_rule_debug", lambda e: e.lower() in ("true", "1", "t")),
"EP_COMPLETE": ("enable_eps_complete", lambda e: e.lower() in ("true", "1", "t")),
}
FULL_SEASON_SUPPORT_GROUP = ["Lilith-Raws"]
BCOLORS = {
"HEADER": "\033[95m",
"OKBLUE": "\033[94m",
"OKCYAN": "\033[96m",
"OKGREEN": "\033[92m",
"WARNING": "\033[93m",
"FAIL": "\033[91m",
"ENDC": "\033[0m",
"BOLD": "\033[1m",
"UNDERLINE": "\033[4m",
}

View File

@@ -0,0 +1,7 @@
DEV_SETTINGS = {
"host_ip": "localhost:8181",
"sleep_time": 10,
"info_path": "../config/bangumi.json",
"rule_path": "../config/rule.json",
"enable_eps_complete": True,
}

View File

@@ -0,0 +1,10 @@
from conf import settings
def getClient():
host=settings.host_ip
username=settings.user_name
password=settings.password
# TODO 多下载器支持
# 从 settings 里读取下载器名称,然后返回对应 Client
from downloader.qb_downloader import QbDownloader
return QbDownloader(host, username, password)

View File

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

View File

@@ -0,0 +1,60 @@
import logging
import time
from qbittorrentapi import Client, LoginFailed
from qbittorrentapi.exceptions import Conflict409Error
from tomlkit import item
from conf import settings
from downloader.exceptions import ConflictError
logger = logging.getLogger(__name__)
class QbDownloader:
def __init__(self, host, username, password):
self._client = Client(
host=host,
username=username,
password=password,
)
while True:
try:
self._client.auth_log_in()
break
except LoginFailed:
logger.warning(
f"Can't log in qBittorrent Server {host} by {username}, retry in {settings.connect_retry_interval}"
)
time.sleep(settings.connect_retry_interval)
def torrents_info(self, status_filter, category):
return self._client.torrents_info(status_filter, category)
def torrents_add(self, urls, save_path, category):
return self._client.torrents_add(
urls=urls,
save_path=save_path,
category=category,
)
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 rss_add_feed(self, url, item_path):
try:
self._client.rss_add_feed(url, item_path)
except Conflict409Error as e:
logger.exception(e)
raise ConflictError()
def rss_remove_item(self, item_path):
try:
self._client.rss_remove_item(item_path)
except Conflict409Error as e:
logger.exception(e)
raise ConflictError()
def rss_set_rule(self, rule_name, rule_def):
self._client.rss_set_rule(rule_name, rule_def)

View File

@@ -1,59 +0,0 @@
import os
import time
from datetime import datetime
class EnvInfo:
debug_mode = True
# Docker Env
if not debug_mode:
host_ip = os.environ["HOST"]
sleep_time = float(os.environ["TIME"])
user_name = os.environ["USER"]
password = os.environ["PASSWORD"]
rss_link = os.environ["RSS"]
download_path = os.environ["DOWNLOAD_PATH"]
method = os.environ["METHOD"]
enable_group_tag = os.getenv("GROUP_TAG", 'False').lower() in ('true', '1', 't')
info_path = "/config/bangumi.json"
rule_path = "/config/clean_rule.json"
not_contain = os.environ["NOT_CONTAIN"]
get_rule_debug = os.getenv("RULE_DEBUG", 'False').lower() in ('true', '1', 't')
enable_eps_complete = os.getenv("EP_COMPLETE", 'False').lower() in ('true', '1', 't')
else:
# Debug ENV
host_ip = "localhost:8181"
sleep_time = 10
user_name = "admin"
password = "adminadmin"
rss_link = "https://mikanani.me/RSS/classic"
download_path = "/downloads/Bangumi"
method = "pn"
enable_group_tag = True
info_path = "../config/bangumi.json"
rule_path = "../config/rule_beta.json"
not_contain = "720"
get_rule_debug = True
enable_eps_complete = True
# Static ENV
rule_url = "https://raw.githubusercontent.com/EstrellaXD/Bangumi_Auto_Collector/main/AutoBangumi/config/rule.json"
rule_name_re = r"\:|\/|\."
class BColors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
class Other:
full_season_support_group = [
"Lilith-Raws"
]

View File

@@ -1,10 +1,13 @@
import os.path
import requests
from qbittorrentapi import Client
from env import EnvInfo, Other
from bs4 import BeautifulSoup
import logging
from conf import settings
from const import FULL_SEASON_SUPPORT_GROUP
from downloader import getClient
logger = logging.getLogger(__name__)
@@ -14,6 +17,7 @@ class FullSeasonGet:
self.bangumi_name = bangumi_name
self.group = group
self.season = season
self.client = getClient()
def get_season_rss(self):
if self.season == "S01":
@@ -21,30 +25,23 @@ class FullSeasonGet:
else:
season = self.season
season = requests.get(
f"https://mikanani.me/RSS/Search?searchstr={self.group}+{self.bangumi_name}+{season}")
f"https://mikanani.me/RSS/Search?searchstr={self.group}+{self.bangumi_name}+{season}"
)
soup = BeautifulSoup(season.content, "xml")
self.torrents = soup.find_all("enclosure")
def add_torrents(self):
qb = Client(
host=EnvInfo.host_ip, username=EnvInfo.user_name, password=EnvInfo.password
)
try:
qb.auth_log_in()
except:
logger.error("Error")
for torrent in self.torrents:
qb.torrents_add(
self.client.torrents_add(
urls=torrent["url"],
save_path=str(
os.path.join(EnvInfo.download_path,
self.bangumi_name, self.season)
os.path.join(settings.download_path, self.bangumi_name, self.season)
),
category="Bangumi",
)
def run(self):
if self.group in Other.full_season_support_group:
if self.group in FULL_SEASON_SUPPORT_GROUP:
self.get_season_rss()
self.add_torrents()

14
AutoBangumi/app/log.py Normal file
View File

@@ -0,0 +1,14 @@
import logging
def setup_logger():
DATE_FORMAT = "%Y-%m-%d %X"
LOGGING_FORMAT = "%(asctime)s %(levelname)s: %(message)s"
logging.basicConfig(
level=logging.DEBUG,
datefmt=DATE_FORMAT,
format=LOGGING_FORMAT,
encoding="utf-8",
)
setup_logger()

View File

@@ -0,0 +1,4 @@
class Parser:
def parse(filename):
# TODO 番剧名称识别
pass

View File

@@ -2,21 +2,17 @@ import re
import qbittorrentapi
import logging
from env import EnvInfo
from downloader import getClient
from conf import settings
logger = logging.getLogger(__name__)
class qBittorrentRename:
def __init__(self):
self.qbt_client = qbittorrentapi.Client(
host=EnvInfo.host_ip, username=EnvInfo.user_name, password=EnvInfo.password
)
try:
self.qbt_client.auth_log_in()
except qbittorrentapi.LoginFailed as e:
logger.exception(e)
self.recent_info = self.qbt_client.torrents_info(
self.client = getClient()
self.recent_info = self.client.torrents_info(
status_filter="completed", category="Bangumi"
)
self.count = 0
@@ -55,7 +51,7 @@ class qBittorrentRename:
def rename_torrent_file(self, hash, path_name, new_name):
if path_name != new_name:
self.qbt_client.torrents_rename_file(
self.client.torrents_rename_file(
torrent_hash=hash, old_path=path_name, new_path=new_name
)
logger.debug(f"{path_name} >> {new_name}")
@@ -68,7 +64,7 @@ class qBittorrentRename:
def run(self):
method_dict = {"pn": self.rename_pn, "normal": self.rename_normal}
if EnvInfo.method not in method_dict:
if settings.method not in method_dict:
logger.error(f"error method")
else:
for i in range(0, self.torrent_count):
@@ -77,7 +73,7 @@ class qBittorrentRename:
name = info.name
hash = info.hash
path_name = info.content_path.split("/")[-1]
new_name = method_dict[EnvInfo.method](name)
new_name = method_dict[settings.method](name)
self.rename_torrent_file(hash, path_name, new_name)
except:
logger.warning(f"{name} rename fail")

View File

@@ -1,63 +1,64 @@
import re
import logging
from env import EnvInfo
import qbittorrentapi
import json
import os
from downloader import getClient
from downloader.exceptions import ConflictError
from conf import settings
from utils import json_config
logger = logging.getLogger(__name__)
class SetRule:
def __init__(self):
with open(EnvInfo.info_path, encoding="utf-8") as f:
self.info = json.load(f)
self.bangumi_info = self.info["bangumi_info"]
self.rss_link = EnvInfo.rss_link
self.host_ip = EnvInfo.host_ip
self.user_name = EnvInfo.user_name
self.password = EnvInfo.password
self.download_path = EnvInfo.download_path
self.qb = qbittorrentapi.Client(
host=self.host_ip, username=self.user_name, password=self.password)
try:
self.qb.auth_log_in()
except qbittorrentapi.LoginFailed as e:
logger.exception(e)
self.info = json_config.load(settings.info_path)
self.bangumi_info = self.info["bangumi_info"]
self.rss_link = settings.rss_link
self.download_path = settings.download_path
self.client = getClient()
def set_rule(self, bangumi_name, group, season):
rule = {
'enable': True,
'mustContain': bangumi_name,
'mustNotContain': EnvInfo.not_contain,
'useRegx': True,
'episodeFilter': '',
'smartFilter': False,
'previouslyMatchedEpisodes': [],
'affectedFeeds': [self.rss_link],
'ignoreDays': 0,
'lastMatch': '',
'addPaused': False,
'assignedCategory': 'Bangumi',
'savePath': str(os.path.join(EnvInfo.download_path, re.sub(EnvInfo.rule_name_re, " ", bangumi_name).strip(), season))
"enable": True,
"mustContain": bangumi_name,
"mustNotContain": settings.not_contain,
"useRegx": True,
"episodeFilter": "",
"smartFilter": False,
"previouslyMatchedEpisodes": [],
"affectedFeeds": [self.rss_link],
"ignoreDays": 0,
"lastMatch": "",
"addPaused": False,
"assignedCategory": "Bangumi",
"savePath": str(
os.path.join(
settings.download_path,
re.sub(settings.rule_name_re, " ", bangumi_name).strip(),
season,
)
),
}
if EnvInfo.enable_group_tag:
if settings.enable_group_tag:
rule_name = f"[{group}] {bangumi_name}"
else:
rule_name = bangumi_name
self.qb.rss_set_rule(rule_name=rule_name, rule_def=rule)
self.client.rss_set_rule(rule_name=rule_name, rule_def=rule)
def rss_feed(self):
try:
self.qb.rss_remove_item(item_path="Mikan_RSS")
except qbittorrentapi.exceptions.Conflict409Error:
self.client.rss_remove_item(item_path="Mikan_RSS")
except ConflictError:
logger.debug("No feed exists, starting adding feed.")
try:
self.qb.rss_add_feed(url=self.rss_link, item_path="Mikan_RSS")
self.client.rss_add_feed(url=self.rss_link, item_path="Mikan_RSS")
logger.debug("Successes adding RSS Feed.")
except ConnectionError:
logger.debug("Error with adding RSS Feed.")
except qbittorrentapi.exceptions.Conflict409Error:
except ConflictError:
logger.debug("RSS Already exists.")
def run(self):
@@ -66,9 +67,7 @@ class SetRule:
if not info["added"]:
self.set_rule(info["title"], info["group"], info["season"])
info["added"] = True
with open(EnvInfo.info_path, 'w', encoding='utf8') as f:
json.dump(self.info, f, indent=4, separators=(
',', ': '), ensure_ascii=False)
json_config.save(settings.info_path, self.info)
logger.debug("Finished.")

View File

View File

@@ -0,0 +1,12 @@
import json
def load(filename):
with open(filename, "r", encoding="utf-8") as f:
return json.load(f)
def save(filename, obj):
with open(filename, "w", encoding="utf8") as f:
json.dump(obj, f, indent=4, separators=(",", ": "), ensure_ascii=False)
pass

View File

@@ -56,4 +56,4 @@
],
"name_position": 3
}
]
]