From 2f7fcbc05681b2f6d5c13574adc607f6e0a87862 Mon Sep 17 00:00:00 2001 From: LuckyPuppy514 <122154133@qq.com> Date: Fri, 7 Apr 2023 23:11:37 +0800 Subject: [PATCH] =?UTF-8?q?v3.0.3=20=E6=A0=87=E9=A2=98=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../java/com/lckp/jproxy/Application.java | 58 +- .../com/lckp/jproxy/model/FormatResult.java | 30 + .../jproxy/service/ISonarrTitleService.java | 197 +++--- .../impl/SonarrExampleServiceImpl.java | 249 +++---- .../impl/SonarrIndexerServiceImpl.java | 306 ++++----- .../service/impl/SonarrTitleServiceImpl.java | 636 +++++++++--------- .../java/com/lckp/jproxy/util/FormatUtil.java | 309 ++++----- .../resources/database/changelog/v3.0.3.sql | 5 + .../resources/rule/radarr@LuckyPuppy514.json | 2 +- .../resources/rule/sonarr@LuckyPuppy514.json | 2 +- .../com/lckp/jproxy/ApplicationTests.java | 32 +- 12 files changed, 941 insertions(+), 887 deletions(-) create mode 100644 src/main/java/com/lckp/jproxy/model/FormatResult.java create mode 100644 src/main/resources/database/changelog/v3.0.3.sql diff --git a/pom.xml b/pom.xml index 1efa28a..b2207a2 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.lckp jproxy - 3.0.2 + 3.0.3 JProxy 介于 Sonarr/Radarr 和 Jackett/Prowlarr 之间的代理,主要用于优化查询和提升识别率 diff --git a/src/main/java/com/lckp/jproxy/Application.java b/src/main/java/com/lckp/jproxy/Application.java index 275d6f8..0e6e457 100644 --- a/src/main/java/com/lckp/jproxy/Application.java +++ b/src/main/java/com/lckp/jproxy/Application.java @@ -1,29 +1,29 @@ -package com.lckp.jproxy; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.ServletComponentScan; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.EnableAspectJAutoProxy; - -/** - * - *

- * 主类 - *

- * - * @author LuckyPuppy514 - * @date 2023-03-04 - */ -@SpringBootApplication -@MapperScan("com.lckp.jproxy.mapper") -@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) -@ServletComponentScan -@EnableCaching -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} +package com.lckp.jproxy; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * + *

+ * 主类 + *

+ * + * @author LuckyPuppy514 + * @date 2023-03-04 + */ +@SpringBootApplication +@MapperScan("com.lckp.jproxy.mapper") +@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +@ServletComponentScan +@EnableCaching +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/src/main/java/com/lckp/jproxy/model/FormatResult.java b/src/main/java/com/lckp/jproxy/model/FormatResult.java new file mode 100644 index 0000000..b3d1485 --- /dev/null +++ b/src/main/java/com/lckp/jproxy/model/FormatResult.java @@ -0,0 +1,30 @@ +package com.lckp.jproxy.model; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 格式化结果 + *

