mirror of
https://github.com/jxxghp/MoviePilot-Plugins.git
synced 2026-03-27 10:05:57 +00:00
Merge pull request #907 from wumode/clashruleprovider
This commit is contained in:
@@ -485,12 +485,13 @@
|
||||
"name": "Clash Rule Provider",
|
||||
"description": "随时为Clash添加一些额外的规则。",
|
||||
"labels": "工具",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"icon": "Mihomo_Meta_A.png",
|
||||
"author": "wumode",
|
||||
"level": 1,
|
||||
"release": true,
|
||||
"history": {
|
||||
"v2.0.7": "修复子规则比较错误",
|
||||
"v2.0.6": "修复已知问题; 改进对代理组的配置和验证",
|
||||
"v2.0.5": "完善了对嵌套逻辑规则和子规则的配置和验证",
|
||||
"v2.0.4": "修复已知问题; 使用异步调度器; 显示规则更改日期",
|
||||
|
||||
@@ -30,7 +30,7 @@ class ClashRuleProvider(_ClashRuleProviderBase):
|
||||
# 插件图标
|
||||
plugin_icon = "Mihomo_Meta_A.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.0.6"
|
||||
plugin_version = "2.0.7"
|
||||
# 插件作者
|
||||
plugin_author = "wumode"
|
||||
# 作者主页
|
||||
|
||||
@@ -32,7 +32,7 @@ class ClashRuleManager:
|
||||
def export_rules(self) -> List[Dict[str, str]]:
|
||||
rules_list = []
|
||||
for rule in self.rules:
|
||||
rules_list.append({'rule': rule.rule.raw_rule, 'remark': rule.remark, 'time_modified': rule.time_modified})
|
||||
rules_list.append({'rule': str(rule.rule), 'remark': rule.remark, 'time_modified': rule.time_modified})
|
||||
return rules_list
|
||||
|
||||
def append_rules(self, clash_rules: List[RuleItem]):
|
||||
@@ -97,30 +97,10 @@ class ClashRuleManager:
|
||||
|
||||
def has_rule(self, clash_rule: Union[ClashRule, LogicRule, MatchRule]) -> bool:
|
||||
"""Check if there is an identical rule"""
|
||||
for r in self.rules:
|
||||
rule = r.rule
|
||||
if rule.rule_type != RoutingRuleType.MATCH:
|
||||
if rule.rule_type == clash_rule.rule_type and rule.action == clash_rule.action \
|
||||
and rule.payload == clash_rule.payload:
|
||||
return True
|
||||
else:
|
||||
if rule.rule_type == clash_rule.rule_type and rule.action == clash_rule.action:
|
||||
return True
|
||||
return False
|
||||
return any(r.rule == clash_rule for r in self.rules)
|
||||
|
||||
def has_rule_item(self, clash_rule: RuleItem) -> bool:
|
||||
for r in self.rules:
|
||||
if clash_rule.remark != r.remark:
|
||||
continue
|
||||
rule = r.rule
|
||||
if rule.rule_type != RoutingRuleType.MATCH:
|
||||
if rule.rule_type == clash_rule.rule.rule_type and rule.action == clash_rule.rule.action \
|
||||
and rule.payload == clash_rule.rule.payload:
|
||||
return True
|
||||
else:
|
||||
if rule.rule_type == clash_rule.rule.rule_type and rule.action == clash_rule.rule.action:
|
||||
return True
|
||||
return False
|
||||
return any(clash_rule.remark == r.remark and r.rule == clash_rule.rule for r in self.rules)
|
||||
|
||||
def reorder_rules(self, moved_priority: int, target_priority: int) -> RuleItem:
|
||||
"""Reorder the rules"""
|
||||
|
||||
@@ -24,7 +24,7 @@ class ClashRuleParser:
|
||||
# Handle regular rules
|
||||
return ClashRuleParser._parse_regular_rule(line)
|
||||
|
||||
except (ValidationError, TypeError, ValueError):
|
||||
except (ValidationError, TypeError, ValueError, RecursionError):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -120,7 +120,7 @@ class ClashRuleParser:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parenthesis_balance(s: str) -> int:
|
||||
def _parenthesis_balance(s: str) -> Optional[int]:
|
||||
"""Calculate balance of parenthesis"""
|
||||
balance = 0
|
||||
for i, char in enumerate(s):
|
||||
@@ -128,6 +128,8 @@ class ClashRuleParser:
|
||||
balance += 1
|
||||
elif char == ')':
|
||||
balance -= 1
|
||||
if balance < 0:
|
||||
return None
|
||||
return balance
|
||||
|
||||
@staticmethod
|
||||
@@ -281,7 +283,7 @@ class ClashRuleParser:
|
||||
)
|
||||
conditions.append(condition)
|
||||
except ValueError:
|
||||
continue
|
||||
raise ValueError(f"Invalid rule format: {content}")
|
||||
return conditions
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ class Action(Enum):
|
||||
PASS = "PASS"
|
||||
COMPATIBLE = "COMPATIBLE"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
class RuleBase(BaseModel):
|
||||
rule_type: RoutingRuleType
|
||||
@@ -74,6 +77,14 @@ class RuleBase(BaseModel):
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
pass
|
||||
|
||||
def __str__(self) -> str:
|
||||
pass
|
||||
|
||||
def __eq__(self, other: 'RuleBase') -> bool:
|
||||
if not isinstance(other, RuleBase):
|
||||
return NotImplemented
|
||||
return self.__str__() == other.__str__()
|
||||
|
||||
|
||||
class ClashRule(RuleBase):
|
||||
"""Represents a parsed Clash routing rule"""
|
||||
@@ -93,6 +104,18 @@ class ClashRule(RuleBase):
|
||||
'raw': self.raw_rule
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
rule_str = f"{self.condition_string()},{self.action}"
|
||||
if self.additional_params:
|
||||
rule_str += f",{self.additional_params.value}"
|
||||
return rule_str
|
||||
|
||||
@validator('payload', allow_reuse=True)
|
||||
def validate_payload(cls, v: str, values: Dict[str, Any]) -> Optional[str]:
|
||||
if values.get('rule_type') == RoutingRuleType.NETWORK and v.upper() not in ('TCP', 'UDP'):
|
||||
raise ValueError('Payload must be TCP or UDP')
|
||||
return v
|
||||
|
||||
|
||||
class LogicRule(RuleBase):
|
||||
"""Represents a logic rule (AND, OR, NOT)"""
|
||||
@@ -121,6 +144,9 @@ class LogicRule(RuleBase):
|
||||
raise ValueError('A condition list must be provided')
|
||||
return v
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.condition_string()},{self.action}"
|
||||
|
||||
|
||||
class SubRule(RuleBase):
|
||||
rule_type: Literal[RoutingRuleType.SUB_RULE] = RoutingRuleType.SUB_RULE
|
||||
@@ -128,7 +154,7 @@ class SubRule(RuleBase):
|
||||
action: str
|
||||
|
||||
def condition_string(self) -> str:
|
||||
return f"{self.rule_type.value},{self.condition.condition_string()}"
|
||||
return f"{self.rule_type.value},({self.condition.condition_string()})"
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
@@ -138,6 +164,9 @@ class SubRule(RuleBase):
|
||||
'raw': self.raw_rule
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.condition_string()},{self.action}"
|
||||
|
||||
|
||||
class MatchRule(RuleBase):
|
||||
"""Represents a match rule"""
|
||||
@@ -154,5 +183,8 @@ class MatchRule(RuleBase):
|
||||
'raw': self.raw_rule
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.condition_string()},{self.action}"
|
||||
|
||||
|
||||
RuleType = Union[ClashRule, LogicRule, SubRule, MatchRule]
|
||||
|
||||
@@ -648,7 +648,7 @@ class ClashRuleProviderService:
|
||||
if rule.payload not in config.get('rule-providers', {}):
|
||||
logger.warn(f"规则集合 {rule.payload!r} 不存在, 跳过 {rule.raw_rule!r}")
|
||||
continue
|
||||
top_rules.append(rule.raw_rule)
|
||||
top_rules.append(str(rule))
|
||||
for raw_rule in config.get("rules", []):
|
||||
rule = ClashRuleParser.parse_rule_line(raw_rule)
|
||||
if not rule:
|
||||
@@ -659,7 +659,7 @@ class ClashRuleProviderService:
|
||||
rule.rule_type != RoutingRuleType.SUB_RULE):
|
||||
logger.warn(f"出站 {rule.action!r} 不存在, 跳过 {rule.raw_rule!r}")
|
||||
continue
|
||||
top_rules.append(rule.raw_rule)
|
||||
top_rules.append(str(rule))
|
||||
config["rules"] = top_rules
|
||||
|
||||
# 添加 Hosts
|
||||
|
||||
Reference in New Issue
Block a user