Merge pull request #907 from wumode/clashruleprovider

This commit is contained in:
jxxghp
2025-10-02 20:57:53 +08:00
committed by GitHub
6 changed files with 46 additions and 31 deletions

View File

@@ -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": "修复已知问题; 使用异步调度器; 显示规则更改日期",

View File

@@ -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"
# 作者主页

View File

@@ -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"""

View File

@@ -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

View File

@@ -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]

View File

@@ -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