refactor(string): split utilities by responsibility

This commit is contained in:
jxxghp
2026-08-15 08:03:55 +08:00
parent a2117bafc8
commit 96118e73e7
99 changed files with 1608 additions and 1424 deletions
+10 -8
View File
@@ -39,7 +39,9 @@ from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, Torren
ChainEventType
from app.adapters.network.http import RequestUtils
from app.schemas.media import build_media_key, resolve_media_identity
from app.domain.string import StringUtils
from app.domain import episode as episode_rules
from app.foundation import size as size_tools
from app.foundation import text as text_tools
from app.adapters.system.host import SystemUtils
@@ -138,7 +140,7 @@ class DownloadChain(ChainBase):
).apply_path_context(file_path)
track_identity: Union[int, str, None] = file_meta.track_number
if track_identity is None:
track_identity = StringUtils.clear_upper(file_meta.title or file_path.stem)
track_identity = text_tools.normalize_upper(file_meta.title or file_path.stem)
if track_identity in (None, ""):
return None
return file_meta.disc_number or 1, track_identity
@@ -554,7 +556,7 @@ class DownloadChain(ChainBase):
return meta.episode
episode_list = getattr(meta, "episode_list", None)
if episode_list:
return StringUtils.format_ep(list(episode_list))
return episode_rules.format_ranges(list(episode_list))
return None
@staticmethod
@@ -669,7 +671,7 @@ class DownloadChain(ChainBase):
media_source=media_source,
media_id=media_id,
seasons=getattr(meta, "season", None),
episodes=StringUtils.format_ep(list(episodes)) if episodes else self._format_failure_episodes(meta),
episodes=episode_rules.format_ranges(list(episodes)) if episodes else self._format_failure_episodes(meta),
site=site if isinstance(site, int) else None,
site_name=getattr(torrent, "site_name", None),
torrent_id=self._torrent_resource_key(torrent),
@@ -916,7 +918,7 @@ class DownloadChain(ChainBase):
return (None, str(err)) if return_detail else None
# 实际下载的集数
download_episodes = StringUtils.format_ep(list(episodes)) if episodes else None
download_episodes = episode_rules.format_ranges(list(episodes)) if episodes else None
if episodes is not None:
context.selected_episodes = sorted(set(episodes))
elif _meta and _meta.episode_list:
@@ -1427,12 +1429,12 @@ class DownloadChain(ChainBase):
if complete_coverage_matched:
logger.info(
f"{meta.org_string} 解析文件集数已完整覆盖目标范围:"
f"{StringUtils.format_ep(sorted(required_episodes))}")
f"{episode_rules.format_ranges(sorted(required_episodes))}")
if required_episodes and not complete_coverage_matched:
missing_episodes = sorted(required_episodes.difference(torrent_episodes_set))
logger.info(
f"{meta.org_string} 解析文件集数未覆盖目标范围,"
f"缺少 {StringUtils.format_ep(missing_episodes)},先放弃这个种子")
f"缺少 {episode_rules.format_ranges(missing_episodes)},先放弃这个种子")
continue
if not required_episodes and need_total and len(torrent_episodes) < need_total:
logger.info(
@@ -1858,7 +1860,7 @@ class DownloadChain(ChainBase):
index = 1
for torrent in torrents:
messages.append(f"{index}. {torrent.title} "
f"{StringUtils.str_filesize(torrent.size)} "
f"{size_tools.format_compact_size(torrent.size)} "
f"{round(torrent.progress, 1)}%")
index += 1
self.post_message(Notification(
+3 -3
View File
@@ -41,7 +41,7 @@ from app.domain.media import is_music_media_source
from app.schemas.media import normalize_media_source, resolve_media_identity
from app.foundation.singleton import Singleton
from app.foundation.text import convert as zhconv_convert
from app.domain.string import StringUtils
from app.domain import title as title_rules
recognize_lock = Lock()
@@ -1412,7 +1412,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
"""
# 提取要素
mtype, key_word, season_num, episode_num, year, content = (
StringUtils.get_keyword(title)
title_rules.parse_search_keyword(title)
)
# 识别
meta = MetaInfo(content)
@@ -1889,7 +1889,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
"""
# 提取要素
mtype, key_word, season_num, episode_num, year, content = (
StringUtils.get_keyword(title)
title_rules.parse_search_keyword(title)
)
# 识别
meta = MetaInfo(content)
+6 -4
View File
@@ -43,7 +43,9 @@ from app.schemas.system import TransferDirectoryConf
from app.schemas.types import EventType, MessageChannel, MediaType
from app.adapters.network.http import RequestUtils
from app.schemas.media import build_media_key, resolve_media_identity
from app.domain.string import StringUtils
from app.domain import episode as episode_rules
from app.domain import title as title_rules
from app.foundation import url as url_tools
class MessageChain(ChainBase):
@@ -2562,9 +2564,9 @@ class MediaInteractionChain(ChainBase):
return "ReSubscribe", re.sub(r"洗版[:\s]*", "", text)
if text.startswith("搜索") or text.startswith("下载"):
return "ReSearch", re.sub(r"(搜索|下载)[:\s]*", "", text)
if StringUtils.is_link(text):
if url_tools.is_link(text):
return None, text
if not StringUtils.is_media_title_like(text):
if not title_rules.is_media_title_like(text):
return None, text
return "Search", text
@@ -3683,7 +3685,7 @@ class MediaInteractionChain(ChainBase):
season_map = no_exists.get(mediakey) or {}
if show_missing_only:
return [
f"{sea} 季缺失 {StringUtils.str_series(no_exist.episodes) if no_exist.episodes else no_exist.total_episode}"
f"{sea} 季缺失 {episode_rules.compact_numbers(no_exist.episodes) if no_exist.episodes else no_exist.total_episode}"
for sea, no_exist in season_map.items()
]
return [
+1 -1
View File
@@ -47,7 +47,7 @@ from app.domain.media import is_music_media_source
from app.schemas.media import normalize_media_source, resolve_media_identity
from app.runtime.reload import ConfigReloadMixin
from app.foundation.singleton import Singleton
from app.domain.string import StringUtils
from app.chain.media import MediaChain
+2 -2
View File
@@ -36,7 +36,7 @@ from app.schemas.types import (
SystemConfigKey,
)
from app.schemas.media import build_media_key, parse_media_key, resolve_media_identity
from app.domain.string import StringUtils
from app.foundation import size as size_tools
from app.foundation.text import convert as zhconv_convert
@@ -466,7 +466,7 @@ class SearchChain(ChainBase):
"index": index,
"title": torrent.torrent_info.title or "未知",
"size": (
StringUtils.format_size(torrent.torrent_info.size)
size_tools.format_size(torrent.torrent_info.size)
if torrent.torrent_info.size
else "0 B"
),
+21 -18
View File
@@ -32,7 +32,10 @@ from app.schemas import MessageChannel, Notification, SiteUserData
from app.schemas.types import EventType, NotificationType
from app.adapters.network.http import RequestUtils
from app.domain.site import SiteUtils
from app.domain.string import StringUtils
from app.domain import site as site_rules
from app.foundation import size as size_tools
from app.foundation import url as url_tools
from app.foundation.dom import DomUtils
site_interaction_manager = SlashInteractionManager()
@@ -71,7 +74,7 @@ class SiteChain(ChainBase):
"""
userdata: SiteUserData = self.run_module("refresh_userdata", site=site)
if userdata:
SiteOper().update_userdata(domain=StringUtils.get_url_domain(site.get("domain")),
SiteOper().update_userdata(domain=site_rules.extract_domain(site.get("domain")),
name=site.get("name"),
payload=userdata.model_dump())
# 发送事件
@@ -229,7 +232,7 @@ class SiteChain(ChainBase):
判断站点是否已经登陆m-team
"""
user_agent = site.ua or settings.USER_AGENT
domain = StringUtils.get_url_domain(site.url)
domain = site_rules.extract_domain(site.url)
url = f"https://api.{domain}/api/member/profile"
headers = {
"User-Agent": user_agent,
@@ -352,7 +355,7 @@ class SiteChain(ChainBase):
"""
判断站点是否已经登陆rousi
"""
url = f"https://{StringUtils.get_url_domain(site.url)}/api/v1/profile"
url = f"https://{site_rules.extract_domain(site.url)}/api/v1/profile"
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
@@ -391,7 +394,7 @@ class SiteChain(ChainBase):
return favicon_url, None
html = etree.HTML(html_text)
try:
if StringUtils.is_valid_html_element(html):
if DomUtils.has_child_elements(html):
fav_link = html.xpath('//head/link[contains(@rel, "icon")]/@href')
if fav_link:
favicon_url = urljoin(url, fav_link[0])
@@ -422,10 +425,10 @@ class SiteChain(ChainBase):
"""
根据主域名获取索引器地址
"""
if StringUtils.get_url_domain(inx.get("domain")) == sub_domain:
if site_rules.extract_domain(inx.get("domain")) == sub_domain:
return inx.get("domain")
for ext_d in inx.get("ext_domains", []):
if StringUtils.get_url_domain(ext_d) == sub_domain:
if site_rules.extract_domain(ext_d) == sub_domain:
return ext_d
return sub_domain
@@ -496,7 +499,7 @@ class SiteChain(ChainBase):
_update_count += 1
elif indexer:
if settings.COOKIECLOUD_BLACKLIST and any(
StringUtils.get_url_domain(domain) == StringUtils.get_url_domain(black_domain) for black_domain
site_rules.extract_domain(domain) == site_rules.extract_domain(black_domain) for black_domain
in str(settings.COOKIECLOUD_BLACKLIST).split(",")):
logger.warn(f"站点 {domain} 已在黑名单中,不添加站点")
continue
@@ -600,7 +603,7 @@ class SiteChain(ChainBase):
if not domain:
return
if str(domain).startswith("http"):
domain = StringUtils.get_url_domain(domain)
domain = site_rules.extract_domain(domain)
# 站点信息
siteoper = SiteOper()
siteshelper = SitesHelper()
@@ -642,7 +645,7 @@ class SiteChain(ChainBase):
if not domain:
return
# 获取主域名中间那段
domain_host = StringUtils.get_url_host(domain)
domain_host = url_tools.host_label(domain)
# 查询以"site.domain_host"开头的配置项,并清除
systemconfig = SystemConfigOper()
site_keys = systemconfig.all().keys()
@@ -664,7 +667,7 @@ class SiteChain(ChainBase):
if not domain:
return
if str(domain).startswith("http"):
domain = StringUtils.get_url_domain(domain)
domain = site_rules.extract_domain(domain)
indexer = SitesHelper().get_indexer(domain)
if not indexer:
return
@@ -678,7 +681,7 @@ class SiteChain(ChainBase):
:return: (是否可用, 错误信息)
"""
# 检查域名是否可用
domain = StringUtils.get_url_domain(url)
domain = site_rules.extract_domain(url)
siteoper = SiteOper()
site_info = siteoper.get_by_domain(domain)
if not site_info:
@@ -1188,7 +1191,7 @@ class SiteChain(ChainBase):
"启用" if site.is_active else "禁用",
"已配置" if site.cookie else "未配置",
"" if site.render else "",
site.domain or StringUtils.get_url_domain(site.url or ""),
site.domain or site_rules.extract_domain(site.url or ""),
]
for site in site_list
]
@@ -1203,7 +1206,7 @@ class SiteChain(ChainBase):
f"{site.id}. {site.name} | 状态:{'启用' if site.is_active else '禁用'}"
f" | Cookie{'已配置' if site.cookie else '未配置'}"
f" | 渲染:{'' if site.render else ''}"
f" | 域名:{site.domain or StringUtils.get_url_domain(site.url or '')}"
f" | 域名:{site.domain or site_rules.extract_domain(site.url or '')}"
)
return "\n".join(lines)
@@ -1506,15 +1509,15 @@ class SiteChain(ChainBase):
incDownloads += download
messages[upload + (rand / 1000)] = (
f"{site}{updated_date}\n"
+ f"上传量:{StringUtils.str_filesize(upload)}\n"
+ f"下载量:{StringUtils.str_filesize(download)}\n"
+ f"上传量:{size_tools.format_compact_size(upload)}\n"
+ f"下载量:{size_tools.format_compact_size(download)}\n"
+ "————————————"
)
if incDownloads or incUploads:
sorted_messages = [messages[key] for key in sorted(messages.keys(), reverse=True)]
sorted_messages.insert(0, f"【汇总】\n"
f"总上传:{StringUtils.str_filesize(incUploads)}\n"
f"总下载:{StringUtils.str_filesize(incDownloads)}\n"
f"总上传:{size_tools.format_compact_size(incUploads)}\n"
f"总下载:{size_tools.format_compact_size(incDownloads)}\n"
f"————————————")
self.post_message(Notification(
channel=channel,
+4 -3
View File
@@ -20,7 +20,8 @@ from app.runtime.log import logger
from app.schemas import Notification
from app.schemas.types import SystemConfigKey, MessageChannel, NotificationType, MediaType
from app.schemas.media import resolve_media_identity
from app.domain.string import StringUtils
from app.domain import site as site_rules
from app.foundation import text as text_tools
class TorrentsChain(ChainBase):
@@ -355,7 +356,7 @@ class TorrentsChain(ChainBase):
"""
归一标题用于低置信标题兜底匹配
"""
return (StringUtils.clear_upper(value or "") or "").strip()
return (text_tools.normalize_upper(value or "") or "").strip()
def clear_torrents(self):
"""
@@ -603,7 +604,7 @@ class TorrentsChain(ChainBase):
"current": indexer.get("id"),
},
)
domain = StringUtils.get_url_domain(indexer.get("domain"))
domain = site_rules.extract_domain(indexer.get("domain"))
domains.append(domain)
if stype == "spider":
# 刷新首页种子
+10 -9
View File
@@ -68,7 +68,8 @@ from app.application.transfer import TransferQueue, TransferTask
from app.domain.media import normalize_music_type
from app.schemas.media import normalize_media_source, resolve_media_identity
from app.foundation.singleton import Singleton
from app.domain.string import StringUtils
from app.domain import episode as episode_rules
from app.foundation import text as text_tools
from app.adapters.system.host import SystemUtils
# 下载器锁
@@ -169,16 +170,16 @@ class JobManager:
return "music", source, media_id, music_type
artists = tuple(
StringUtils.clear_upper(artist)
text_tools.normalize_upper(artist)
for artist in (getattr(media, "artists", None) or [])
if StringUtils.clear_upper(artist)
if text_tools.normalize_upper(artist)
)
if music_type == MUSIC_ENTITY_ALBUM:
album_artist = StringUtils.clear_upper(
album_artist = text_tools.normalize_upper(
getattr(media, "album_artist", None)
or (artists[0] if artists else "")
)
album = StringUtils.clear_upper(
album = text_tools.normalize_upper(
getattr(media, "album", None) or getattr(media, "title", None) or ""
)
return "music", "local", music_type, album_artist, album, getattr(media, "year", None)
@@ -188,8 +189,8 @@ class JobManager:
"local",
music_type,
artists,
StringUtils.clear_upper(getattr(media, "title", None) or ""),
StringUtils.clear_upper(getattr(media, "album", None) or ""),
text_tools.normalize_upper(getattr(media, "title", None) or ""),
text_tools.normalize_upper(getattr(media, "album", None) or ""),
getattr(media, "disc_number", None),
getattr(media, "track_number", None),
)
@@ -1420,7 +1421,7 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
task.mediainfo, task.meta.begin_season
)
if season_episodes:
se_str = f"{task.meta.season} {StringUtils.format_ep(season_episodes)}"
se_str = f"{task.meta.season} {episode_rules.format_ranges(season_episodes)}"
else:
se_str = f"{task.meta.season}"
# 发送入库成功消息
@@ -3242,7 +3243,7 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
return False
if source_meta.type != target_meta.type:
return False
if StringUtils.clear_upper(source_meta.name) != StringUtils.clear_upper(
if text_tools.normalize_upper(source_meta.name) != text_tools.normalize_upper(
target_meta.name
):
return False