+ * + * @author LuckyPuppy514 + * @date 2023-04-07 + */ +@Getter +@Setter +public class FormatResult implements Serializable { + private static final long serialVersionUID = 1L; + + /* + * 格式化结果 + */ + private String formatText; + + /* + * 剩余信息 + */ + private String restText; +} diff --git a/src/main/java/com/lckp/jproxy/service/ISonarrTitleService.java b/src/main/java/com/lckp/jproxy/service/ISonarrTitleService.java index 58499ef..15a9ab5 100644 --- a/src/main/java/com/lckp/jproxy/service/ISonarrTitleService.java +++ b/src/main/java/com/lckp/jproxy/service/ISonarrTitleService.java @@ -1,98 +1,99 @@ -package com.lckp.jproxy.service; - -import java.util.List; -import java.util.Map; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.service.IService; -import com.lckp.jproxy.entity.SonarrRule; -import com.lckp.jproxy.entity.SonarrTitle; -import com.lckp.jproxy.model.request.SonarrTitleQueryRequest; - -/** - *

- * SonarrTitle 服务类 - *

- * - * @author LuckyPuppy514 - * @since 2023-03-19 - */ -public interface ISonarrTitleService extends IService { - /** - * - * 同步 - * - * boolean - */ - public boolean sync(); - - /** - * - * 查询需要同步 TMDB 标题的 tvdbId - * - * @return List - */ - public List queryNeedSyncTmdbTitle(); - - /** - * - * 根据标题查询 - * - * @param title - * @return SonarrTitle - */ - public SonarrTitle queryByTitle(String title); - - /** - * - * 查询所有标题 - * - * @return List - */ - public List queryAll(); - - /** - * - * 格式化标题 - * - * @param text - * @param format - * @param cleanTitleRegex - * @param sonarrRuleList - * @param sonarrTitleList - * @return String - */ - public String formatTitle(String text, String format, String cleanTitleRegex, - List sonarrRuleList, List sonarrTitleList); - - /** - * - * 格式化 - * - * @param text - * @param format - * @param tokenRuleMap - * @return String - */ - public String format(String text, String format, Map> tokenRuleMap); - - /** - * - * 格式化并缓存 - * - * @param text - * @param format - * @param tokenRuleMap - * @return String - */ - public String formatWithCache(String text, String format, Map> tokenRuleMap); - - /** - * - * 分页查询 - * - * @param request - * @return IPage - */ - public IPage query(SonarrTitleQueryRequest request); -} +package com.lckp.jproxy.service; + +import java.util.List; +import java.util.Map; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.lckp.jproxy.entity.SonarrRule; +import com.lckp.jproxy.entity.SonarrTitle; +import com.lckp.jproxy.model.FormatResult; +import com.lckp.jproxy.model.request.SonarrTitleQueryRequest; + +/** + *

+ * SonarrTitle 服务类 + *

+ * + * @author LuckyPuppy514 + * @since 2023-03-19 + */ +public interface ISonarrTitleService extends IService { + /** + * + * 同步 + * + * boolean + */ + public boolean sync(); + + /** + * + * 查询需要同步 TMDB 标题的 tvdbId + * + * @return List + */ + public List queryNeedSyncTmdbTitle(); + + /** + * + * 根据标题查询 + * + * @param title + * @return SonarrTitle + */ + public SonarrTitle queryByTitle(String title); + + /** + * + * 查询所有标题 + * + * @return List + */ + public List queryAll(); + + /** + * + * 格式化标题 + * + * @param text + * @param format + * @param cleanTitleRegex + * @param sonarrRuleList + * @param sonarrTitleList + * @return FormatResult + */ + public FormatResult formatTitle(String text, String format, String cleanTitleRegex, + List sonarrRuleList, List sonarrTitleList); + + /** + * + * 格式化 + * + * @param text + * @param format + * @param tokenRuleMap + * @return String + */ + public String format(String text, String format, Map> tokenRuleMap); + + /** + * + * 格式化并缓存 + * + * @param text + * @param format + * @param tokenRuleMap + * @return String + */ + public String formatWithCache(String text, String format, Map> tokenRuleMap); + + /** + * + * 分页查询 + * + * @param request + * @return IPage + */ + public IPage query(SonarrTitleQueryRequest request); +} diff --git a/src/main/java/com/lckp/jproxy/service/impl/SonarrExampleServiceImpl.java b/src/main/java/com/lckp/jproxy/service/impl/SonarrExampleServiceImpl.java index 69f89e5..b537caa 100644 --- a/src/main/java/com/lckp/jproxy/service/impl/SonarrExampleServiceImpl.java +++ b/src/main/java/com/lckp/jproxy/service/impl/SonarrExampleServiceImpl.java @@ -1,124 +1,125 @@ -package com.lckp.jproxy.service.impl; - -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.aop.framework.AopContext; -import org.springframework.context.MessageSource; -import org.springframework.stereotype.Service; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.lckp.jproxy.constant.Messages; -import com.lckp.jproxy.constant.SystemConfigKey; -import com.lckp.jproxy.constant.TableField; -import com.lckp.jproxy.constant.Token; -import com.lckp.jproxy.constant.ValidStatus; -import com.lckp.jproxy.entity.SonarrExample; -import com.lckp.jproxy.entity.SonarrRule; -import com.lckp.jproxy.entity.SonarrTitle; -import com.lckp.jproxy.mapper.SonarrExampleMapper; -import com.lckp.jproxy.model.request.SonarrExampleQueryRequest; -import com.lckp.jproxy.service.ISonarrExampleService; -import com.lckp.jproxy.service.ISonarrRuleService; -import com.lckp.jproxy.service.ISonarrTitleService; -import com.lckp.jproxy.service.ISystemConfigService; -import com.lckp.jproxy.util.FormatUtil; - -import lombok.RequiredArgsConstructor; - -/** - *

- * SonarrExample 服务实现类 - *

- * - * @author LuckyPuppy514 - * @since 2023-03-30 - */ -@Service -@RequiredArgsConstructor -public class SonarrExampleServiceImpl extends ServiceImpl - implements ISonarrExampleService { - - private final ISystemConfigService systemConfigService; - - private final ISonarrRuleService sonarrRuleService; - - private final ISonarrTitleService sonarrTitleService; - - private final MessageSource messageSource; - - private final ISonarrExampleService proxy() { - return (ISonarrExampleService) AopContext.currentProxy(); - } - - /** - * @param request - * @return - * @see com.lckp.jproxy.service.ISonarrExampleService#query(com.lckp.jproxy.model.request.SonarrExampleQueryRequest) - */ - @Override - public IPage query(SonarrExampleQueryRequest request, Locale locale) { - QueryWrapper wrapper = new QueryWrapper<>(); - if (StringUtils.isNotBlank(request.getOriginalText())) { - wrapper.like(TableField.ORIGINAL_TEXT, request.getOriginalText().trim()); - } - wrapper.orderByDesc(TableField.UPDATE_TIME); - Page result = request.mybatisPlusPage(); - List resultList = null; - if (request.getValidStatus() == null) { - result = page(request.mybatisPlusPage(), wrapper); - resultList = result.getRecords(); - } else { - resultList = proxy().list(wrapper); - } - if (!resultList.isEmpty()) { - String format = systemConfigService.queryValueByKey(SystemConfigKey.SONARR_INDEXER_FORMAT); - String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); - Map> tokenRuleMap = new ConcurrentHashMap<>(); - Matcher matcher = Pattern.compile(Token.REGEX).matcher(format); - while (matcher.find()) { - String token = matcher.group(1); - tokenRuleMap.put(token, sonarrRuleService.query(token)); - } - List sonarrTitleList = sonarrTitleService.queryAll(); - for (SonarrExample sonarrExample : resultList) { - sonarrExample.setFormatText(sonarrExample.getOriginalText()); - sonarrExample.setValidStatus(ValidStatus.INVALID.getCode()); - String formatText = sonarrTitleService.formatTitle(sonarrExample.getOriginalText(), format, - cleanTitleRegex, tokenRuleMap.get(Token.TITLE), sonarrTitleList); - if (formatText.contains("{" + Token.TITLE + "}")) { - formatText = FormatUtil.replaceToken(Token.TITLE, - messageSource.getMessage(Messages.EXAMPLE_MATCH_TITLE_FAIL, null, locale), - formatText); - } - formatText = sonarrTitleService.format(sonarrExample.getOriginalText(), formatText, - tokenRuleMap); - sonarrExample.setFormatText(formatText); - if (!sonarrExample.getOriginalText().equals(sonarrExample.getFormatText())) { - sonarrExample.setValidStatus(ValidStatus.VALID.getCode()); - } - } - } - // 手动分页 - if (request.getValidStatus() != null && !resultList.isEmpty()) { - resultList = resultList.stream() - .filter(example -> request.getValidStatus().equals(example.getValidStatus())).toList(); - result.setTotal(resultList.size()); - result.setPages((long) Math.ceil(result.getTotal() / 1.0 / result.getSize())); - int si = (int) ((result.getCurrent() - 1) * result.getSize()); - int ei = (int) (si + result.getSize()); - ei = ei <= resultList.size() ? ei : resultList.size(); - result.setRecords((resultList.subList(si, ei))); - } - return result; - } - -} +package com.lckp.jproxy.service.impl; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.aop.framework.AopContext; +import org.springframework.context.MessageSource; +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.lckp.jproxy.constant.Messages; +import com.lckp.jproxy.constant.SystemConfigKey; +import com.lckp.jproxy.constant.TableField; +import com.lckp.jproxy.constant.Token; +import com.lckp.jproxy.constant.ValidStatus; +import com.lckp.jproxy.entity.SonarrExample; +import com.lckp.jproxy.entity.SonarrRule; +import com.lckp.jproxy.entity.SonarrTitle; +import com.lckp.jproxy.mapper.SonarrExampleMapper; +import com.lckp.jproxy.model.FormatResult; +import com.lckp.jproxy.model.request.SonarrExampleQueryRequest; +import com.lckp.jproxy.service.ISonarrExampleService; +import com.lckp.jproxy.service.ISonarrRuleService; +import com.lckp.jproxy.service.ISonarrTitleService; +import com.lckp.jproxy.service.ISystemConfigService; +import com.lckp.jproxy.util.FormatUtil; + +import lombok.RequiredArgsConstructor; + +/** + *

+ * SonarrExample 服务实现类 + *

+ * + * @author LuckyPuppy514 + * @since 2023-03-30 + */ +@Service +@RequiredArgsConstructor +public class SonarrExampleServiceImpl extends ServiceImpl + implements ISonarrExampleService { + + private final ISystemConfigService systemConfigService; + + private final ISonarrRuleService sonarrRuleService; + + private final ISonarrTitleService sonarrTitleService; + + private final MessageSource messageSource; + + private final ISonarrExampleService proxy() { + return (ISonarrExampleService) AopContext.currentProxy(); + } + + /** + * @param request + * @return + * @see com.lckp.jproxy.service.ISonarrExampleService#query(com.lckp.jproxy.model.request.SonarrExampleQueryRequest) + */ + @Override + public IPage query(SonarrExampleQueryRequest request, Locale locale) { + QueryWrapper wrapper = new QueryWrapper<>(); + if (StringUtils.isNotBlank(request.getOriginalText())) { + wrapper.like(TableField.ORIGINAL_TEXT, request.getOriginalText().trim()); + } + wrapper.orderByDesc(TableField.UPDATE_TIME); + Page result = request.mybatisPlusPage(); + List resultList = null; + if (request.getValidStatus() == null) { + result = page(request.mybatisPlusPage(), wrapper); + resultList = result.getRecords(); + } else { + resultList = proxy().list(wrapper); + } + if (!resultList.isEmpty()) { + String format = systemConfigService.queryValueByKey(SystemConfigKey.SONARR_INDEXER_FORMAT); + String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); + Map> tokenRuleMap = new ConcurrentHashMap<>(); + Matcher matcher = Pattern.compile(Token.REGEX).matcher(format); + while (matcher.find()) { + String token = matcher.group(1); + tokenRuleMap.put(token, sonarrRuleService.query(token)); + } + List sonarrTitleList = sonarrTitleService.queryAll(); + for (SonarrExample sonarrExample : resultList) { + sonarrExample.setFormatText(sonarrExample.getOriginalText()); + sonarrExample.setValidStatus(ValidStatus.INVALID.getCode()); + FormatResult formatResult = sonarrTitleService.formatTitle(sonarrExample.getOriginalText(), + format, cleanTitleRegex, tokenRuleMap.get(Token.TITLE), sonarrTitleList); + String formatText = formatResult.getFormatText(); + if (formatText.contains("{" + Token.TITLE + "}")) { + formatText = FormatUtil.replaceToken(Token.TITLE, + messageSource.getMessage(Messages.EXAMPLE_MATCH_TITLE_FAIL, null, locale), + formatText); + } + formatText = sonarrTitleService.format(formatResult.getRestText(), formatText, tokenRuleMap); + sonarrExample.setFormatText(formatText); + if (!sonarrExample.getOriginalText().equals(sonarrExample.getFormatText())) { + sonarrExample.setValidStatus(ValidStatus.VALID.getCode()); + } + } + } + // 手动分页 + if (request.getValidStatus() != null && !resultList.isEmpty()) { + resultList = resultList.stream() + .filter(example -> request.getValidStatus().equals(example.getValidStatus())).toList(); + result.setTotal(resultList.size()); + result.setPages((long) Math.ceil(result.getTotal() / 1.0 / result.getSize())); + int si = (int) ((result.getCurrent() - 1) * result.getSize()); + int ei = (int) (si + result.getSize()); + ei = ei <= resultList.size() ? ei : resultList.size(); + result.setRecords((resultList.subList(si, ei))); + } + return result; + } + +} diff --git a/src/main/java/com/lckp/jproxy/service/impl/SonarrIndexerServiceImpl.java b/src/main/java/com/lckp/jproxy/service/impl/SonarrIndexerServiceImpl.java index b3b968c..c2b14b1 100644 --- a/src/main/java/com/lckp/jproxy/service/impl/SonarrIndexerServiceImpl.java +++ b/src/main/java/com/lckp/jproxy/service/impl/SonarrIndexerServiceImpl.java @@ -1,152 +1,154 @@ -package com.lckp.jproxy.service.impl; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.DocumentHelper; -import org.dom4j.Element; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; - -import com.lckp.jproxy.constant.ApiField; -import com.lckp.jproxy.constant.CacheName; -import com.lckp.jproxy.constant.SystemConfigKey; -import com.lckp.jproxy.constant.TableField; -import com.lckp.jproxy.constant.Token; -import com.lckp.jproxy.entity.SonarrRule; -import com.lckp.jproxy.entity.SonarrTitle; -import com.lckp.jproxy.entity.TmdbTitle; -import com.lckp.jproxy.service.ISonarrIndexerService; -import com.lckp.jproxy.service.ISonarrRuleService; -import com.lckp.jproxy.service.ISonarrTitleService; -import com.lckp.jproxy.service.ISystemConfigService; -import com.lckp.jproxy.service.ITmdbTitleService; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -/** - *

- * Sonarr 索引器服务实现类 - *

- * - * @author LuckyPuppy514 - * @date 2023-03-20 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class SonarrIndexerServiceImpl extends IndexerServiceImpl implements ISonarrIndexerService { - - protected final ISonarrTitleService sonarrTitleService; - - protected final ISonarrRuleService sonarrRuleService; - - protected final ITmdbTitleService tmdbTitleService; - - protected final ISystemConfigService systemConfigService; - - /** - * - * @param title - * @return - * @see com.lckp.jproxy.service.impl.IndexerServiceImpl#getSearchTitle(java.lang.String) - */ - @Override - @Cacheable(cacheNames = CacheName.SONARR_SEARCH_TITLE, key = "#title") - public List getSearchTitle(String title) { - List searchTitleList = new ArrayList<>(); - SonarrTitle sonarrTitle = sonarrTitleService.queryByTitle(title); - if (sonarrTitle == null) { - sonarrTitle = syncAndGetSonarrTitle(title); - } - searchTitleList.add(sonarrTitle.getTitle()); - // 主标题追加 TMDB 标题 - if (Integer.valueOf(0).equals(sonarrTitle.getSno())) { - List tmdbTitleList = tmdbTitleService.query() - .eq(TableField.TVDB_ID, sonarrTitle.getTvdbId()).list(); - for (TmdbTitle tmdbTitle : tmdbTitleList) { - searchTitleList.add(tmdbTitle.getTitle()); - } - } - return searchTitleList; - } - - /** - * @param xml - * @return - * @see com.lckp.jproxy.service.ISonarrIndexerService#executeFormatRule(java.lang.String) - */ - @Override - public String executeFormatRule(String xml) { - if (StringUtils.isBlank(xml) || !xml.contains("<" + ApiField.INDEXER_ITEM + ">")) { - return xml; - } - try { - String format = systemConfigService.queryValueByKey(SystemConfigKey.SONARR_INDEXER_FORMAT); - Map> tokenRuleMap = new ConcurrentHashMap<>(); - Matcher matcher = Pattern.compile(Token.REGEX).matcher(format); - while (matcher.find()) { - String token = matcher.group(1); - tokenRuleMap.put(token, sonarrRuleService.query(token)); - } - if (!tokenRuleMap.containsKey(Token.TITLE)) { - return xml; - } - String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); - List sonarrTitleList = sonarrTitleService.queryAll(); - Document document = DocumentHelper.parseText(xml); - Element root = document.getRootElement(); - Element channel = root.element(ApiField.INDEXER_CHANNEL); - for (Iterator items = channel.elementIterator(ApiField.INDEXER_ITEM); items.hasNext();) { - Element item = items.next(); - Element titleElement = item.element(ApiField.INDEXER_TITLE); - String text = titleElement.getText(); - String newText = sonarrTitleService.formatTitle(text, format, cleanTitleRegex, - tokenRuleMap.get(Token.TITLE), sonarrTitleList); - if (newText.contains("{" + Token.TITLE + "}")) { - log.debug("索引器格式化失败:{} ==> 未匹配到标题", text); - continue; - } - newText = sonarrTitleService.format(text, newText, tokenRuleMap); - titleElement.setText(newText); - log.debug("索引器格式化:{} ==> {}", text, newText); - } - return document.asXML(); - } catch (DocumentException e) { - log.error("xml 解析出错:{}", e); - } - return xml; - } - - /** - * - * 同步后再次获取 Sonarr 标题 - * - * @param title - * @return SonarrTitle - */ - private synchronized SonarrTitle syncAndGetSonarrTitle(String title) { - SonarrTitle sonarrTitle = sonarrTitleService.queryByTitle(title); - if (sonarrTitle == null) { - sonarrTitleService.sync(); - sonarrTitle = sonarrTitleService.queryByTitle(title); - if (sonarrTitle == null) { - log.error("找不到匹配的标题:{}", title); - sonarrTitle = new SonarrTitle(); - sonarrTitle.setTitle(title); - } else { - tmdbTitleService.sync(sonarrTitleService.queryNeedSyncTmdbTitle()); - } - } - return sonarrTitle; - } -} +package com.lckp.jproxy.service.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import com.lckp.jproxy.constant.ApiField; +import com.lckp.jproxy.constant.CacheName; +import com.lckp.jproxy.constant.SystemConfigKey; +import com.lckp.jproxy.constant.TableField; +import com.lckp.jproxy.constant.Token; +import com.lckp.jproxy.entity.SonarrRule; +import com.lckp.jproxy.entity.SonarrTitle; +import com.lckp.jproxy.entity.TmdbTitle; +import com.lckp.jproxy.model.FormatResult; +import com.lckp.jproxy.service.ISonarrIndexerService; +import com.lckp.jproxy.service.ISonarrRuleService; +import com.lckp.jproxy.service.ISonarrTitleService; +import com.lckp.jproxy.service.ISystemConfigService; +import com.lckp.jproxy.service.ITmdbTitleService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * Sonarr 索引器服务实现类 + *

+ * + * @author LuckyPuppy514 + * @date 2023-03-20 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SonarrIndexerServiceImpl extends IndexerServiceImpl implements ISonarrIndexerService { + + protected final ISonarrTitleService sonarrTitleService; + + protected final ISonarrRuleService sonarrRuleService; + + protected final ITmdbTitleService tmdbTitleService; + + protected final ISystemConfigService systemConfigService; + + /** + * + * @param title + * @return + * @see com.lckp.jproxy.service.impl.IndexerServiceImpl#getSearchTitle(java.lang.String) + */ + @Override + @Cacheable(cacheNames = CacheName.SONARR_SEARCH_TITLE, key = "#title") + public List getSearchTitle(String title) { + List searchTitleList = new ArrayList<>(); + SonarrTitle sonarrTitle = sonarrTitleService.queryByTitle(title); + if (sonarrTitle == null) { + sonarrTitle = syncAndGetSonarrTitle(title); + } + searchTitleList.add(sonarrTitle.getTitle()); + // 主标题追加 TMDB 标题 + if (Integer.valueOf(0).equals(sonarrTitle.getSno())) { + List tmdbTitleList = tmdbTitleService.query() + .eq(TableField.TVDB_ID, sonarrTitle.getTvdbId()).list(); + for (TmdbTitle tmdbTitle : tmdbTitleList) { + searchTitleList.add(tmdbTitle.getTitle()); + } + } + return searchTitleList; + } + + /** + * @param xml + * @return + * @see com.lckp.jproxy.service.ISonarrIndexerService#executeFormatRule(java.lang.String) + */ + @Override + public String executeFormatRule(String xml) { + if (StringUtils.isBlank(xml) || !xml.contains("<" + ApiField.INDEXER_ITEM + ">")) { + return xml; + } + try { + String format = systemConfigService.queryValueByKey(SystemConfigKey.SONARR_INDEXER_FORMAT); + Map> tokenRuleMap = new ConcurrentHashMap<>(); + Matcher matcher = Pattern.compile(Token.REGEX).matcher(format); + while (matcher.find()) { + String token = matcher.group(1); + tokenRuleMap.put(token, sonarrRuleService.query(token)); + } + if (!tokenRuleMap.containsKey(Token.TITLE)) { + return xml; + } + String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); + List sonarrTitleList = sonarrTitleService.queryAll(); + Document document = DocumentHelper.parseText(xml); + Element root = document.getRootElement(); + Element channel = root.element(ApiField.INDEXER_CHANNEL); + for (Iterator items = channel.elementIterator(ApiField.INDEXER_ITEM); items.hasNext();) { + Element item = items.next(); + Element titleElement = item.element(ApiField.INDEXER_TITLE); + String text = titleElement.getText(); + FormatResult formatResult = sonarrTitleService.formatTitle(text, format, cleanTitleRegex, + tokenRuleMap.get(Token.TITLE), sonarrTitleList); + String formatText = formatResult.getFormatText(); + if (formatText.contains("{" + Token.TITLE + "}")) { + log.debug("索引器格式化失败:{} ==> 未匹配到标题", text); + continue; + } + formatText = sonarrTitleService.format(formatResult.getRestText(), formatText, tokenRuleMap); + titleElement.setText(formatText); + log.debug("索引器格式化:{} ==> {}", text, formatText); + } + return document.asXML(); + } catch (DocumentException e) { + log.error("xml 解析出错:{}", e); + } + return xml; + } + + /** + * + * 同步后再次获取 Sonarr 标题 + * + * @param title + * @return SonarrTitle + */ + private synchronized SonarrTitle syncAndGetSonarrTitle(String title) { + SonarrTitle sonarrTitle = sonarrTitleService.queryByTitle(title); + if (sonarrTitle == null) { + sonarrTitleService.sync(); + sonarrTitle = sonarrTitleService.queryByTitle(title); + if (sonarrTitle == null) { + log.error("找不到匹配的标题:{}", title); + sonarrTitle = new SonarrTitle(); + sonarrTitle.setTitle(title); + } else { + tmdbTitleService.sync(sonarrTitleService.queryNeedSyncTmdbTitle()); + } + } + return sonarrTitle; + } +} diff --git a/src/main/java/com/lckp/jproxy/service/impl/SonarrTitleServiceImpl.java b/src/main/java/com/lckp/jproxy/service/impl/SonarrTitleServiceImpl.java index 51ee35d..6e3efc4 100644 --- a/src/main/java/com/lckp/jproxy/service/impl/SonarrTitleServiceImpl.java +++ b/src/main/java/com/lckp/jproxy/service/impl/SonarrTitleServiceImpl.java @@ -1,315 +1,321 @@ -package com.lckp.jproxy.service.impl; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.aop.framework.AopContext; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestTemplate; - -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONArray; -import com.alibaba.fastjson2.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.lckp.jproxy.constant.ApiField; -import com.lckp.jproxy.constant.CacheName; -import com.lckp.jproxy.constant.Common; -import com.lckp.jproxy.constant.Monitored; -import com.lckp.jproxy.constant.SystemConfigKey; -import com.lckp.jproxy.constant.TableField; -import com.lckp.jproxy.constant.Token; -import com.lckp.jproxy.entity.SonarrRule; -import com.lckp.jproxy.entity.SonarrTitle; -import com.lckp.jproxy.mapper.SonarrTitleMapper; -import com.lckp.jproxy.model.request.SonarrTitleQueryRequest; -import com.lckp.jproxy.service.ISonarrTitleService; -import com.lckp.jproxy.service.ISystemConfigService; -import com.lckp.jproxy.util.FormatUtil; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -/** - *

- * SonarrTitle 服务实现类 - *

- * - * @author LuckyPuppy514 - * @since 2023-03-19 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class SonarrTitleServiceImpl extends ServiceImpl - implements ISonarrTitleService { - - private final ISystemConfigService systemConfigService; - - private final SonarrTitleMapper sonarrTitleMapper; - - private final RedisTemplate redisTemplate; - - private final RestTemplate restTemplate; - - @Value("${time.sync-interval}") - private long syncInterval; - - private final ISonarrTitleService proxy() { - return (ISonarrTitleService) AopContext.currentProxy(); - } - - /** - * - * @see com.lckp.jproxy.service.ISonarrTitleService#sync() - */ - @Override - @Transactional(rollbackFor = Exception.class) - @CacheEvict(cacheNames = { CacheName.SONARR_SEARCH_TITLE, CacheName.INDEXER_SEARCH_OFFSET, - CacheName.SONARR_RESULT_TITLE }, allEntries = true, condition = "#result == true") - public synchronized boolean sync() { - if (!Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheName.SONARR_TITLE_SYNC_INTERVAL, - 1, syncInterval, TimeUnit.MINUTES))) { - return false; - } - - // 请求 Sonarr 接口获取数据 - StringBuilder url = new StringBuilder( - systemConfigService.queryValueByKey(SystemConfigKey.SONARR_URL)); - url.append("/api/v3/series?" + ApiField.SONARR_APIKEY); - url.append("=" + systemConfigService.queryValueByKey(SystemConfigKey.SONARR_APIKEY)); - JSONArray jsonArray = JSON - .parseArray(restTemplate.getForEntity(url.toString(), String.class).getBody()); - String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); - List sonarrTitleList = new ArrayList<>(); - for (Object object1 : jsonArray) { - JSONObject jsonObject = (JSONObject) object1; - // 主标题 - int sno = 0; - Integer tvdbId = jsonObject.getInteger(ApiField.SONARR_TVDB_ID); - Integer id = generateSonarrTitleId(tvdbId, sno); - String mainTitle = jsonObject.getString(ApiField.SONARR_TITLE); - String title = mainTitle; - Integer monitored = Monitored.getByFlag(jsonObject.getBooleanValue(ApiField.SONARR_MONITORED)) - .getCode(); - SonarrTitle sonarrTitle = new SonarrTitle(); - sonarrTitle.setId(id); - sonarrTitle.setTvdbId(tvdbId); - sonarrTitle.setSno(sno++); - sonarrTitle.setMainTitle(mainTitle); - sonarrTitle.setTitle(title); - sonarrTitle.setCleanTitle(FormatUtil.cleanTitle(title, cleanTitleRegex)); - sonarrTitle.setSeasonNumber(-1); - sonarrTitle.setMonitored(monitored); - sonarrTitleList.add(sonarrTitle); - // 备选标题 - JSONArray alternateTitles = jsonObject.getJSONArray(ApiField.SONARR_ALTERNATE_TITLES); - for (Object object2 : alternateTitles) { - JSONObject alternateTitle = (JSONObject) object2; - id = generateSonarrTitleId(tvdbId, sno); - title = alternateTitle.getString(ApiField.SONARR_TITLE); - sonarrTitle = new SonarrTitle(); - sonarrTitle.setId(id); - sonarrTitle.setTvdbId(tvdbId); - sonarrTitle.setSno(sno++); - sonarrTitle.setMainTitle(mainTitle); - sonarrTitle.setTitle(title); - sonarrTitle.setCleanTitle(FormatUtil.cleanTitle(title, cleanTitleRegex)); - sonarrTitle.setSeasonNumber(alternateTitle.getInteger(ApiField.SONARR_SCENE_SEASON_NUMBER)); - sonarrTitle.setMonitored(monitored); - sonarrTitleList.add(sonarrTitle); - } - } - proxy().saveOrUpdateBatch(sonarrTitleList, Common.BATCH_SIZE); - return true; - } - - /** - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#queryNeedSyncTmdbTitle() - */ - @Override - public List queryNeedSyncTmdbTitle() { - return sonarrTitleMapper.selectNeedSyncTmdbTitle(); - } - - /** - * - * 生成 sonarrTitle 主键 id - * - * @param tvdbId - * @param sno - * @return Integer - */ - private Integer generateSonarrTitleId(Integer tvdbId, int sno) { - return Integer.parseInt(new StringBuilder(tvdbId.toString()).append(sno).toString()); - } - - /** - * @param title - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#queryByTitle(java.lang.String) - */ - @Override - public SonarrTitle queryByTitle(String title) { - String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); - String cleanTitle = FormatUtil.cleanTitle(title, cleanTitleRegex); - List sonarrTitleList = proxy().query().eq(TableField.CLEAN_TITLE, cleanTitle).list(); - SonarrTitle sonarrTitle = null; - if (!sonarrTitleList.isEmpty()) { - sonarrTitle = sonarrTitleList.get(0); - } else { - title = FormatUtil.removeSeason(title); - cleanTitle = FormatUtil.cleanTitle(title, cleanTitleRegex); - sonarrTitleList = proxy().query().eq(TableField.CLEAN_TITLE, cleanTitle).list(); - if (!sonarrTitleList.isEmpty()) { - sonarrTitle = sonarrTitleList.get(0); - } - } - if (sonarrTitle != null) { - sonarrTitle.setTitle(title); - } - return sonarrTitle; - } - - /** - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#queryAll() - */ - @Override - @Cacheable(cacheNames = CacheName.SONARR_RESULT_TITLE) - public List queryAll() { - return sonarrTitleMapper.selectSonarrTitleAndTmdbTitle(); - } - - /** - * - * @param text - * @param format - * @param cleanTitleRegex - * @param soanrrRuleList - * @param sonarrTitleList - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#formatTitle(java.lang.String, - * java.lang.String, java.lang.String, java.util.List, java.util.List) - */ - @Override - public String formatTitle(String text, String format, String cleanTitleRegex, - List sonarrRuleList, List sonarrTitleList) { - for (SonarrRule sonarrRule : sonarrRuleList) { - if (sonarrRule.getRegex().contains("{" + Token.CLEAN_TITLE + "}")) { - for (SonarrTitle sonarrTitle : sonarrTitleList) { - String cleanTitle = sonarrTitle.getCleanTitle() != null ? sonarrTitle.getCleanTitle() - : FormatUtil.cleanTitle(sonarrTitle.getTitle(), cleanTitleRegex); - String cleanText = FormatUtil.cleanTitle(text, cleanTitleRegex); - String regex = sonarrRule.getRegex().replace("{" + Token.CLEAN_TITLE + "}", cleanTitle); - if (cleanText.matches(regex)) { - format = FormatUtil.replaceToken(Token.TITLE, sonarrTitle.getMainTitle(), format); - Integer seasonNumber = sonarrTitle.getSeasonNumber(); - if (!Integer.valueOf(-1).equals(seasonNumber) - && !Integer.valueOf(1).equals(seasonNumber)) { - format = FormatUtil.replaceToken(Token.SEASON, "S" + seasonNumber, format); - } - break; - } - } - } else { - Matcher matcher = Pattern.compile(sonarrRule.getRegex()).matcher(text); - if (matcher.find()) { - try { - String value = matcher.replaceAll(sonarrRule.getReplacement()); - format = FormatUtil.replaceToken(Token.TITLE, value, format); - break; - } catch (Exception e) { - log.error("replaceAll 出错:{}\n{}", e.getMessage(), JSON.toJSONString(sonarrRule)); - } - } - } - } - return format.trim(); - } - - /** - * @param text - * @param format - * @param tokenRuleMap - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#format(java.lang.String, - * java.lang.String, java.util.Map) - */ - @Override - public String format(String text, String format, Map> tokenRuleMap) { - boolean episodeFinded = false; - Matcher matcher = Pattern.compile(Token.REGEX).matcher(format); - while (matcher.find()) { - String token = matcher.group(1); - List sonarrRuleList = tokenRuleMap.get(token); - for (SonarrRule sonarrRule : sonarrRuleList) { - Matcher tokenMatcher = Pattern.compile(sonarrRule.getRegex()).matcher(text); - if (tokenMatcher.find()) { - try { - String value = tokenMatcher.replaceAll(sonarrRule.getReplacement()); - format = FormatUtil.replaceToken(token, value, format, sonarrRule.getOffset()); - if (Token.EPISODE.equals(token)) { - episodeFinded = true; - } - break; - } catch (Exception e) { - log.error("replaceAll 出错:{}\n{}", e.getMessage(), JSON.toJSONString(sonarrRule)); - } - } - } - } - if (!episodeFinded && format.contains("{" + Token.EPISODE + "}")) { - return text; - } - matcher = Pattern.compile(Token.REGEX).matcher(format); - while (matcher.find()) { - format = FormatUtil.removeToken(matcher.group(1), format); - } - return format.trim(); - } - - /** - * @param text - * @param format - * @param tokenRuleMap - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#formatWithCache(java.lang.String, - * java.lang.String, java.util.Map) - */ - @Override - @Cacheable(cacheNames = CacheName.DOWNLOADER_FORMAT_NAME, key = "#text") - public String formatWithCache(String text, String format, Map> tokenRuleMap) { - return format(text, format, tokenRuleMap); - } - - /** - * @param request - * @return - * @see com.lckp.jproxy.service.ISonarrTitleService#query(com.lckp.jproxy.model.request.SonarrTitleQueryRequest) - */ - @Override - public IPage query(SonarrTitleQueryRequest request) { - QueryWrapper wrapper = new QueryWrapper<>(); - if (StringUtils.isNotBlank(request.getTitle())) { - wrapper.like(TableField.TITLE, request.getTitle().trim()); - } - if (request.getTvdbId() != null) { - wrapper.eq(TableField.TVDB_ID, request.getTvdbId()); - } - wrapper.orderByDesc(TableField.UPDATE_TIME); - return proxy().page(request.mybatisPlusPage(), wrapper); - } -} +package com.lckp.jproxy.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.lckp.jproxy.constant.ApiField; +import com.lckp.jproxy.constant.CacheName; +import com.lckp.jproxy.constant.Common; +import com.lckp.jproxy.constant.Monitored; +import com.lckp.jproxy.constant.SystemConfigKey; +import com.lckp.jproxy.constant.TableField; +import com.lckp.jproxy.constant.Token; +import com.lckp.jproxy.entity.SonarrRule; +import com.lckp.jproxy.entity.SonarrTitle; +import com.lckp.jproxy.mapper.SonarrTitleMapper; +import com.lckp.jproxy.model.FormatResult; +import com.lckp.jproxy.model.request.SonarrTitleQueryRequest; +import com.lckp.jproxy.service.ISonarrTitleService; +import com.lckp.jproxy.service.ISystemConfigService; +import com.lckp.jproxy.util.FormatUtil; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * SonarrTitle 服务实现类 + *

+ * + * @author LuckyPuppy514 + * @since 2023-03-19 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SonarrTitleServiceImpl extends ServiceImpl + implements ISonarrTitleService { + + private final ISystemConfigService systemConfigService; + + private final SonarrTitleMapper sonarrTitleMapper; + + private final RedisTemplate redisTemplate; + + private final RestTemplate restTemplate; + + @Value("${time.sync-interval}") + private long syncInterval; + + private final ISonarrTitleService proxy() { + return (ISonarrTitleService) AopContext.currentProxy(); + } + + /** + * + * @see com.lckp.jproxy.service.ISonarrTitleService#sync() + */ + @Override + @Transactional(rollbackFor = Exception.class) + @CacheEvict(cacheNames = { CacheName.SONARR_SEARCH_TITLE, CacheName.INDEXER_SEARCH_OFFSET, + CacheName.SONARR_RESULT_TITLE }, allEntries = true, condition = "#result == true") + public synchronized boolean sync() { + if (!Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheName.SONARR_TITLE_SYNC_INTERVAL, + 1, syncInterval, TimeUnit.MINUTES))) { + return false; + } + + // 请求 Sonarr 接口获取数据 + StringBuilder url = new StringBuilder( + systemConfigService.queryValueByKey(SystemConfigKey.SONARR_URL)); + url.append("/api/v3/series?" + ApiField.SONARR_APIKEY); + url.append("=" + systemConfigService.queryValueByKey(SystemConfigKey.SONARR_APIKEY)); + JSONArray jsonArray = JSON + .parseArray(restTemplate.getForEntity(url.toString(), String.class).getBody()); + String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); + List sonarrTitleList = new ArrayList<>(); + for (Object object1 : jsonArray) { + JSONObject jsonObject = (JSONObject) object1; + // 主标题 + int sno = 0; + Integer tvdbId = jsonObject.getInteger(ApiField.SONARR_TVDB_ID); + Integer id = generateSonarrTitleId(tvdbId, sno); + String mainTitle = jsonObject.getString(ApiField.SONARR_TITLE); + String title = mainTitle; + Integer monitored = Monitored.getByFlag(jsonObject.getBooleanValue(ApiField.SONARR_MONITORED)) + .getCode(); + SonarrTitle sonarrTitle = new SonarrTitle(); + sonarrTitle.setId(id); + sonarrTitle.setTvdbId(tvdbId); + sonarrTitle.setSno(sno++); + sonarrTitle.setMainTitle(mainTitle); + sonarrTitle.setTitle(title); + sonarrTitle.setCleanTitle(FormatUtil.cleanTitle(title, cleanTitleRegex)); + sonarrTitle.setSeasonNumber(-1); + sonarrTitle.setMonitored(monitored); + sonarrTitleList.add(sonarrTitle); + // 备选标题 + JSONArray alternateTitles = jsonObject.getJSONArray(ApiField.SONARR_ALTERNATE_TITLES); + for (Object object2 : alternateTitles) { + JSONObject alternateTitle = (JSONObject) object2; + id = generateSonarrTitleId(tvdbId, sno); + title = alternateTitle.getString(ApiField.SONARR_TITLE); + sonarrTitle = new SonarrTitle(); + sonarrTitle.setId(id); + sonarrTitle.setTvdbId(tvdbId); + sonarrTitle.setSno(sno++); + sonarrTitle.setMainTitle(mainTitle); + sonarrTitle.setTitle(title); + sonarrTitle.setCleanTitle(FormatUtil.cleanTitle(title, cleanTitleRegex)); + sonarrTitle.setSeasonNumber(alternateTitle.getInteger(ApiField.SONARR_SCENE_SEASON_NUMBER)); + sonarrTitle.setMonitored(monitored); + sonarrTitleList.add(sonarrTitle); + } + } + proxy().saveOrUpdateBatch(sonarrTitleList, Common.BATCH_SIZE); + return true; + } + + /** + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#queryNeedSyncTmdbTitle() + */ + @Override + public List queryNeedSyncTmdbTitle() { + return sonarrTitleMapper.selectNeedSyncTmdbTitle(); + } + + /** + * + * 生成 sonarrTitle 主键 id + * + * @param tvdbId + * @param sno + * @return Integer + */ + private Integer generateSonarrTitleId(Integer tvdbId, int sno) { + return Integer.parseInt(new StringBuilder(tvdbId.toString()).append(sno).toString()); + } + + /** + * @param title + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#queryByTitle(java.lang.String) + */ + @Override + public SonarrTitle queryByTitle(String title) { + String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX); + String cleanTitle = FormatUtil.cleanTitle(title, cleanTitleRegex); + List sonarrTitleList = proxy().query().eq(TableField.CLEAN_TITLE, cleanTitle).list(); + SonarrTitle sonarrTitle = null; + if (!sonarrTitleList.isEmpty()) { + sonarrTitle = sonarrTitleList.get(0); + } else { + title = FormatUtil.removeSeason(title); + cleanTitle = FormatUtil.cleanTitle(title, cleanTitleRegex); + sonarrTitleList = proxy().query().eq(TableField.CLEAN_TITLE, cleanTitle).list(); + if (!sonarrTitleList.isEmpty()) { + sonarrTitle = sonarrTitleList.get(0); + } + } + if (sonarrTitle != null) { + sonarrTitle.setTitle(title); + } + return sonarrTitle; + } + + /** + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#queryAll() + */ + @Override + @Cacheable(cacheNames = CacheName.SONARR_RESULT_TITLE) + public List queryAll() { + return sonarrTitleMapper.selectSonarrTitleAndTmdbTitle(); + } + + /** + * + * @param text + * @param format + * @param cleanTitleRegex + * @param sonarrRuleList + * @param sonarrTitleList + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#formatTitle(java.lang.String, + * java.lang.String, java.lang.String, java.util.List, java.util.List) + */ + @Override + public FormatResult formatTitle(String text, String format, String cleanTitleRegex, + List sonarrRuleList, List sonarrTitleList) { + FormatResult result = new FormatResult(); + result.setRestText(text); + for (SonarrRule sonarrRule : sonarrRuleList) { + if (sonarrRule.getRegex().contains("{" + Token.CLEAN_TITLE + "}")) { + for (SonarrTitle sonarrTitle : sonarrTitleList) { + String cleanTitle = sonarrTitle.getCleanTitle() != null ? sonarrTitle.getCleanTitle() + : FormatUtil.cleanTitle(sonarrTitle.getTitle(), cleanTitleRegex); + String cleanText = FormatUtil.cleanTitle(text, cleanTitleRegex); + String regex = sonarrRule.getRegex().replace("{" + Token.CLEAN_TITLE + "}", cleanTitle); + if (cleanText.matches(regex)) { + result.setRestText(text.replaceAll( + "((?i)" + cleanTitle.replaceAll(FormatUtil.PLACEHOLDER, ".{0,3}") + ")", "")); + format = FormatUtil.replaceToken(Token.TITLE, sonarrTitle.getMainTitle(), format); + Integer seasonNumber = sonarrTitle.getSeasonNumber(); + if (!Integer.valueOf(-1).equals(seasonNumber) + && !Integer.valueOf(1).equals(seasonNumber)) { + format = FormatUtil.replaceToken(Token.SEASON, "S" + seasonNumber, format); + } + break; + } + } + } else { + Matcher matcher = Pattern.compile(sonarrRule.getRegex()).matcher(text); + if (matcher.find()) { + try { + String value = matcher.replaceAll(sonarrRule.getReplacement()); + format = FormatUtil.replaceToken(Token.TITLE, value, format); + break; + } catch (Exception e) { + log.error("replaceAll 出错:{}\n{}", e.getMessage(), JSON.toJSONString(sonarrRule)); + } + } + } + } + result.setFormatText(format.trim()); + return result; + } + + /** + * @param text + * @param format + * @param tokenRuleMap + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#format(java.lang.String, + * java.lang.String, java.util.Map) + */ + @Override + public String format(String text, String format, Map> tokenRuleMap) { + boolean episodeFinded = false; + Matcher matcher = Pattern.compile(Token.REGEX).matcher(format); + while (matcher.find()) { + String token = matcher.group(1); + List sonarrRuleList = tokenRuleMap.get(token); + for (SonarrRule sonarrRule : sonarrRuleList) { + Matcher tokenMatcher = Pattern.compile(sonarrRule.getRegex()).matcher(text); + if (tokenMatcher.find()) { + try { + String value = tokenMatcher.replaceAll(sonarrRule.getReplacement()); + format = FormatUtil.replaceToken(token, value, format, sonarrRule.getOffset()); + if (Token.EPISODE.equals(token)) { + episodeFinded = true; + } + break; + } catch (Exception e) { + log.error("replaceAll 出错:{}\n{}", e.getMessage(), JSON.toJSONString(sonarrRule)); + } + } + } + } + if (!episodeFinded && format.contains("{" + Token.EPISODE + "}")) { + return text; + } + matcher = Pattern.compile(Token.REGEX).matcher(format); + while (matcher.find()) { + format = FormatUtil.removeToken(matcher.group(1), format); + } + return format.trim(); + } + + /** + * @param text + * @param format + * @param tokenRuleMap + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#formatWithCache(java.lang.String, + * java.lang.String, java.util.Map) + */ + @Override + @Cacheable(cacheNames = CacheName.DOWNLOADER_FORMAT_NAME, key = "#text") + public String formatWithCache(String text, String format, Map> tokenRuleMap) { + return format(text, format, tokenRuleMap); + } + + /** + * @param request + * @return + * @see com.lckp.jproxy.service.ISonarrTitleService#query(com.lckp.jproxy.model.request.SonarrTitleQueryRequest) + */ + @Override + public IPage query(SonarrTitleQueryRequest request) { + QueryWrapper wrapper = new QueryWrapper<>(); + if (StringUtils.isNotBlank(request.getTitle())) { + wrapper.like(TableField.TITLE, request.getTitle().trim()); + } + if (request.getTvdbId() != null) { + wrapper.eq(TableField.TVDB_ID, request.getTvdbId()); + } + wrapper.orderByDesc(TableField.UPDATE_TIME); + return proxy().page(request.mybatisPlusPage(), wrapper); + } +} diff --git a/src/main/java/com/lckp/jproxy/util/FormatUtil.java b/src/main/java/com/lckp/jproxy/util/FormatUtil.java index eb349a6..b4845e8 100644 --- a/src/main/java/com/lckp/jproxy/util/FormatUtil.java +++ b/src/main/java/com/lckp/jproxy/util/FormatUtil.java @@ -1,150 +1,159 @@ -package com.lckp.jproxy.util; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; - -/** - *

- * 格式化工具类 - *

- * - * @author LuckyPuppy514 - * @date 2023-03-20 - */ -public class FormatUtil { - FormatUtil() { - } - - /** - * - * 清洗标题 - * - * @param title - * @param regex - * @return String - */ - public static String cleanTitle(String title, String regex) { - String cleanTitle = title.replaceAll(regex, ""); - if (StringUtils.isBlank(cleanTitle)) { - cleanTitle = title; - } - return cleanTitle.toLowerCase(); - } - - /** - * - * 去除标题中的年份 - * - * @param title - * @return String - */ - public static String removeYear(String title) { - if (StringUtils.isNotBlank(title)) { - return title.replaceAll(" \\d{4}$", ""); - } - return ""; - } - - /** - * - * 去除标题中的集数 - * - * @param title - * @return String - */ - public static String removeEpisode(String title) { - if (StringUtils.isNotBlank(title)) { - return title.replaceAll(" \\d+$", ""); - } - return ""; - } - - /** - * - * 去除标题中的季数 - * - * @param title - * @return String - */ - public static String removeSeason(String title) { - if (StringUtils.isNotBlank(title)) { - return title.replaceAll(" (S\\d+)$", ""); - } - return ""; - } - - /** - * - * 去除标题中的季数和集数 - * - * @param title - * @return String - */ - public static String removeSeasonEpisode(String title) { - if (StringUtils.isNotBlank(title)) { - return title.replaceAll(" (S\\d+ |)\\d+$", ""); - } - return ""; - } - - /** - * - * 用 value 替换 token - * - * @param token - * @param value - * @param text - * @param offset - * @return String - */ - public static String replaceToken(String token, String value, String text, Integer offset) { - return replaceToken(token, executeOffset(value, offset), text); - } - - /** - * - * 执行偏移 - * - * @param value - * @param offset - * @return String - */ - public static String executeOffset(String value, Integer offset) { - if (offset != null && !Integer.valueOf(0).equals(offset)) { - Matcher matcher = Pattern.compile("(\\d+)").matcher(value); - while (matcher.find()) { - String numberString = matcher.group(1); - int number = Integer.parseInt(numberString); - value = value.replace(numberString, String.valueOf(number + offset)); - } - } - return value; - } - - /** - * - * 用 value 替换 token - * - * @param token - * @param value - * @param text - * @return String - */ - public static String replaceToken(String token, String value, String text) { - return text.replace("{" + token + "}", value); - } - - /** - * - * 移除 token - * - * @param token - * @param text - * @return String - */ - public static String removeToken(String token, String text) { - return text.replace("{" + token + "}", ""); - } -} +package com.lckp.jproxy.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +/** + *

+ * 格式化工具类 + *

+ * + * @author LuckyPuppy514 + * @date 2023-03-20 + */ +public class FormatUtil { + FormatUtil() { + } + + // 正则表达式特殊字符 + private static final String REGEX_SPECIAL_CHAR = "[\\$\\(\\)\\*\\+\\.\\[\\]\\?\\^\\{\\}\\|\\\\]"; + + // 占位符 + public static final String PLACEHOLDER = "\s"; + private static final String PLACEHOLDERS = "\s+"; + + /** + * + * 清洗标题 + * + * @param title + * @param regex + * @return String + */ + public static String cleanTitle(String title, String regex) { + title = title.replaceAll(REGEX_SPECIAL_CHAR, PLACEHOLDER); + String cleanTitle = title.replaceAll(regex, PLACEHOLDER); + if (StringUtils.isBlank(cleanTitle)) { + cleanTitle = title; + } + cleanTitle = cleanTitle.replaceAll(PLACEHOLDERS, PLACEHOLDER); + return cleanTitle.toLowerCase(); + } + + /** + * + * 去除标题中的年份 + * + * @param title + * @return String + */ + public static String removeYear(String title) { + if (StringUtils.isNotBlank(title)) { + return title.replaceAll(" \\d{4}$", ""); + } + return ""; + } + + /** + * + * 去除标题中的集数 + * + * @param title + * @return String + */ + public static String removeEpisode(String title) { + if (StringUtils.isNotBlank(title)) { + return title.replaceAll(" \\d+$", ""); + } + return ""; + } + + /** + * + * 去除标题中的季数 + * + * @param title + * @return String + */ + public static String removeSeason(String title) { + if (StringUtils.isNotBlank(title)) { + return title.replaceAll(" (S\\d+)$", ""); + } + return ""; + } + + /** + * + * 去除标题中的季数和集数 + * + * @param title + * @return String + */ + public static String removeSeasonEpisode(String title) { + if (StringUtils.isNotBlank(title)) { + return title.replaceAll(" (S\\d+ |)\\d+$", ""); + } + return ""; + } + + /** + * + * 用 value 替换 token + * + * @param token + * @param value + * @param text + * @param offset + * @return String + */ + public static String replaceToken(String token, String value, String text, Integer offset) { + return replaceToken(token, executeOffset(value, offset), text); + } + + /** + * + * 执行偏移 + * + * @param value + * @param offset + * @return String + */ + public static String executeOffset(String value, Integer offset) { + if (offset != null && !Integer.valueOf(0).equals(offset)) { + Matcher matcher = Pattern.compile("(\\d+)").matcher(value); + while (matcher.find()) { + String numberString = matcher.group(1); + int number = Integer.parseInt(numberString); + value = value.replace(numberString, String.valueOf(number + offset)); + } + } + return value; + } + + /** + * + * 用 value 替换 token + * + * @param token + * @param value + * @param text + * @return String + */ + public static String replaceToken(String token, String value, String text) { + return text.replace("{" + token + "}", value); + } + + /** + * + * 移除 token + * + * @param token + * @param text + * @return String + */ + public static String removeToken(String token, String text) { + return text.replace("{" + token + "}", ""); + } +} diff --git a/src/main/resources/database/changelog/v3.0.3.sql b/src/main/resources/database/changelog/v3.0.3.sql new file mode 100644 index 0000000..09e07a8 --- /dev/null +++ b/src/main/resources/database/changelog/v3.0.3.sql @@ -0,0 +1,5 @@ +--liquibase formatted sql +--changeset LuckyPuppy514:1 +--comment: 更新净标题正则表达式 +UPDATE system_config SET value='(`|~|!|@|#|%|&|_|/|=|''|"|:|<|>|-|—|·|,|、|。|‘|’|“|”|?|!|:|(|)|【|】|《|》|♀)' WHERE id=16; +--rollback DELETE FROM system_config WHERE id=16; \ No newline at end of file diff --git a/src/main/resources/rule/radarr@LuckyPuppy514.json b/src/main/resources/rule/radarr@LuckyPuppy514.json index d7f8803..bba9f23 100644 --- a/src/main/resources/rule/radarr@LuckyPuppy514.json +++ b/src/main/resources/rule/radarr@LuckyPuppy514.json @@ -1 +1 @@ -[{"id":"00000000000000000000000000000000","token":"title","priority":999,"regex":".*{cleanTitle}.*","replacement":"{mainTitle}","offset":0,"example":"","remark":"\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB","author":"LuckyPuppy514"},{"id":"21E1CBECBB3A42D29F410EED2AB36330","token":"title","priority":1100,"regex":".*(Doraemon the Movie Nobita'?s Little Star Wars 2021|哆啦A梦剧场版 大雄的宇宙小战争2021).*","replacement":"哆啦A梦:大雄的宇宙小战争 2021","offset":0,"example":"Doraemon the Movie Nobitas Little Star Wars 2021\nDoraemon the Movie Nobita's Little Star Wars 2021\n哆啦A梦剧场版 大雄的宇宙小战争2021","remark":"哆啦A梦:大雄的宇宙小战争 2021","author":"LuckyPuppy514"},{"id":"D0424165776B45E4927C698E1FE0E856","token":"language","priority":1100,"regex":".*(繁简|简繁|简日|日简|繁日|日繁).*","replacement":"[$1]","offset":0,"example":"xxx繁日xxx\nxxx简日xxx\nxxx繁简xxx\nxxx简繁xxx","remark":"简繁 | 间日 | 繁日","author":"LuckyPuppy514"},{"id":"32F62461B8124572A53602A4B76BE70A","token":"language","priority":1200,"regex":".*(简体|简中|简|(?i)\\bCHS\\b).*","replacement":"[简中]","offset":0,"example":"xxx简体xxx\nxxx简中xxx\nxxx简xxx\nxxx CHS xxx\nxxx chs xxx","remark":"简体 | 简中 | 简 | CHS","author":"LuckyPuppy514"},{"id":"01616775F99C484CBC6BF79A3FBE513E","token":"language","priority":1300,"regex":".*(繁体|繁中|繁|(?i)\\bCHT\\b|zh-TW|zh-HK|zh-SG).*","replacement":"[繁中]","offset":0,"example":"xxx繁xxx\nxxx CHT xxx\nxxx cht xxx\nxxxzh-TWxxx\nxxxzh-HKxxx\nxxxzh-SGxxx","remark":"繁体 | 繁中 | 繁 | CHT | zh-TW | zh-HK | zh-SG","author":"LuckyPuppy514"},{"id":"CC8FAC9272334EBDADE2C3BAE33BCBC7","token":"language","priority":1400,"regex":".*(中文|汉语|(?i)Chinese|zh-CN).*","replacement":"[中文]","offset":0,"example":"xxx中文xxx\nxxx汉语xxx\nxxxChinesexxx\nxxxzh-CNxxx","remark":"中文 | 汉语 | Chinese | zh-CN","author":"LuckyPuppy514"},{"id":"3D1B02EC745443B9916894D1EEFDF74D","token":"resolution","priority":1100,"regex":".*((?i)1280X720|720P|\\bSD\\b|标清|\\b720\\b).*","replacement":"[720P]","offset":0,"example":"xxx 1280X720 xxx\nxxx 720P xxx\nxxx SD xxx\nxxx 标清 xxx\nxxx 720 xxx\n【幻櫻字幕組】【4月新番】【打工吧!魔王大人_Hataraku Maou-sama】【08】【BIG5_MP4】【1280X720】","remark":"1280X720 | 720P| SD | 标清 | 720","author":"LuckyPuppy514"},{"id":"F0B2B567595B439B9731062453D13D9D","token":"resolution","priority":1000,"regex":".*((?i)1920X1080|1080P|\\bHD\\b|Full-HD|高清|\\b1080\\b|\\b1K\\b).*","replacement":"[1080P]","offset":0,"example":"xxx1920X1080xxx\nxxx1080Pxxx\nxxx HD xxx\nxxx高清xxx\nxxx 1080 xxx\n[BDRip_Full-HD_AVC]\nxxx 1K xxx","remark":"1920X1080 | 1080P | HD | Full-HD | 高清 | 1080 | 1K","author":"LuckyPuppy514"},{"id":"802F223555604889874EE1E918CEEE9F","token":"resolution","priority":1300,"regex":".*((?i)2560X1440|1440P|\\b2K\\b|\\b1440\\b).*","replacement":"[1440P]","offset":0,"example":"xxx 2560X1440 xxx\nxxx 1440P xxx\nxxx 2K xxx\nxxx 1440 xxx","remark":"2560X1440 | 1440P | 2K | 1440","author":"LuckyPuppy514"},{"id":"F0B8685B8BD44A349CD1D71FAD9262E2","token":"resolution","priority":1200,"regex":".*((?i)3840x2160|2160P|\\b4K\\b|\\bUHD\\b|超高清|\\b2160\\b).*","replacement":"[2160P]","offset":0,"example":"xxx3840x2160xxx\nxxx2160Pxxx\nxxx 4K xxx\nxxx UHD xxx\nxxx超高清xxx\nxxx 2160 xxx","remark":"3840x2160 | 2160P| 4K | UHD | 超高清 | 2160","author":"LuckyPuppy514"},{"id":"419DC749C479440185FC8F8E1BDD04EE","token":"quality","priority":2100,"regex":".*((?i)\\bCAM\\b|\\bTELESYNC\\b|\\bTELECINE\\b).*","replacement":"[CAM]","offset":0,"example":"xxx CAM xxx\nxxx TELESYNC xxx\nxxx TELECINE xxx","remark":"CAM | TELESYNC | TELECINE","author":"LuckyPuppy514"},{"id":"E44AD59943A64E3587DAA6E60B236BC7","token":"quality","priority":2000,"regex":".*((?i)\\bSDTV\\b).*","replacement":"[SDTV]","offset":0,"example":"xxx SDTV xxx","remark":"SDTV","author":"LuckyPuppy514"},{"id":"CAA6E3718F284B3E9087D25FCC681BA6","token":"quality","priority":1100,"regex":".*((?i)\\bHDTV\\b).*","replacement":"[HDTV]","offset":0,"example":"xxx HDTV xxx","remark":"HDTV","author":"LuckyPuppy514"},{"id":"B8A66E6AFB5D40EDA74A964ADB560DC9","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?DL\\b).*","replacement":"[WEBDL]","offset":0,"example":"xxx WEBDL xxx\nxxx WEB-DL xxx","remark":"WEBDL | WEB-DL","author":"LuckyPuppy514"},{"id":"0C4678B2749C496480EA805A5112DE52","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?RIP\\b).*","replacement":"[WEBRip]","offset":0,"example":"xxx WEBRip xxx\nxxx WEB-Rip xxx","remark":"WEBRip | WEB-Rip","author":"LuckyPuppy514"},{"id":"29D8FC56D1A0462B8D158DEE61A9418C","token":"quality","priority":1400,"regex":".*((?i)\\bRAW.?HD\\b).*","replacement":"[Raw-HD]","offset":0,"example":"xxx RawHD xxx\nxxx Raw-HD xxx","remark":"RawHD | Raw-HD","author":"LuckyPuppy514"},{"id":"52A87D6958E848DFA1441E68143EC00C","token":"quality","priority":1500,"regex":".*((?i)\\bBluray.?Remux\\b).*","replacement":"[Bluray-Remux]","offset":0,"example":"xxx BlurayRemux xxx\nxxx Bluray-Remux xxx","remark":"BlurayRemux | Bluray-Remux","author":"LuckyPuppy514"},{"id":"B8CC5016E5F64040B073E768864A017D","token":"quality","priority":1200,"regex":".*((?i)\\bBluray\\b).*","replacement":"[Bluray]","offset":0,"example":"xxx Bluray xxx","remark":"Bluray","author":"LuckyPuppy514"},{"id":"7318BF3B11FB414892978952883575E5","token":"quality","priority":1300,"regex":".*((?i)\\bRemux\\b).*","replacement":"[Remux]","offset":0,"example":"xxx Remux xxx","remark":"Remux","author":"LuckyPuppy514"},{"id":"52F4F33130AA4A92A3170E048AAAECE6","token":"group","priority":1300,"regex":"^\\s*\\[([^]]+)\\].+","replacement":"[$1]","offset":0,"example":"[Group Name] xxx","remark":"[Group Name]","author":"LuckyPuppy514"},{"id":"B13F8B7DBC31454DB5433882BEC6673C","token":"group","priority":1400,"regex":"^\\s*【([^】]+)】.+","replacement":"[$1]","offset":0,"example":"【发布组名称】xxx","remark":"【发布组名称】","author":"LuckyPuppy514"},{"id":"ECE3FC67F60647B4BF025476E3BE31A2","token":"group","priority":1200,"regex":".*(\\[|\\(|【|(| )([^ ]+(字幕组|字幕組|字幕社|发布组|发布組|汉化组|汉化組|压制组|压制組)).*","replacement":"[$2]","offset":0,"example":"[KRL字幕組] xxxxxxxxxxxxx\n[北宇治字幕组] xxxxxxxxxxxxx\n【北宇治字幕组】 xxxxxxxxxxxxx\nxxxxxxxxxxxxx [悠哈璃羽字幕社] xxxxxxxxxxxxx\n[豌豆字幕组&风之圣殿字幕组] xxxxxxxxxxx\n[爱恋字幕社&波子汽水汉化组] xxxxxxxxxxxxx","remark":"字幕组 | 字幕組 | 字幕社 | 发布组 | 发布組 | 汉化组 | 汉化組 | 压制组 | 压制組","author":"LuckyPuppy514"},{"id":"DED9BE2BB9734B5EBA21CC281AE371C5","token":"group","priority":1100,"regex":".*\\b(ANi|GM-Team|Lilith-Raws|LoliHouse|MMSUB|NC-Raws|NaN-Raws|OPFans枫雪动漫|OPFans楓雪動漫|PorterRAWS|SickeClipse|Skymoon-Raws|SweetSub|XK SPIRITS|c.c動漫|喵萌Production|喵萌奶茶屋|猎户不鸽压制|猎户随缘压制|神楽坂 まひろ|离谱Sub|Sakurato)\\b.*","replacement":"[$1]","offset":0,"example":"xxx ANi xxx\nxxx GM-Team xxx\nxxx Lilith-Raws xxx\nxxx OPFans楓雪動漫 xxx","remark":"ANi | GM-Team | Lilith-Raws | LoliHouse ......","author":"LuckyPuppy514"},{"id":"7E5FCBE09C1742649BA223120CCBBD5E","token":"language","priority":1000,"regex":"(?=.*简)(?=.*日)(?=.*繁).*","replacement":"[简繁日]","offset":0,"example":"xxx简日繁xxx\nxxx简繁日xxx\nxxx日简繁xxx","remark":"简繁日","author":"LuckyPuppy514"},{"id":"160235AB3F7B4094842F5436F6F42C8F","token":"resolution","priority":1500,"regex":".*((?i)854X480|480P|\\b480\\b).*","replacement":"[480P]","offset":0,"example":"xxx854X480xxx\nxxx480Pxxx\nxxx480xxx","remark":"854X480 | 480P | 480","author":"LuckyPuppy514"},{"id":"9E79CCEC074A4CAD8AC8DE227E398FFC","token":"resolution","priority":1400,"regex":".*((?i)960X540|540P|\\b540\\b).*","replacement":"[540P]","offset":0,"example":"xxx960X540xxx\nxxx540Pxxx\nxxx 540 xxx","remark":"960X540 | 540P | 540","author":"LuckyPuppy514"},{"id":"64EF964B7ED4422AA67893B652BE0D1D","token":"resolution","priority":1600,"regex":".*((?i)640X360|360P|\\b360\\b).*","replacement":"[360P]","offset":0,"example":"xxx640X360xxx\nxxx360Pxxx\nxxx 360 xxx","remark":"640X360 | 360P | 360","author":"LuckyPuppy514"},{"id":"8622CBF30DE5420690637CCA83C0B941","token":"dynamic_range","priority":1000,"regex":".*\\b((?i)HDR10|HDR|SDR)\\b.*","replacement":"[$1]","offset":0,"example":"xxx HDR10 xxx\nxxx HDR xxx\nxxx SDR xxx","remark":"HDR10 | HDR | SDR","author":"LuckyPuppy514"},{"id":"09DD664F333D4B1683CE1EA75B681F10","token":"bit","priority":1000,"regex":".*\\b((?i)10bit|8bit)s?\\b.*","replacement":"[$1]","offset":0,"example":"xxx 10bit xxx\nxxx 8bit xxx","remark":"10bit | 8bit","author":"LuckyPuppy514"},{"id":"D606BF155E3943559607BE56C40E3990","token":"language","priority":2100,"regex":".+-ADWeb$","replacement":"[简中]","offset":0,"example":"A Record of a Mortal's Journey to Immortality S01 2020 1080p WEB-DL H264 AAC-ADWeb\n Fen Jie Xian S01E05-E06 2022 1080p WEB-DL H265 AAC-ADWeb\nLost In The Kunlun Mountains S01E13-14 2022 2160p WEB-DL H265 AAC-ADWeb\nHuanmeng Shan Hai Yao S01E07 2022 1080p WEB-DL H265 AAC-ADWeb\nOne Piece E1029 1999 1080p WEB-DL H264 AAC-ADWeb","remark":"ADWeb","author":"LuckyPuppy514"},{"id":"1DEDD0BD47074CCFA048F6BE08B2D15D","token":"quality","priority":1600,"regex":".*(BD.?Rip).*","replacement":"[BDRip]","offset":0,"example":"xxx BDRip xxx\nxxx BD-Rip xxx","remark":"BDRip | BD-Rip","author":"LuckyPuppy514"},{"id":"38B8F077EF4E4DC799166AD8A25FA3A9","token":"language","priority":2000,"regex":".*\\b(Baha|baha)\\b.*","replacement":"[繁中]","offset":0,"example":"xxx Baha xxx\nxxx baha xxx","remark":"Baha | baha","author":"LuckyPuppy514"},{"id":"74BE48EA58B541BBAA7382E377594975","token":"group","priority":1000,"regex":".*(喵萌奶茶屋&LoliHouse|离谱Sub&DBD-Raws|悠哈璃羽字幕社&LoliHouse|动漫国字幕组&LoliHouse|极影字幕社&ANK-Raws|豌豆字幕组&LoliHouse).*","replacement":"[$1]","offset":0,"example":"xxx喵萌奶茶屋&LoliHousexxx\nxxx离谱Sub&DBD-Rawsxxx\nxxx悠哈璃羽字幕社&LoliHousexxx","remark":"喵萌奶茶屋&LoliHouse | 离谱Sub&DBD-Raws | 悠哈璃羽字幕社&LoliHouse ......","author":"LuckyPuppy514"},{"id":"117871426A4044BE977420CCE661EC88","token":"subtitle","priority":1000,"regex":".*(内嵌|外挂).*","replacement":"[$1]","offset":0,"example":"xxx内嵌xxx\nxxx外挂xxx","remark":"内嵌 | 外挂","author":"LuckyPuppy514"},{"id":"C8E8B03BFCC34DFBB254C3A7A97067C4","token":"year","priority":1000,"regex":".*\\b(19[5-9]\\d|20\\d{2})\\b.*","replacement":"$1","offset":0,"example":"xxx 1950 xxx\nxxx 2099 xxx","remark":"1950 | 2099 ......","author":"LuckyPuppy514"}] \ No newline at end of file +[{"id":"00000000000000000000000000000000","token":"title","priority":999,"regex":".*\\b{cleanTitle}\\b.*","replacement":"{mainTitle}","offset":0,"example":"","remark":"\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB","author":"LuckyPuppy514"},{"id":"21E1CBECBB3A42D29F410EED2AB36330","token":"title","priority":1100,"regex":".*(Doraemon the Movie Nobita'?s Little Star Wars 2021|哆啦A梦剧场版 大雄的宇宙小战争2021).*","replacement":"哆啦A梦:大雄的宇宙小战争 2021","offset":0,"example":"Doraemon the Movie Nobitas Little Star Wars 2021\nDoraemon the Movie Nobita's Little Star Wars 2021\n哆啦A梦剧场版 大雄的宇宙小战争2021","remark":"哆啦A梦:大雄的宇宙小战争 2021","author":"LuckyPuppy514"},{"id":"D0424165776B45E4927C698E1FE0E856","token":"language","priority":1100,"regex":".*(繁简|简繁|简日|日简|繁日|日繁).*","replacement":"[$1]","offset":0,"example":"xxx繁日xxx\nxxx简日xxx\nxxx繁简xxx\nxxx简繁xxx","remark":"简繁 | 间日 | 繁日","author":"LuckyPuppy514"},{"id":"32F62461B8124572A53602A4B76BE70A","token":"language","priority":1200,"regex":".*(简体|简中|简|(?i)\\bCHS\\b).*","replacement":"[简中]","offset":0,"example":"xxx简体xxx\nxxx简中xxx\nxxx简xxx\nxxx CHS xxx\nxxx chs xxx","remark":"简体 | 简中 | 简 | CHS","author":"LuckyPuppy514"},{"id":"01616775F99C484CBC6BF79A3FBE513E","token":"language","priority":1300,"regex":".*(繁体|繁中|繁|(?i)\\bCHT\\b|zh-TW|zh-HK|zh-SG).*","replacement":"[繁中]","offset":0,"example":"xxx繁xxx\nxxx CHT xxx\nxxx cht xxx\nxxxzh-TWxxx\nxxxzh-HKxxx\nxxxzh-SGxxx","remark":"繁体 | 繁中 | 繁 | CHT | zh-TW | zh-HK | zh-SG","author":"LuckyPuppy514"},{"id":"CC8FAC9272334EBDADE2C3BAE33BCBC7","token":"language","priority":1400,"regex":".*(中文|汉语|(?i)Chinese|zh-CN).*","replacement":"[中文]","offset":0,"example":"xxx中文xxx\nxxx汉语xxx\nxxxChinesexxx\nxxxzh-CNxxx","remark":"中文 | 汉语 | Chinese | zh-CN","author":"LuckyPuppy514"},{"id":"3D1B02EC745443B9916894D1EEFDF74D","token":"resolution","priority":1100,"regex":".*((?i)1280X720|720P|\\bSD\\b|标清|\\b720\\b).*","replacement":"[720P]","offset":0,"example":"xxx 1280X720 xxx\nxxx 720P xxx\nxxx SD xxx\nxxx 标清 xxx\nxxx 720 xxx\n【幻櫻字幕組】【4月新番】【打工吧!魔王大人_Hataraku Maou-sama】【08】【BIG5_MP4】【1280X720】","remark":"1280X720 | 720P| SD | 标清 | 720","author":"LuckyPuppy514"},{"id":"F0B2B567595B439B9731062453D13D9D","token":"resolution","priority":1000,"regex":".*((?i)1920X1080|1080P|\\bHD\\b|Full-HD|高清|\\b1080\\b|\\b1K\\b).*","replacement":"[1080P]","offset":0,"example":"xxx1920X1080xxx\nxxx1080Pxxx\nxxx HD xxx\nxxx高清xxx\nxxx 1080 xxx\n[BDRip_Full-HD_AVC]\nxxx 1K xxx","remark":"1920X1080 | 1080P | HD | Full-HD | 高清 | 1080 | 1K","author":"LuckyPuppy514"},{"id":"802F223555604889874EE1E918CEEE9F","token":"resolution","priority":1300,"regex":".*((?i)2560X1440|1440P|\\b2K\\b|\\b1440\\b).*","replacement":"[1440P]","offset":0,"example":"xxx 2560X1440 xxx\nxxx 1440P xxx\nxxx 2K xxx\nxxx 1440 xxx","remark":"2560X1440 | 1440P | 2K | 1440","author":"LuckyPuppy514"},{"id":"F0B8685B8BD44A349CD1D71FAD9262E2","token":"resolution","priority":1200,"regex":".*((?i)3840x2160|2160P|\\b4K\\b|\\bUHD\\b|超高清|\\b2160\\b).*","replacement":"[2160P]","offset":0,"example":"xxx3840x2160xxx\nxxx2160Pxxx\nxxx 4K xxx\nxxx UHD xxx\nxxx超高清xxx\nxxx 2160 xxx","remark":"3840x2160 | 2160P| 4K | UHD | 超高清 | 2160","author":"LuckyPuppy514"},{"id":"419DC749C479440185FC8F8E1BDD04EE","token":"quality","priority":2100,"regex":".*((?i)\\bCAM\\b|\\bTELESYNC\\b|\\bTELECINE\\b).*","replacement":"[CAM]","offset":0,"example":"xxx CAM xxx\nxxx TELESYNC xxx\nxxx TELECINE xxx","remark":"CAM | TELESYNC | TELECINE","author":"LuckyPuppy514"},{"id":"E44AD59943A64E3587DAA6E60B236BC7","token":"quality","priority":2000,"regex":".*((?i)\\bSDTV\\b).*","replacement":"[SDTV]","offset":0,"example":"xxx SDTV xxx","remark":"SDTV","author":"LuckyPuppy514"},{"id":"CAA6E3718F284B3E9087D25FCC681BA6","token":"quality","priority":1100,"regex":".*((?i)\\bHDTV\\b).*","replacement":"[HDTV]","offset":0,"example":"xxx HDTV xxx","remark":"HDTV","author":"LuckyPuppy514"},{"id":"B8A66E6AFB5D40EDA74A964ADB560DC9","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?DL\\b).*","replacement":"[WEBDL]","offset":0,"example":"xxx WEBDL xxx\nxxx WEB-DL xxx","remark":"WEBDL | WEB-DL","author":"LuckyPuppy514"},{"id":"0C4678B2749C496480EA805A5112DE52","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?RIP\\b).*","replacement":"[WEBRip]","offset":0,"example":"xxx WEBRip xxx\nxxx WEB-Rip xxx","remark":"WEBRip | WEB-Rip","author":"LuckyPuppy514"},{"id":"29D8FC56D1A0462B8D158DEE61A9418C","token":"quality","priority":1400,"regex":".*((?i)\\bRAW.?HD\\b).*","replacement":"[Raw-HD]","offset":0,"example":"xxx RawHD xxx\nxxx Raw-HD xxx","remark":"RawHD | Raw-HD","author":"LuckyPuppy514"},{"id":"52A87D6958E848DFA1441E68143EC00C","token":"quality","priority":1500,"regex":".*((?i)\\bBluray.?Remux\\b).*","replacement":"[Bluray-Remux]","offset":0,"example":"xxx BlurayRemux xxx\nxxx Bluray-Remux xxx","remark":"BlurayRemux | Bluray-Remux","author":"LuckyPuppy514"},{"id":"B8CC5016E5F64040B073E768864A017D","token":"quality","priority":1200,"regex":".*((?i)\\bBluray\\b).*","replacement":"[Bluray]","offset":0,"example":"xxx Bluray xxx","remark":"Bluray","author":"LuckyPuppy514"},{"id":"7318BF3B11FB414892978952883575E5","token":"quality","priority":1300,"regex":".*((?i)\\bRemux\\b).*","replacement":"[Remux]","offset":0,"example":"xxx Remux xxx","remark":"Remux","author":"LuckyPuppy514"},{"id":"52F4F33130AA4A92A3170E048AAAECE6","token":"group","priority":1300,"regex":"^\\s*\\[([^]]+)\\].+","replacement":"[$1]","offset":0,"example":"[Group Name] xxx","remark":"[Group Name]","author":"LuckyPuppy514"},{"id":"B13F8B7DBC31454DB5433882BEC6673C","token":"group","priority":1400,"regex":"^\\s*【([^】]+)】.+","replacement":"[$1]","offset":0,"example":"【发布组名称】xxx","remark":"【发布组名称】","author":"LuckyPuppy514"},{"id":"ECE3FC67F60647B4BF025476E3BE31A2","token":"group","priority":1200,"regex":".*(\\[|\\(|【|(| )([^ ]+(字幕组|字幕組|字幕社|发布组|发布組|汉化组|汉化組|压制组|压制組)).*","replacement":"[$2]","offset":0,"example":"[KRL字幕組] xxxxxxxxxxxxx\n[北宇治字幕组] xxxxxxxxxxxxx\n【北宇治字幕组】 xxxxxxxxxxxxx\nxxxxxxxxxxxxx [悠哈璃羽字幕社] xxxxxxxxxxxxx\n[豌豆字幕组&风之圣殿字幕组] xxxxxxxxxxx\n[爱恋字幕社&波子汽水汉化组] xxxxxxxxxxxxx","remark":"字幕组 | 字幕組 | 字幕社 | 发布组 | 发布組 | 汉化组 | 汉化組 | 压制组 | 压制組","author":"LuckyPuppy514"},{"id":"DED9BE2BB9734B5EBA21CC281AE371C5","token":"group","priority":1100,"regex":".*\\b(ANi|GM-Team|Lilith-Raws|LoliHouse|MMSUB|NC-Raws|NaN-Raws|OPFans枫雪动漫|OPFans楓雪動漫|PorterRAWS|SickeClipse|Skymoon-Raws|SweetSub|XK SPIRITS|c.c動漫|喵萌Production|喵萌奶茶屋|猎户不鸽压制|猎户随缘压制|神楽坂 まひろ|离谱Sub|Sakurato)\\b.*","replacement":"[$1]","offset":0,"example":"xxx ANi xxx\nxxx GM-Team xxx\nxxx Lilith-Raws xxx\nxxx OPFans楓雪動漫 xxx","remark":"ANi | GM-Team | Lilith-Raws | LoliHouse ......","author":"LuckyPuppy514"},{"id":"7E5FCBE09C1742649BA223120CCBBD5E","token":"language","priority":1000,"regex":"(?=.*简)(?=.*日)(?=.*繁).*","replacement":"[简繁日]","offset":0,"example":"xxx简日繁xxx\nxxx简繁日xxx\nxxx日简繁xxx","remark":"简繁日","author":"LuckyPuppy514"},{"id":"160235AB3F7B4094842F5436F6F42C8F","token":"resolution","priority":1500,"regex":".*((?i)854X480|480P|\\b480\\b).*","replacement":"[480P]","offset":0,"example":"xxx854X480xxx\nxxx480Pxxx\nxxx480xxx","remark":"854X480 | 480P | 480","author":"LuckyPuppy514"},{"id":"9E79CCEC074A4CAD8AC8DE227E398FFC","token":"resolution","priority":1400,"regex":".*((?i)960X540|540P|\\b540\\b).*","replacement":"[540P]","offset":0,"example":"xxx960X540xxx\nxxx540Pxxx\nxxx 540 xxx","remark":"960X540 | 540P | 540","author":"LuckyPuppy514"},{"id":"64EF964B7ED4422AA67893B652BE0D1D","token":"resolution","priority":1600,"regex":".*((?i)640X360|360P|\\b360\\b).*","replacement":"[360P]","offset":0,"example":"xxx640X360xxx\nxxx360Pxxx\nxxx 360 xxx","remark":"640X360 | 360P | 360","author":"LuckyPuppy514"},{"id":"8622CBF30DE5420690637CCA83C0B941","token":"dynamic_range","priority":1000,"regex":".*\\b((?i)HDR10|HDR|SDR)\\b.*","replacement":"[$1]","offset":0,"example":"xxx HDR10 xxx\nxxx HDR xxx\nxxx SDR xxx","remark":"HDR10 | HDR | SDR","author":"LuckyPuppy514"},{"id":"09DD664F333D4B1683CE1EA75B681F10","token":"bit","priority":1000,"regex":".*\\b((?i)10bit|8bit)s?\\b.*","replacement":"[$1]","offset":0,"example":"xxx 10bit xxx\nxxx 8bit xxx","remark":"10bit | 8bit","author":"LuckyPuppy514"},{"id":"D606BF155E3943559607BE56C40E3990","token":"language","priority":2100,"regex":".+-ADWeb$","replacement":"[简中]","offset":0,"example":"A Record of a Mortal's Journey to Immortality S01 2020 1080p WEB-DL H264 AAC-ADWeb\n Fen Jie Xian S01E05-E06 2022 1080p WEB-DL H265 AAC-ADWeb\nLost In The Kunlun Mountains S01E13-14 2022 2160p WEB-DL H265 AAC-ADWeb\nHuanmeng Shan Hai Yao S01E07 2022 1080p WEB-DL H265 AAC-ADWeb\nOne Piece E1029 1999 1080p WEB-DL H264 AAC-ADWeb","remark":"ADWeb","author":"LuckyPuppy514"},{"id":"1DEDD0BD47074CCFA048F6BE08B2D15D","token":"quality","priority":1600,"regex":".*(BD.?Rip).*","replacement":"[BDRip]","offset":0,"example":"xxx BDRip xxx\nxxx BD-Rip xxx","remark":"BDRip | BD-Rip","author":"LuckyPuppy514"},{"id":"38B8F077EF4E4DC799166AD8A25FA3A9","token":"language","priority":2000,"regex":".*\\b(Baha|baha)\\b.*","replacement":"[繁中]","offset":0,"example":"xxx Baha xxx\nxxx baha xxx","remark":"Baha | baha","author":"LuckyPuppy514"},{"id":"74BE48EA58B541BBAA7382E377594975","token":"group","priority":1000,"regex":".*(喵萌奶茶屋&LoliHouse|离谱Sub&DBD-Raws|悠哈璃羽字幕社&LoliHouse|动漫国字幕组&LoliHouse|极影字幕社&ANK-Raws|豌豆字幕组&LoliHouse).*","replacement":"[$1]","offset":0,"example":"xxx喵萌奶茶屋&LoliHousexxx\nxxx离谱Sub&DBD-Rawsxxx\nxxx悠哈璃羽字幕社&LoliHousexxx","remark":"喵萌奶茶屋&LoliHouse | 离谱Sub&DBD-Raws | 悠哈璃羽字幕社&LoliHouse ......","author":"LuckyPuppy514"},{"id":"117871426A4044BE977420CCE661EC88","token":"subtitle","priority":1000,"regex":".*(内嵌|外挂).*","replacement":"[$1]","offset":0,"example":"xxx内嵌xxx\nxxx外挂xxx","remark":"内嵌 | 外挂","author":"LuckyPuppy514"},{"id":"C8E8B03BFCC34DFBB254C3A7A97067C4","token":"year","priority":1000,"regex":".*\\b(19[5-9]\\d|20\\d{2})\\b.*","replacement":"$1","offset":0,"example":"xxx 1950 xxx\nxxx 2099 xxx","remark":"1950 | 2099 ......","author":"LuckyPuppy514"}] \ No newline at end of file diff --git a/src/main/resources/rule/sonarr@LuckyPuppy514.json b/src/main/resources/rule/sonarr@LuckyPuppy514.json index bf3ec03..fcd5eda 100644 --- a/src/main/resources/rule/sonarr@LuckyPuppy514.json +++ b/src/main/resources/rule/sonarr@LuckyPuppy514.json @@ -1 +1 @@ -[{"id":"00000000000000000000000000000000","token":"title","priority":999,"regex":".*{cleanTitle}.*","replacement":"{mainTitle}","offset":0,"example":"","remark":"\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB","author":"LuckyPuppy514"},{"id":"310B92F87CC247FCA579522DE3E5DF67","token":"season","priority":1106,"regex":".*(第八季|第捌季).*","replacement":"S8","offset":0,"example":"xxx第八季xxx\nxxx第捌季xxx","remark":"第八季 | 第捌季","author":"LuckyPuppy514"},{"id":"E4964E6099B54FC688F3BEFF6D30CA7A","token":"season","priority":1107,"regex":".*(第九季|第玖季).*","replacement":"S9","offset":0,"example":"xxx第九季xxx\nxxx第玖季xxx","remark":"第九季 | 第玖季","author":"LuckyPuppy514"},{"id":"4722BC9AF4854DE3BCD75F2AE8F6F9BD","token":"season","priority":1108,"regex":".*(第十季|第拾季).*","replacement":"S10","offset":0,"example":"xxx第十季xxx\nxxx第拾季xxx","remark":"第十季 | 第拾季","author":"LuckyPuppy514"},{"id":"62FD5691579946F5A651CD000E4CE06D","token":"episode","priority":2000,"regex":".*第0*(\\d+)(集|话|話).*","replacement":"E$1","offset":0,"example":"xxx第015集xxx\nxxx第016话xxx\nxxx第017話xxx","remark":"第01集 | 第01话 | 第01話 ......","author":"LuckyPuppy514"},{"id":"1993DBAD1C654CB1A97B0C6D39A8A17A","token":"episode","priority":2100,"regex":".*(\\b|\\d)((?i)E|EP|Episode)0*(\\d+)\\b.*","replacement":"E$3","offset":0,"example":"xxx E03 xxx\nxxx EP03 xxx\nxxx Episode03 xxx\nxxx S02E03 xxx","remark":"E01 | EP01 | Episode01 | S02E01 ......","author":"LuckyPuppy514"},{"id":"3F3A754426CD4D0E923B1A12F6E34871","token":"episode","priority":1100,"regex":".*(\\b|\\d)((?i)E|EP|Episode)0*(\\d+)(-|~)(S\\d+|)((?i)E|EP|Episode|)0*(\\d+)\\b.*","replacement":"E$3-E$7","offset":0,"example":"xxx E01-02 xxx\nxxx E01-E02 xxx\nxxx S02E01-E02 xxx\nxxx S02E01-S02E02 xxx","remark":"E01-02 | E01~E02 | S02E01-E02 | S02E01-S02E02 ......","author":"LuckyPuppy514"},{"id":"1CC25DA511A24C6E9836954C151A5179","token":"episode","priority":1000,"regex":".*第0*(\\d+)[^\\d]0*(\\d+)(集|话|話).*","replacement":"E$1-E$2","offset":0,"example":"xxx第01-02集xxx\nxxx第01~02话xxx\nxxx第01_02话xxx\n","remark":"第01-02集 | 第01~02 话 | 第01_02話 ......","author":"LuckyPuppy514"},{"id":"9805F010DE284C9BB3B547CA45BDAADD","token":"episode","priority":2200,"regex":".*\\[0*(\\d{1,3}|1[0-8]\\d{2})(|(?i)v\\d|_v\\d)(集|话|話| END|)]*\\].*","replacement":"E$1","offset":0,"example":"xxx[01]xxx\nxxx[01集]xxx\nxxx[01v2]xxx\nxxx[1899_v2]xxx","remark":"[01] | [01集] | [01v2] | [1899_v2] ......","author":"LuckyPuppy514"},{"id":"E306A2242A0C4951B334D9237382739B","token":"episode","priority":1300,"regex":".*【[^】]*0*(\\d{1,3}|1[0-8]\\d{2})(-|~)0*(\\d{1,3}|1[0-8]\\d{2})(|[^\\d][^】]*)】.*","replacement":"E$1-E$3","offset":0,"example":"xxx【01-02】xxx\nxxx【01~02】xxx\nxxx【TV 01-02】xxx\nxxx【TV 01-1899 Fin】xxx","remark":"【01-02】|【01~02】|【TV 01-02】|【TV 01-1899 Fin】......","author":"LuckyPuppy514"},{"id":"703FB6AECEE544C3A376474A689B45BF","token":"episode","priority":2400,"regex":".* 0*(\\d{1,3}|1[0-8]\\d{2})( .*|$)","replacement":"E$1","offset":0,"example":"xxx 01 xxx\nxxx 1899 xxx","remark":"01 | 1899 ......","author":"LuckyPuppy514"},{"id":"4D4A6759A13E409DA15A1938328DCD2A","token":"episode","priority":1400,"regex":".*\\b0*(\\d{1,3}|1[0-8]\\d{2})(-|~)0*(\\d{1,3}|1[0-8]\\d{2})\\b.*","replacement":"E$1-E$3","offset":0,"example":"xxx 01-02 xxx\nxxx 01~02 xxx\nxxx 01-1899 xxx","remark":"01-02 | 01~02 | 01-1899 ......","author":"LuckyPuppy514"},{"id":"3AD5D4E8E0A44F15ABAA05E598F70CEB","token":"season","priority":2000,"regex":".*\\b0*(\\d{1,2})( - | )0*(\\d{1,3}|1[0-8]\\d{2})\\b.*","replacement":"S$1","offset":0,"example":"xxx 01 - 01 xxx\nxxx 01 01 xxx\nxxx 99 1899 xxx","remark":"01 - 01 | 01 01 | 99 1899 ......","author":"LuckyPuppy514"},{"id":"016EB2A2B6184CDDBB10468B9036DF60","token":"episode","priority":1999,"regex":".*\\[SickeClipse\\].*_0*(\\d{1,3}|1[0-8]\\d{0,2})_.*","replacement":"E$1","offset":0,"example":"[SickeClipse]xxx_01_xxx\n[SickeClipse]xxx_1899_xxx","remark":"[SickeClipse] _01_ | [SickeClipse] _1899_ ......","author":"LuckyPuppy514"},{"id":"5FAB6211D0BA48D3A77D0215D64BC5A5","token":"episode","priority":999999,"regex":".+","replacement":"","offset":0,"example":"xxxxxx","remark":"unknown = ignore","author":"LuckyPuppy514"},{"id":"D0424165776B45E4927C698E1FE0E856","token":"language","priority":1100,"regex":".*(繁简|简繁|简日|日简|繁日|日繁).*","replacement":"[$1]","offset":0,"example":"xxx繁日xxx\nxxx简日xxx\nxxx繁简xxx\nxxx简繁xxx","remark":"简繁 | 间日 | 繁日","author":"LuckyPuppy514"},{"id":"32F62461B8124572A53602A4B76BE70A","token":"language","priority":1200,"regex":".*(简体|简中|简|(?i)\\bCHS\\b).*","replacement":"[简中]","offset":0,"example":"xxx简体xxx\nxxx简中xxx\nxxx简xxx\nxxx CHS xxx\nxxx chs xxx","remark":"简体 | 简中 | 简 | CHS","author":"LuckyPuppy514"},{"id":"01616775F99C484CBC6BF79A3FBE513E","token":"language","priority":1300,"regex":".*(繁体|繁中|繁|(?i)\\bCHT\\b|zh-TW|zh-HK|zh-SG).*","replacement":"[繁中]","offset":0,"example":"xxx繁xxx\nxxx CHT xxx\nxxx cht xxx\nxxxzh-TWxxx\nxxxzh-HKxxx\nxxxzh-SGxxx","remark":"繁体 | 繁中 | 繁 | CHT | zh-TW | zh-HK | zh-SG","author":"LuckyPuppy514"},{"id":"CC8FAC9272334EBDADE2C3BAE33BCBC7","token":"language","priority":1400,"regex":".*(中文|汉语|(?i)Chinese|zh-CN).*","replacement":"[中文]","offset":0,"example":"xxx中文xxx\nxxx汉语xxx\nxxxChinesexxx\nxxxzh-CNxxx","remark":"中文 | 汉语 | Chinese | zh-CN","author":"LuckyPuppy514"},{"id":"3D1B02EC745443B9916894D1EEFDF74D","token":"resolution","priority":1100,"regex":".*((?i)1280X720|720P|\\bSD\\b|标清|\\b720\\b).*","replacement":"[720P]","offset":0,"example":"xxx 1280X720 xxx\nxxx 720P xxx\nxxx SD xxx\nxxx 标清 xxx\nxxx 720 xxx\n【幻櫻字幕組】【4月新番】【打工吧!魔王大人_Hataraku Maou-sama】【08】【BIG5_MP4】【1280X720】","remark":"1280X720 | 720P| SD | 标清 | 720","author":"LuckyPuppy514"},{"id":"F0B2B567595B439B9731062453D13D9D","token":"resolution","priority":1000,"regex":".*((?i)1920X1080|1080P|\\bHD\\b|Full-HD|高清|\\b1080\\b|\\b1K\\b).*","replacement":"[1080P]","offset":0,"example":"xxx1920X1080xxx\nxxx1080Pxxx\nxxx HD xxx\nxxx高清xxx\nxxx 1080 xxx\n[BDRip_Full-HD_AVC]\nxxx 1K xxx","remark":"1920X1080 | 1080P | HD | Full-HD | 高清 | 1080 | 1K","author":"LuckyPuppy514"},{"id":"802F223555604889874EE1E918CEEE9F","token":"resolution","priority":1300,"regex":".*((?i)2560X1440|1440P|\\b2K\\b|\\b1440\\b).*","replacement":"[1440P]","offset":0,"example":"xxx 2560X1440 xxx\nxxx 1440P xxx\nxxx 2K xxx\nxxx 1440 xxx","remark":"2560X1440 | 1440P | 2K | 1440","author":"LuckyPuppy514"},{"id":"F0B8685B8BD44A349CD1D71FAD9262E2","token":"resolution","priority":1200,"regex":".*((?i)3840x2160|2160P|\\b4K\\b|\\bUHD\\b|超高清|\\b2160\\b).*","replacement":"[2160P]","offset":0,"example":"xxx3840x2160xxx\nxxx2160Pxxx\nxxx 4K xxx\nxxx UHD xxx\nxxx超高清xxx\nxxx 2160 xxx","remark":"3840x2160 | 2160P| 4K | UHD | 超高清 | 2160","author":"LuckyPuppy514"},{"id":"419DC749C479440185FC8F8E1BDD04EE","token":"quality","priority":2100,"regex":".*((?i)\\bCAM\\b|\\bTELESYNC\\b|\\bTELECINE\\b).*","replacement":"[CAM]","offset":0,"example":"xxx CAM xxx\nxxx TELESYNC xxx\nxxx TELECINE xxx","remark":"CAM | TELESYNC | TELECINE","author":"LuckyPuppy514"},{"id":"E44AD59943A64E3587DAA6E60B236BC7","token":"quality","priority":2000,"regex":".*((?i)\\bSDTV\\b).*","replacement":"[SDTV]","offset":0,"example":"xxx SDTV xxx","remark":"SDTV","author":"LuckyPuppy514"},{"id":"CAA6E3718F284B3E9087D25FCC681BA6","token":"quality","priority":1100,"regex":".*((?i)\\bHDTV\\b).*","replacement":"[HDTV]","offset":0,"example":"xxx HDTV xxx","remark":"HDTV","author":"LuckyPuppy514"},{"id":"B8A66E6AFB5D40EDA74A964ADB560DC9","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?DL\\b).*","replacement":"[WEBDL]","offset":0,"example":"xxx WEBDL xxx\nxxx WEB-DL xxx","remark":"WEBDL | WEB-DL","author":"LuckyPuppy514"},{"id":"0C4678B2749C496480EA805A5112DE52","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?RIP\\b).*","replacement":"[WEBRip]","offset":0,"example":"xxx WEBRip xxx\nxxx WEB-Rip xxx","remark":"WEBRip | WEB-Rip","author":"LuckyPuppy514"},{"id":"29D8FC56D1A0462B8D158DEE61A9418C","token":"quality","priority":1400,"regex":".*((?i)\\bRAW.?HD\\b).*","replacement":"[Raw-HD]","offset":0,"example":"xxx RawHD xxx\nxxx Raw-HD xxx","remark":"RawHD | Raw-HD","author":"LuckyPuppy514"},{"id":"52A87D6958E848DFA1441E68143EC00C","token":"quality","priority":1500,"regex":".*((?i)\\bBluray.?Remux\\b).*","replacement":"[Bluray-Remux]","offset":0,"example":"xxx BlurayRemux xxx\nxxx Bluray-Remux xxx","remark":"BlurayRemux | Bluray-Remux","author":"LuckyPuppy514"},{"id":"B8CC5016E5F64040B073E768864A017D","token":"quality","priority":1200,"regex":".*((?i)\\bBluray\\b).*","replacement":"[Bluray]","offset":0,"example":"xxx Bluray xxx","remark":"Bluray","author":"LuckyPuppy514"},{"id":"7318BF3B11FB414892978952883575E5","token":"quality","priority":1300,"regex":".*((?i)\\bRemux\\b).*","replacement":"[Remux]","offset":0,"example":"xxx Remux xxx","remark":"Remux","author":"LuckyPuppy514"},{"id":"52F4F33130AA4A92A3170E048AAAECE6","token":"group","priority":1300,"regex":"^\\s*\\[([^]]+)\\].+","replacement":"[$1]","offset":0,"example":"[Group Name] xxx","remark":"[Group Name]","author":"LuckyPuppy514"},{"id":"B13F8B7DBC31454DB5433882BEC6673C","token":"group","priority":1400,"regex":"^\\s*【([^】]+)】.+","replacement":"[$1]","offset":0,"example":"【发布组名称】xxx","remark":"【发布组名称】","author":"LuckyPuppy514"},{"id":"ECE3FC67F60647B4BF025476E3BE31A2","token":"group","priority":1200,"regex":".*(\\[|\\(|【|(| )([^ ]+(字幕组|字幕組|字幕社|发布组|发布組|汉化组|汉化組|压制组|压制組)).*","replacement":"[$2]","offset":0,"example":"[KRL字幕組] xxxxxxxxxxxxx\n[北宇治字幕组] xxxxxxxxxxxxx\n【北宇治字幕组】 xxxxxxxxxxxxx\nxxxxxxxxxxxxx [悠哈璃羽字幕社] xxxxxxxxxxxxx\n[豌豆字幕组&风之圣殿字幕组] xxxxxxxxxxx\n[爱恋字幕社&波子汽水汉化组] xxxxxxxxxxxxx","remark":"字幕组 | 字幕組 | 字幕社 | 发布组 | 发布組 | 汉化组 | 汉化組 | 压制组 | 压制組","author":"LuckyPuppy514"},{"id":"DED9BE2BB9734B5EBA21CC281AE371C5","token":"group","priority":1100,"regex":".*\\b(ANi|GM-Team|Lilith-Raws|LoliHouse|MMSUB|NC-Raws|NaN-Raws|OPFans枫雪动漫|OPFans楓雪動漫|PorterRAWS|SickeClipse|Skymoon-Raws|SweetSub|XK SPIRITS|c.c動漫|喵萌Production|喵萌奶茶屋|猎户不鸽压制|猎户随缘压制|神楽坂 まひろ|离谱Sub|Sakurato)\\b.*","replacement":"[$1]","offset":0,"example":"xxx ANi xxx\nxxx GM-Team xxx\nxxx Lilith-Raws xxx\nxxx OPFans楓雪動漫 xxx","remark":"ANi | GM-Team | Lilith-Raws | LoliHouse ......","author":"LuckyPuppy514"},{"id":"CE743DB48D16408FA81F5941274525F1","token":"season","priority":999999,"regex":".+","replacement":"S1","offset":0,"example":"xxxxxx","remark":"Default = S1","author":"LuckyPuppy514"},{"id":"62A1C2FF54E64B5B9ACE4C1DA0670F1E","token":"episode","priority":1200,"regex":".*\\[[^]]*0*(\\d{1,3}|1[0-8]\\d{2})(-|~)0*(\\d{1,3}|1[0-8]\\d{2})(|[^\\d][^]]*)\\].*","replacement":"E$1-E$3","offset":0,"example":"xxx[01-02]xxx\nxxx[01~02]xxx\nxxx[TV 01-02]xxx\nxxx[TV 01-1899 Fin]xxx","remark":"[01-02] | [01~02] | [TV 01-02] | [TV 01-1899 Fin] ......","author":"LuckyPuppy514"},{"id":"7E5FCBE09C1742649BA223120CCBBD5E","token":"language","priority":1000,"regex":"(?=.*简)(?=.*日)(?=.*繁).*","replacement":"[简繁日]","offset":0,"example":"xxx简日繁xxx\nxxx简繁日xxx\nxxx日简繁xxx","remark":"简繁日","author":"LuckyPuppy514"},{"id":"F2B453DB500D4A0C801B3BDAEDA27BF6","token":"season","priority":999,"regex":".*(死神.*千年血战|死神.*千年血戰).*","replacement":"S2","offset":0,"example":"xxx 死神 千年血战 xxx\nxxx 死神 千年血戰 xxx","remark":"死神 千年血战篇","author":"LuckyPuppy514"},{"id":"9AC7703D6A6149CCADCDB741BD60A2AD","token":"season","priority":1300,"regex":".*\\b([1-9])((?i)(st|nd|rd|th)\\s*Season)\\b.*","replacement":"S$1","offset":0,"example":"xxx 1st Season xxx\nxxx 4th Season xxx","remark":"1st Season | 2nd Season | 9th Season ......","author":"LuckyPuppy514"},{"id":"1E4BC53829E447328EB15E60B5F0529C","token":"season","priority":2109,"regex":".*Ⅰ.*","replacement":"S1","offset":0,"example":"xxxⅠxxx","remark":"Ⅰ","author":"LuckyPuppy514"},{"id":"2D0782716EE741DC970E6C8634EF968A","token":"season","priority":2100,"regex":".*(Ⅱ|\\bII\\b).*","replacement":"S2","offset":0,"example":"xxxⅡxxx\nxxx II xxx","remark":"Ⅱ | II","author":"LuckyPuppy514"},{"id":"8A4CD5A87E0141A6BE3142C709ADD731","token":"season","priority":2101,"regex":".*(Ⅲ|\\bIII\\b).*","replacement":"S3","offset":0,"example":"xxxⅢxxx\nxxx III xxx","remark":"Ⅲ | III","author":"LuckyPuppy514"},{"id":"EAB4B3B2D4C74483A08B7A8C3769E887","token":"season","priority":2102,"regex":".*(Ⅳ|\\bIV\\b).*","replacement":"S4","offset":0,"example":"xxxⅣxxx\nxxx IV xxx","remark":"Ⅳ | IV","author":"LuckyPuppy514"},{"id":"C53964A936794F99BB5F8FE53D715723","token":"season","priority":2103,"regex":".*Ⅴ.*","replacement":"S5","offset":0,"example":"xxxⅤxxx","remark":"Ⅴ","author":"LuckyPuppy514"},{"id":"FDCF7B24E2B044DC9AB2083017247517","token":"season","priority":2104,"regex":".*(Ⅵ|\\bVI\\b).*","replacement":"S6","offset":0,"example":"xxxⅥxxx\nxxx VI xxx","remark":"Ⅵ | VI","author":"LuckyPuppy514"},{"id":"09040C8B0ADF4D74B7506F2D79EA9571","token":"season","priority":2105,"regex":".*(Ⅶ|\\bVII\\b).*","replacement":"S7","offset":0,"example":"xxxⅦxxx\nxxx VII xxx","remark":"Ⅶ | VII","author":"LuckyPuppy514"},{"id":"EC553FA385C5454B9B2DB967EC02067E","token":"season","priority":2106,"regex":".*(Ⅷ|\\bVIII\\b).*","replacement":"S8","offset":0,"example":"xxxⅧxxx\nxxx VIII xxx","remark":"Ⅷ | VIII","author":"LuckyPuppy514"},{"id":"9DA462C04E7C4FFD8C8109DA969C9F09","token":"season","priority":2107,"regex":".*(Ⅸ|\\bIX\\b).*","replacement":"S9","offset":0,"example":"xxxⅨxxx\nxxx IX xxx","remark":"Ⅸ | IX","author":"LuckyPuppy514"},{"id":"D9297421B03C4D43B182ADE8179B3205","token":"season","priority":2108,"regex":".*Ⅹ.*","replacement":"S10","offset":0,"example":"xxxⅩxxx","remark":"Ⅹ","author":"LuckyPuppy514"},{"id":"6FC4770FE7EE495DB77C5DB3B166511A","token":"episode","priority":2300,"regex":".*【0*(\\d{1,3}|1[0-8]\\d{2})(|(?i)v\\d|_v\\d)(集|话|話|)]*】.*","replacement":"E$1","offset":0,"example":"xxx【01】xxx\nxxx【01话】xxx\nxxx【1899v2】xxx","remark":"【01】|【01集】|【01v2】|【01_v2】|【1899话】......","author":"LuckyPuppy514"},{"id":"160235AB3F7B4094842F5436F6F42C8F","token":"resolution","priority":1500,"regex":".*((?i)854X480|480P|\\b480\\b).*","replacement":"[480P]","offset":0,"example":"xxx854X480xxx\nxxx480Pxxx\nxxx480xxx","remark":"854X480 | 480P | 480","author":"LuckyPuppy514"},{"id":"9E79CCEC074A4CAD8AC8DE227E398FFC","token":"resolution","priority":1400,"regex":".*((?i)960X540|540P|\\b540\\b).*","replacement":"[540P]","offset":0,"example":"xxx960X540xxx\nxxx540Pxxx\nxxx 540 xxx","remark":"960X540 | 540P | 540","author":"LuckyPuppy514"},{"id":"64EF964B7ED4422AA67893B652BE0D1D","token":"resolution","priority":1600,"regex":".*((?i)640X360|360P|\\b360\\b).*","replacement":"[360P]","offset":0,"example":"xxx640X360xxx\nxxx360Pxxx\nxxx 360 xxx","remark":"640X360 | 360P | 360","author":"LuckyPuppy514"},{"id":"8622CBF30DE5420690637CCA83C0B941","token":"dynamic_range","priority":1000,"regex":".*\\b((?i)HDR10|HDR|SDR)\\b.*","replacement":"[$1]","offset":0,"example":"xxx HDR10 xxx\nxxx HDR xxx\nxxx SDR xxx","remark":"HDR10 | HDR | SDR","author":"LuckyPuppy514"},{"id":"09DD664F333D4B1683CE1EA75B681F10","token":"bit","priority":1000,"regex":".*\\b((?i)10bit|8bit)s?\\b.*","replacement":"[$1]","offset":0,"example":"xxx 10bit xxx\nxxx 8bit xxx","remark":"10bit | 8bit","author":"LuckyPuppy514"},{"id":"D606BF155E3943559607BE56C40E3990","token":"language","priority":2100,"regex":".+-ADWeb$","replacement":"[简中]","offset":0,"example":"A Record of a Mortal's Journey to Immortality S01 2020 1080p WEB-DL H264 AAC-ADWeb\n Fen Jie Xian S01E05-E06 2022 1080p WEB-DL H265 AAC-ADWeb\nLost In The Kunlun Mountains S01E13-14 2022 2160p WEB-DL H265 AAC-ADWeb\nHuanmeng Shan Hai Yao S01E07 2022 1080p WEB-DL H265 AAC-ADWeb\nOne Piece E1029 1999 1080p WEB-DL H264 AAC-ADWeb","remark":"ADWeb","author":"LuckyPuppy514"},{"id":"1DEDD0BD47074CCFA048F6BE08B2D15D","token":"quality","priority":1600,"regex":".*(BD.?Rip).*","replacement":"[BDRip]","offset":0,"example":"xxx BDRip xxx\nxxx BD-Rip xxx","remark":"BDRip | BD-Rip","author":"LuckyPuppy514"},{"id":"38B8F077EF4E4DC799166AD8A25FA3A9","token":"language","priority":2000,"regex":".*\\b(Baha|baha)\\b.*","replacement":"[繁中]","offset":0,"example":"xxx Baha xxx\nxxx baha xxx","remark":"Baha | baha","author":"LuckyPuppy514"},{"id":"74BE48EA58B541BBAA7382E377594975","token":"group","priority":1000,"regex":".*(喵萌奶茶屋&LoliHouse|离谱Sub&DBD-Raws|悠哈璃羽字幕社&LoliHouse|动漫国字幕组&LoliHouse|极影字幕社&ANK-Raws|豌豆字幕组&LoliHouse).*","replacement":"[$1]","offset":0,"example":"xxx喵萌奶茶屋&LoliHousexxx\nxxx离谱Sub&DBD-Rawsxxx\nxxx悠哈璃羽字幕社&LoliHousexxx","remark":"喵萌奶茶屋&LoliHouse | 离谱Sub&DBD-Raws | 悠哈璃羽字幕社&LoliHouse ......","author":"LuckyPuppy514"},{"id":"117871426A4044BE977420CCE661EC88","token":"subtitle","priority":1000,"regex":".*(内嵌|外挂).*","replacement":"[$1]","offset":0,"example":"xxx内嵌xxx\nxxx外挂xxx","remark":"内嵌 | 外挂","author":"LuckyPuppy514"},{"id":"34D9195DE802451196F87408F7A8302E","token":"season","priority":1000,"regex":".*第0*(\\d+)季.*","replacement":"S$1","offset":0,"example":"xxx第2季xxx\nxxx第03季xxx","remark":"第2季 | 第02季 ......","author":"LuckyPuppy514"},{"id":"53CAF512D9D54D36B9B5F4EAF0059F4F","token":"season","priority":1200,"regex":".*\\b((?i)S|Season)0*(\\d+)((?i)E\\d+|EP\\d+|Episode\\d+|)\\b.*","replacement":"S$2","offset":0,"example":"xxx S02 xxx\nxxx S02E03 xxx\nxxx s02 xxx\nxxx s02e03 xxx","remark":"S02 | S02E03 | S02EP03 | S02Episode03 ......","author":"LuckyPuppy514"},{"id":"87FC2BE8FBE54D3680639CAFE2204F23","token":"season","priority":1100,"regex":".*(第二季|第贰季).*","replacement":"S2","offset":0,"example":"xxx第二季xxx\nxxx第贰季xxx","remark":"第二季 | 第贰季","author":"LuckyPuppy514"},{"id":"CEDCA54CF4FA4D9B8A860C56E482EBA3","token":"season","priority":1101,"regex":".*(第三季|第叁季).*","replacement":"S3","offset":0,"example":"xxx第三季xxx\nxxx第叁季xxx","remark":"第三季 | 第叁季","author":"LuckyPuppy514"},{"id":"FB8711F9F4F24E7BB191E2EFA8970BFE","token":"season","priority":1102,"regex":".*(第四季|第肆季).*","replacement":"S4","offset":0,"example":"xxx第四季xxx\nxxx第肆季xxx","remark":"第四季 | 第肆季","author":"LuckyPuppy514"},{"id":"A3D37164634F4B33A1F1E0941DC5CFF0","token":"season","priority":1103,"regex":".*(第五季|第伍季).*","replacement":"S5","offset":0,"example":"xxx第五季xxx\nxxx第伍季xxx\nxxx5th Seasonxxx\nxxxⅤxxx\nxxx V xxx","remark":"第五季 | 第伍季","author":"LuckyPuppy514"},{"id":"E84E8520957D4ED798F5F0D473EAD2E0","token":"season","priority":1104,"regex":".*(第六季|第陆季).*","replacement":"S6","offset":0,"example":"xxx第六季xxx\nxxx第陆季xxx","remark":"第六季 | 第陆季","author":"LuckyPuppy514"},{"id":"B1535A31C25F4A42A21EF6C8D590477E","token":"season","priority":1105,"regex":".*(第七季|第柒季).*","replacement":"S7","offset":0,"example":"xxx第七季xxx\nxxx第柒季xxx","remark":"第七季 | 第柒季","author":"LuckyPuppy514"},{"id":"5FFEBF0FFE924263BB28BB13F05E31D8","token":"season","priority":1109,"regex":".*(第一季|第壹季).*","replacement":"S1","offset":0,"example":"xxx第一季xxx\nxxx第壹季xxx","remark":"第一季 | 第壹季","author":"LuckyPuppy514"},{"id":"50221375663F41EE82E48C823A493F1F","token":"original","priority":1000,"regex":"(.+)","replacement":"[$1]","offset":0,"example":"Scream 2022 2160p UHD BluRay x265 10bit DoVi mUHD-FRDS","remark":"保留所有原始信息","author":"LuckyPuppy514"}] \ No newline at end of file +[{"id":"00000000000000000000000000000000","token":"title","priority":999,"regex":".*\\b{cleanTitle}\\b.*","replacement":"{mainTitle}","offset":0,"example":"","remark":"\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB\uD83D\uDEAB","author":"LuckyPuppy514"},{"id":"310B92F87CC247FCA579522DE3E5DF67","token":"season","priority":1106,"regex":".*(第八季|第捌季).*","replacement":"S8","offset":0,"example":"xxx第八季xxx\nxxx第捌季xxx","remark":"第八季 | 第捌季","author":"LuckyPuppy514"},{"id":"E4964E6099B54FC688F3BEFF6D30CA7A","token":"season","priority":1107,"regex":".*(第九季|第玖季).*","replacement":"S9","offset":0,"example":"xxx第九季xxx\nxxx第玖季xxx","remark":"第九季 | 第玖季","author":"LuckyPuppy514"},{"id":"4722BC9AF4854DE3BCD75F2AE8F6F9BD","token":"season","priority":1108,"regex":".*(第十季|第拾季).*","replacement":"S10","offset":0,"example":"xxx第十季xxx\nxxx第拾季xxx","remark":"第十季 | 第拾季","author":"LuckyPuppy514"},{"id":"62FD5691579946F5A651CD000E4CE06D","token":"episode","priority":2000,"regex":".*第0*(\\d+)(集|话|話).*","replacement":"E$1","offset":0,"example":"xxx第015集xxx\nxxx第016话xxx\nxxx第017話xxx","remark":"第01集 | 第01话 | 第01話 ......","author":"LuckyPuppy514"},{"id":"1993DBAD1C654CB1A97B0C6D39A8A17A","token":"episode","priority":2100,"regex":".*(\\b|\\d)((?i)E|EP|Episode)0*(\\d+)\\b.*","replacement":"E$3","offset":0,"example":"xxx E03 xxx\nxxx EP03 xxx\nxxx Episode03 xxx\nxxx S02E03 xxx","remark":"E01 | EP01 | Episode01 | S02E01 ......","author":"LuckyPuppy514"},{"id":"3F3A754426CD4D0E923B1A12F6E34871","token":"episode","priority":1100,"regex":".*(\\b|\\d)((?i)E|EP|Episode)0*(\\d+)(-|~)(S\\d+|)((?i)E|EP|Episode|)0*(\\d+)\\b.*","replacement":"E$3-E$7","offset":0,"example":"xxx E01-02 xxx\nxxx E01-E02 xxx\nxxx S02E01-E02 xxx\nxxx S02E01-S02E02 xxx","remark":"E01-02 | E01~E02 | S02E01-E02 | S02E01-S02E02 ......","author":"LuckyPuppy514"},{"id":"1CC25DA511A24C6E9836954C151A5179","token":"episode","priority":1000,"regex":".*第0*(\\d+)[^\\d]0*(\\d+)(集|话|話).*","replacement":"E$1-E$2","offset":0,"example":"xxx第01-02集xxx\nxxx第01~02话xxx\nxxx第01_02话xxx\n","remark":"第01-02集 | 第01~02 话 | 第01_02話 ......","author":"LuckyPuppy514"},{"id":"9805F010DE284C9BB3B547CA45BDAADD","token":"episode","priority":2200,"regex":".*\\[0*(\\d{1,3}|1[0-8]\\d{2})(|(?i)v\\d|_v\\d)(集|话|話| END|)]*\\].*","replacement":"E$1","offset":0,"example":"xxx[01]xxx\nxxx[01集]xxx\nxxx[01v2]xxx\nxxx[1899_v2]xxx","remark":"[01] | [01集] | [01v2] | [1899_v2] ......","author":"LuckyPuppy514"},{"id":"E306A2242A0C4951B334D9237382739B","token":"episode","priority":1300,"regex":".*【[^】]*0*(\\d{1,3}|1[0-8]\\d{2})(-|~)0*(\\d{1,3}|1[0-8]\\d{2})(|[^\\d][^】]*)】.*","replacement":"E$1-E$3","offset":0,"example":"xxx【01-02】xxx\nxxx【01~02】xxx\nxxx【TV 01-02】xxx\nxxx【TV 01-1899 Fin】xxx","remark":"【01-02】|【01~02】|【TV 01-02】|【TV 01-1899 Fin】......","author":"LuckyPuppy514"},{"id":"703FB6AECEE544C3A376474A689B45BF","token":"episode","priority":2400,"regex":".* 0*(\\d{1,3}|1[0-8]\\d{2})( .*|$)","replacement":"E$1","offset":0,"example":"xxx 01 xxx\nxxx 1899 xxx","remark":"01 | 1899 ......","author":"LuckyPuppy514"},{"id":"4D4A6759A13E409DA15A1938328DCD2A","token":"episode","priority":1400,"regex":".*\\b0*(\\d{1,3}|1[0-8]\\d{2})(-|~)0*(\\d{1,3}|1[0-8]\\d{2})\\b.*","replacement":"E$1-E$3","offset":0,"example":"xxx 01-02 xxx\nxxx 01~02 xxx\nxxx 01-1899 xxx","remark":"01-02 | 01~02 | 01-1899 ......","author":"LuckyPuppy514"},{"id":"3AD5D4E8E0A44F15ABAA05E598F70CEB","token":"season","priority":2000,"regex":".*\\b0*(\\d{1,2})( - | )0*(\\d{1,3}|1[0-8]\\d{2})\\b.*","replacement":"S$1","offset":0,"example":"xxx 01 - 01 xxx\nxxx 01 01 xxx\nxxx 99 1899 xxx","remark":"01 - 01 | 01 01 | 99 1899 ......","author":"LuckyPuppy514"},{"id":"016EB2A2B6184CDDBB10468B9036DF60","token":"episode","priority":1999,"regex":".*\\[SickeClipse\\].*_0*(\\d{1,3}|1[0-8]\\d{0,2})_.*","replacement":"E$1","offset":0,"example":"[SickeClipse]xxx_01_xxx\n[SickeClipse]xxx_1899_xxx","remark":"[SickeClipse] _01_ | [SickeClipse] _1899_ ......","author":"LuckyPuppy514"},{"id":"5FAB6211D0BA48D3A77D0215D64BC5A5","token":"episode","priority":999999,"regex":".+","replacement":"","offset":0,"example":"xxxxxx","remark":"unknown = ignore","author":"LuckyPuppy514"},{"id":"D0424165776B45E4927C698E1FE0E856","token":"language","priority":1100,"regex":".*(繁简|简繁|简日|日简|繁日|日繁).*","replacement":"[$1]","offset":0,"example":"xxx繁日xxx\nxxx简日xxx\nxxx繁简xxx\nxxx简繁xxx","remark":"简繁 | 间日 | 繁日","author":"LuckyPuppy514"},{"id":"32F62461B8124572A53602A4B76BE70A","token":"language","priority":1200,"regex":".*(简体|简中|简|(?i)\\bCHS\\b).*","replacement":"[简中]","offset":0,"example":"xxx简体xxx\nxxx简中xxx\nxxx简xxx\nxxx CHS xxx\nxxx chs xxx","remark":"简体 | 简中 | 简 | CHS","author":"LuckyPuppy514"},{"id":"01616775F99C484CBC6BF79A3FBE513E","token":"language","priority":1300,"regex":".*(繁体|繁中|繁|(?i)\\bCHT\\b|zh-TW|zh-HK|zh-SG).*","replacement":"[繁中]","offset":0,"example":"xxx繁xxx\nxxx CHT xxx\nxxx cht xxx\nxxxzh-TWxxx\nxxxzh-HKxxx\nxxxzh-SGxxx","remark":"繁体 | 繁中 | 繁 | CHT | zh-TW | zh-HK | zh-SG","author":"LuckyPuppy514"},{"id":"CC8FAC9272334EBDADE2C3BAE33BCBC7","token":"language","priority":1400,"regex":".*(中文|汉语|(?i)Chinese|zh-CN).*","replacement":"[中文]","offset":0,"example":"xxx中文xxx\nxxx汉语xxx\nxxxChinesexxx\nxxxzh-CNxxx","remark":"中文 | 汉语 | Chinese | zh-CN","author":"LuckyPuppy514"},{"id":"3D1B02EC745443B9916894D1EEFDF74D","token":"resolution","priority":1100,"regex":".*((?i)1280X720|720P|\\bSD\\b|标清|\\b720\\b).*","replacement":"[720P]","offset":0,"example":"xxx 1280X720 xxx\nxxx 720P xxx\nxxx SD xxx\nxxx 标清 xxx\nxxx 720 xxx\n【幻櫻字幕組】【4月新番】【打工吧!魔王大人_Hataraku Maou-sama】【08】【BIG5_MP4】【1280X720】","remark":"1280X720 | 720P| SD | 标清 | 720","author":"LuckyPuppy514"},{"id":"F0B2B567595B439B9731062453D13D9D","token":"resolution","priority":1000,"regex":".*((?i)1920X1080|1080P|\\bHD\\b|Full-HD|高清|\\b1080\\b|\\b1K\\b).*","replacement":"[1080P]","offset":0,"example":"xxx1920X1080xxx\nxxx1080Pxxx\nxxx HD xxx\nxxx高清xxx\nxxx 1080 xxx\n[BDRip_Full-HD_AVC]\nxxx 1K xxx","remark":"1920X1080 | 1080P | HD | Full-HD | 高清 | 1080 | 1K","author":"LuckyPuppy514"},{"id":"802F223555604889874EE1E918CEEE9F","token":"resolution","priority":1300,"regex":".*((?i)2560X1440|1440P|\\b2K\\b|\\b1440\\b).*","replacement":"[1440P]","offset":0,"example":"xxx 2560X1440 xxx\nxxx 1440P xxx\nxxx 2K xxx\nxxx 1440 xxx","remark":"2560X1440 | 1440P | 2K | 1440","author":"LuckyPuppy514"},{"id":"F0B8685B8BD44A349CD1D71FAD9262E2","token":"resolution","priority":1200,"regex":".*((?i)3840x2160|2160P|\\b4K\\b|\\bUHD\\b|超高清|\\b2160\\b).*","replacement":"[2160P]","offset":0,"example":"xxx3840x2160xxx\nxxx2160Pxxx\nxxx 4K xxx\nxxx UHD xxx\nxxx超高清xxx\nxxx 2160 xxx","remark":"3840x2160 | 2160P| 4K | UHD | 超高清 | 2160","author":"LuckyPuppy514"},{"id":"419DC749C479440185FC8F8E1BDD04EE","token":"quality","priority":2100,"regex":".*((?i)\\bCAM\\b|\\bTELESYNC\\b|\\bTELECINE\\b).*","replacement":"[CAM]","offset":0,"example":"xxx CAM xxx\nxxx TELESYNC xxx\nxxx TELECINE xxx","remark":"CAM | TELESYNC | TELECINE","author":"LuckyPuppy514"},{"id":"E44AD59943A64E3587DAA6E60B236BC7","token":"quality","priority":2000,"regex":".*((?i)\\bSDTV\\b).*","replacement":"[SDTV]","offset":0,"example":"xxx SDTV xxx","remark":"SDTV","author":"LuckyPuppy514"},{"id":"CAA6E3718F284B3E9087D25FCC681BA6","token":"quality","priority":1100,"regex":".*((?i)\\bHDTV\\b).*","replacement":"[HDTV]","offset":0,"example":"xxx HDTV xxx","remark":"HDTV","author":"LuckyPuppy514"},{"id":"B8A66E6AFB5D40EDA74A964ADB560DC9","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?DL\\b).*","replacement":"[WEBDL]","offset":0,"example":"xxx WEBDL xxx\nxxx WEB-DL xxx","remark":"WEBDL | WEB-DL","author":"LuckyPuppy514"},{"id":"0C4678B2749C496480EA805A5112DE52","token":"quality","priority":1000,"regex":".*((?i)\\bWEB.?RIP\\b).*","replacement":"[WEBRip]","offset":0,"example":"xxx WEBRip xxx\nxxx WEB-Rip xxx","remark":"WEBRip | WEB-Rip","author":"LuckyPuppy514"},{"id":"29D8FC56D1A0462B8D158DEE61A9418C","token":"quality","priority":1400,"regex":".*((?i)\\bRAW.?HD\\b).*","replacement":"[Raw-HD]","offset":0,"example":"xxx RawHD xxx\nxxx Raw-HD xxx","remark":"RawHD | Raw-HD","author":"LuckyPuppy514"},{"id":"52A87D6958E848DFA1441E68143EC00C","token":"quality","priority":1500,"regex":".*((?i)\\bBluray.?Remux\\b).*","replacement":"[Bluray-Remux]","offset":0,"example":"xxx BlurayRemux xxx\nxxx Bluray-Remux xxx","remark":"BlurayRemux | Bluray-Remux","author":"LuckyPuppy514"},{"id":"B8CC5016E5F64040B073E768864A017D","token":"quality","priority":1200,"regex":".*((?i)\\bBluray\\b).*","replacement":"[Bluray]","offset":0,"example":"xxx Bluray xxx","remark":"Bluray","author":"LuckyPuppy514"},{"id":"7318BF3B11FB414892978952883575E5","token":"quality","priority":1300,"regex":".*((?i)\\bRemux\\b).*","replacement":"[Remux]","offset":0,"example":"xxx Remux xxx","remark":"Remux","author":"LuckyPuppy514"},{"id":"52F4F33130AA4A92A3170E048AAAECE6","token":"group","priority":1300,"regex":"^\\s*\\[([^]]+)\\].+","replacement":"[$1]","offset":0,"example":"[Group Name] xxx","remark":"[Group Name]","author":"LuckyPuppy514"},{"id":"B13F8B7DBC31454DB5433882BEC6673C","token":"group","priority":1400,"regex":"^\\s*【([^】]+)】.+","replacement":"[$1]","offset":0,"example":"【发布组名称】xxx","remark":"【发布组名称】","author":"LuckyPuppy514"},{"id":"ECE3FC67F60647B4BF025476E3BE31A2","token":"group","priority":1200,"regex":".*(\\[|\\(|【|(| )([^ ]+(字幕组|字幕組|字幕社|发布组|发布組|汉化组|汉化組|压制组|压制組)).*","replacement":"[$2]","offset":0,"example":"[KRL字幕組] xxxxxxxxxxxxx\n[北宇治字幕组] xxxxxxxxxxxxx\n【北宇治字幕组】 xxxxxxxxxxxxx\nxxxxxxxxxxxxx [悠哈璃羽字幕社] xxxxxxxxxxxxx\n[豌豆字幕组&风之圣殿字幕组] xxxxxxxxxxx\n[爱恋字幕社&波子汽水汉化组] xxxxxxxxxxxxx","remark":"字幕组 | 字幕組 | 字幕社 | 发布组 | 发布組 | 汉化组 | 汉化組 | 压制组 | 压制組","author":"LuckyPuppy514"},{"id":"DED9BE2BB9734B5EBA21CC281AE371C5","token":"group","priority":1100,"regex":".*\\b(ANi|GM-Team|Lilith-Raws|LoliHouse|MMSUB|NC-Raws|NaN-Raws|OPFans枫雪动漫|OPFans楓雪動漫|PorterRAWS|SickeClipse|Skymoon-Raws|SweetSub|XK SPIRITS|c.c動漫|喵萌Production|喵萌奶茶屋|猎户不鸽压制|猎户随缘压制|神楽坂 まひろ|离谱Sub|Sakurato)\\b.*","replacement":"[$1]","offset":0,"example":"xxx ANi xxx\nxxx GM-Team xxx\nxxx Lilith-Raws xxx\nxxx OPFans楓雪動漫 xxx","remark":"ANi | GM-Team | Lilith-Raws | LoliHouse ......","author":"LuckyPuppy514"},{"id":"CE743DB48D16408FA81F5941274525F1","token":"season","priority":999999,"regex":".+","replacement":"S1","offset":0,"example":"xxxxxx","remark":"Default = S1","author":"LuckyPuppy514"},{"id":"62A1C2FF54E64B5B9ACE4C1DA0670F1E","token":"episode","priority":1200,"regex":".*\\[[^]]*0*(\\d{1,3}|1[0-8]\\d{2})(-|~)0*(\\d{1,3}|1[0-8]\\d{2})(|[^\\d][^]]*)\\].*","replacement":"E$1-E$3","offset":0,"example":"xxx[01-02]xxx\nxxx[01~02]xxx\nxxx[TV 01-02]xxx\nxxx[TV 01-1899 Fin]xxx","remark":"[01-02] | [01~02] | [TV 01-02] | [TV 01-1899 Fin] ......","author":"LuckyPuppy514"},{"id":"7E5FCBE09C1742649BA223120CCBBD5E","token":"language","priority":1000,"regex":"(?=.*简)(?=.*日)(?=.*繁).*","replacement":"[简繁日]","offset":0,"example":"xxx简日繁xxx\nxxx简繁日xxx\nxxx日简繁xxx","remark":"简繁日","author":"LuckyPuppy514"},{"id":"F2B453DB500D4A0C801B3BDAEDA27BF6","token":"season","priority":999,"regex":".*(死神.*千年血战|死神.*千年血戰).*","replacement":"S2","offset":0,"example":"xxx 死神 千年血战 xxx\nxxx 死神 千年血戰 xxx","remark":"死神 千年血战篇","author":"LuckyPuppy514"},{"id":"9AC7703D6A6149CCADCDB741BD60A2AD","token":"season","priority":1300,"regex":".*\\b([1-9])((?i)(st|nd|rd|th)\\s*Season)\\b.*","replacement":"S$1","offset":0,"example":"xxx 1st Season xxx\nxxx 4th Season xxx","remark":"1st Season | 2nd Season | 9th Season ......","author":"LuckyPuppy514"},{"id":"1E4BC53829E447328EB15E60B5F0529C","token":"season","priority":2109,"regex":".*Ⅰ.*","replacement":"S1","offset":0,"example":"xxxⅠxxx","remark":"Ⅰ","author":"LuckyPuppy514"},{"id":"2D0782716EE741DC970E6C8634EF968A","token":"season","priority":2100,"regex":".*(Ⅱ|\\bII\\b).*","replacement":"S2","offset":0,"example":"xxxⅡxxx\nxxx II xxx","remark":"Ⅱ | II","author":"LuckyPuppy514"},{"id":"8A4CD5A87E0141A6BE3142C709ADD731","token":"season","priority":2101,"regex":".*(Ⅲ|\\bIII\\b).*","replacement":"S3","offset":0,"example":"xxxⅢxxx\nxxx III xxx","remark":"Ⅲ | III","author":"LuckyPuppy514"},{"id":"EAB4B3B2D4C74483A08B7A8C3769E887","token":"season","priority":2102,"regex":".*(Ⅳ|\\bIV\\b).*","replacement":"S4","offset":0,"example":"xxxⅣxxx\nxxx IV xxx","remark":"Ⅳ | IV","author":"LuckyPuppy514"},{"id":"C53964A936794F99BB5F8FE53D715723","token":"season","priority":2103,"regex":".*Ⅴ.*","replacement":"S5","offset":0,"example":"xxxⅤxxx","remark":"Ⅴ","author":"LuckyPuppy514"},{"id":"FDCF7B24E2B044DC9AB2083017247517","token":"season","priority":2104,"regex":".*(Ⅵ|\\bVI\\b).*","replacement":"S6","offset":0,"example":"xxxⅥxxx\nxxx VI xxx","remark":"Ⅵ | VI","author":"LuckyPuppy514"},{"id":"09040C8B0ADF4D74B7506F2D79EA9571","token":"season","priority":2105,"regex":".*(Ⅶ|\\bVII\\b).*","replacement":"S7","offset":0,"example":"xxxⅦxxx\nxxx VII xxx","remark":"Ⅶ | VII","author":"LuckyPuppy514"},{"id":"EC553FA385C5454B9B2DB967EC02067E","token":"season","priority":2106,"regex":".*(Ⅷ|\\bVIII\\b).*","replacement":"S8","offset":0,"example":"xxxⅧxxx\nxxx VIII xxx","remark":"Ⅷ | VIII","author":"LuckyPuppy514"},{"id":"9DA462C04E7C4FFD8C8109DA969C9F09","token":"season","priority":2107,"regex":".*(Ⅸ|\\bIX\\b).*","replacement":"S9","offset":0,"example":"xxxⅨxxx\nxxx IX xxx","remark":"Ⅸ | IX","author":"LuckyPuppy514"},{"id":"D9297421B03C4D43B182ADE8179B3205","token":"season","priority":2108,"regex":".*Ⅹ.*","replacement":"S10","offset":0,"example":"xxxⅩxxx","remark":"Ⅹ","author":"LuckyPuppy514"},{"id":"6FC4770FE7EE495DB77C5DB3B166511A","token":"episode","priority":2300,"regex":".*【0*(\\d{1,3}|1[0-8]\\d{2})(|(?i)v\\d|_v\\d)(集|话|話|)]*】.*","replacement":"E$1","offset":0,"example":"xxx【01】xxx\nxxx【01话】xxx\nxxx【1899v2】xxx","remark":"【01】|【01集】|【01v2】|【01_v2】|【1899话】......","author":"LuckyPuppy514"},{"id":"160235AB3F7B4094842F5436F6F42C8F","token":"resolution","priority":1500,"regex":".*((?i)854X480|480P|\\b480\\b).*","replacement":"[480P]","offset":0,"example":"xxx854X480xxx\nxxx480Pxxx\nxxx480xxx","remark":"854X480 | 480P | 480","author":"LuckyPuppy514"},{"id":"9E79CCEC074A4CAD8AC8DE227E398FFC","token":"resolution","priority":1400,"regex":".*((?i)960X540|540P|\\b540\\b).*","replacement":"[540P]","offset":0,"example":"xxx960X540xxx\nxxx540Pxxx\nxxx 540 xxx","remark":"960X540 | 540P | 540","author":"LuckyPuppy514"},{"id":"64EF964B7ED4422AA67893B652BE0D1D","token":"resolution","priority":1600,"regex":".*((?i)640X360|360P|\\b360\\b).*","replacement":"[360P]","offset":0,"example":"xxx640X360xxx\nxxx360Pxxx\nxxx 360 xxx","remark":"640X360 | 360P | 360","author":"LuckyPuppy514"},{"id":"8622CBF30DE5420690637CCA83C0B941","token":"dynamic_range","priority":1000,"regex":".*\\b((?i)HDR10|HDR|SDR)\\b.*","replacement":"[$1]","offset":0,"example":"xxx HDR10 xxx\nxxx HDR xxx\nxxx SDR xxx","remark":"HDR10 | HDR | SDR","author":"LuckyPuppy514"},{"id":"09DD664F333D4B1683CE1EA75B681F10","token":"bit","priority":1000,"regex":".*\\b((?i)10bit|8bit)s?\\b.*","replacement":"[$1]","offset":0,"example":"xxx 10bit xxx\nxxx 8bit xxx","remark":"10bit | 8bit","author":"LuckyPuppy514"},{"id":"D606BF155E3943559607BE56C40E3990","token":"language","priority":2100,"regex":".+-ADWeb$","replacement":"[简中]","offset":0,"example":"A Record of a Mortal's Journey to Immortality S01 2020 1080p WEB-DL H264 AAC-ADWeb\n Fen Jie Xian S01E05-E06 2022 1080p WEB-DL H265 AAC-ADWeb\nLost In The Kunlun Mountains S01E13-14 2022 2160p WEB-DL H265 AAC-ADWeb\nHuanmeng Shan Hai Yao S01E07 2022 1080p WEB-DL H265 AAC-ADWeb\nOne Piece E1029 1999 1080p WEB-DL H264 AAC-ADWeb","remark":"ADWeb","author":"LuckyPuppy514"},{"id":"1DEDD0BD47074CCFA048F6BE08B2D15D","token":"quality","priority":1600,"regex":".*(BD.?Rip).*","replacement":"[BDRip]","offset":0,"example":"xxx BDRip xxx\nxxx BD-Rip xxx","remark":"BDRip | BD-Rip","author":"LuckyPuppy514"},{"id":"38B8F077EF4E4DC799166AD8A25FA3A9","token":"language","priority":2000,"regex":".*\\b(Baha|baha)\\b.*","replacement":"[繁中]","offset":0,"example":"xxx Baha xxx\nxxx baha xxx","remark":"Baha | baha","author":"LuckyPuppy514"},{"id":"74BE48EA58B541BBAA7382E377594975","token":"group","priority":1000,"regex":".*(喵萌奶茶屋&LoliHouse|离谱Sub&DBD-Raws|悠哈璃羽字幕社&LoliHouse|动漫国字幕组&LoliHouse|极影字幕社&ANK-Raws|豌豆字幕组&LoliHouse).*","replacement":"[$1]","offset":0,"example":"xxx喵萌奶茶屋&LoliHousexxx\nxxx离谱Sub&DBD-Rawsxxx\nxxx悠哈璃羽字幕社&LoliHousexxx","remark":"喵萌奶茶屋&LoliHouse | 离谱Sub&DBD-Raws | 悠哈璃羽字幕社&LoliHouse ......","author":"LuckyPuppy514"},{"id":"117871426A4044BE977420CCE661EC88","token":"subtitle","priority":1000,"regex":".*(内嵌|外挂).*","replacement":"[$1]","offset":0,"example":"xxx内嵌xxx\nxxx外挂xxx","remark":"内嵌 | 外挂","author":"LuckyPuppy514"},{"id":"34D9195DE802451196F87408F7A8302E","token":"season","priority":1000,"regex":".*第0*(\\d+)季.*","replacement":"S$1","offset":0,"example":"xxx第2季xxx\nxxx第03季xxx","remark":"第2季 | 第02季 ......","author":"LuckyPuppy514"},{"id":"53CAF512D9D54D36B9B5F4EAF0059F4F","token":"season","priority":1200,"regex":".*\\b((?i)S|Season)0*(\\d+)((?i)E\\d+|EP\\d+|Episode\\d+|)\\b.*","replacement":"S$2","offset":0,"example":"xxx S02 xxx\nxxx S02E03 xxx\nxxx s02 xxx\nxxx s02e03 xxx","remark":"S02 | S02E03 | S02EP03 | S02Episode03 ......","author":"LuckyPuppy514"},{"id":"87FC2BE8FBE54D3680639CAFE2204F23","token":"season","priority":1100,"regex":".*(第二季|第贰季).*","replacement":"S2","offset":0,"example":"xxx第二季xxx\nxxx第贰季xxx","remark":"第二季 | 第贰季","author":"LuckyPuppy514"},{"id":"CEDCA54CF4FA4D9B8A860C56E482EBA3","token":"season","priority":1101,"regex":".*(第三季|第叁季).*","replacement":"S3","offset":0,"example":"xxx第三季xxx\nxxx第叁季xxx","remark":"第三季 | 第叁季","author":"LuckyPuppy514"},{"id":"FB8711F9F4F24E7BB191E2EFA8970BFE","token":"season","priority":1102,"regex":".*(第四季|第肆季).*","replacement":"S4","offset":0,"example":"xxx第四季xxx\nxxx第肆季xxx","remark":"第四季 | 第肆季","author":"LuckyPuppy514"},{"id":"A3D37164634F4B33A1F1E0941DC5CFF0","token":"season","priority":1103,"regex":".*(第五季|第伍季).*","replacement":"S5","offset":0,"example":"xxx第五季xxx\nxxx第伍季xxx\nxxx5th Seasonxxx\nxxxⅤxxx\nxxx V xxx","remark":"第五季 | 第伍季","author":"LuckyPuppy514"},{"id":"E84E8520957D4ED798F5F0D473EAD2E0","token":"season","priority":1104,"regex":".*(第六季|第陆季).*","replacement":"S6","offset":0,"example":"xxx第六季xxx\nxxx第陆季xxx","remark":"第六季 | 第陆季","author":"LuckyPuppy514"},{"id":"B1535A31C25F4A42A21EF6C8D590477E","token":"season","priority":1105,"regex":".*(第七季|第柒季).*","replacement":"S7","offset":0,"example":"xxx第七季xxx\nxxx第柒季xxx","remark":"第七季 | 第柒季","author":"LuckyPuppy514"},{"id":"5FFEBF0FFE924263BB28BB13F05E31D8","token":"season","priority":1109,"regex":".*(第一季|第壹季).*","replacement":"S1","offset":0,"example":"xxx第一季xxx\nxxx第壹季xxx","remark":"第一季 | 第壹季","author":"LuckyPuppy514"},{"id":"50221375663F41EE82E48C823A493F1F","token":"original","priority":1000,"regex":"(.+)","replacement":"[$1]","offset":0,"example":"Scream 2022 2160p UHD BluRay x265 10bit DoVi mUHD-FRDS","remark":"保留所有原始信息","author":"LuckyPuppy514"}] \ No newline at end of file diff --git a/src/test/java/com/lckp/jproxy/ApplicationTests.java b/src/test/java/com/lckp/jproxy/ApplicationTests.java index bff17c5..9314007 100644 --- a/src/test/java/com/lckp/jproxy/ApplicationTests.java +++ b/src/test/java/com/lckp/jproxy/ApplicationTests.java @@ -1,16 +1,16 @@ -package com.lckp.jproxy; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@SpringBootTest -class ApplicationTests { - - @Test - void test() { - log.debug("SpringBootTest"); - } -} +package com.lckp.jproxy; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@SpringBootTest +class ApplicationTests { + + @Test + void test() { + log.debug("SpringBootTest"); + } +}