url_matcher.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """
  2. URL Pattern Matching - 域名模式匹配工具
  3. 用于工具的域名过滤功能,支持 glob 模式:
  4. - *.example.com
  5. - www.example.*
  6. - https://*.example.com/path/*
  7. """
  8. import re
  9. from typing import List, Optional
  10. from urllib.parse import urlparse
  11. def normalize_pattern(pattern: str) -> str:
  12. """
  13. 规范化 URL 模式
  14. Args:
  15. pattern: URL 模式(可能包含协议、通配符等)
  16. Returns:
  17. 规范化的模式
  18. """
  19. # 如果没有协议,添加通配符协议
  20. if not pattern.startswith(("http://", "https://", "*://")):
  21. pattern = f"*://{pattern}"
  22. return pattern
  23. def pattern_to_regex(pattern: str) -> re.Pattern:
  24. """
  25. 将 glob 模式转换为正则表达式
  26. 支持的通配符:
  27. - * : 匹配任意字符(不包括 /)
  28. - ** : 匹配任意字符(包括 /)
  29. Args:
  30. pattern: glob 模式
  31. Returns:
  32. 编译后的正则表达式
  33. """
  34. # 转义正则表达式特殊字符
  35. regex = re.escape(pattern)
  36. # 替换通配符
  37. regex = regex.replace(r"\*\*", ".__DOUBLE_STAR__")
  38. regex = regex.replace(r"\*", r"[^/]*")
  39. regex = regex.replace(".__DOUBLE_STAR__", ".*")
  40. # 添加开始和结束锚点
  41. regex = f"^{regex}$"
  42. return re.compile(regex, re.IGNORECASE)
  43. def match_url_with_pattern(url: str, pattern: str) -> bool:
  44. """
  45. 检查 URL 是否匹配模式
  46. Args:
  47. url: 要检查的 URL
  48. pattern: URL 模式(支持通配符)
  49. Returns:
  50. 是否匹配
  51. Examples:
  52. >>> match_url_with_pattern("https://google.com", "*.google.com")
  53. False
  54. >>> match_url_with_pattern("https://www.google.com", "*.google.com")
  55. True
  56. >>> match_url_with_pattern("https://www.google.co.uk", "www.google.*")
  57. True
  58. >>> match_url_with_pattern("https://github.com/user/repo", "https://github.com/**")
  59. True
  60. """
  61. # 规范化模式
  62. pattern = normalize_pattern(pattern)
  63. # 解析 URL
  64. parsed_url = urlparse(url)
  65. # 构建完整 URL 字符串用于匹配
  66. url_str = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
  67. if parsed_url.query:
  68. url_str += f"?{parsed_url.query}"
  69. # 转换为正则并匹配
  70. regex = pattern_to_regex(pattern)
  71. return bool(regex.match(url_str))
  72. def match_url_with_patterns(url: str, patterns: List[str]) -> bool:
  73. """
  74. 检查 URL 是否匹配任一模式
  75. Args:
  76. url: 要检查的 URL
  77. patterns: URL 模式列表
  78. Returns:
  79. 是否匹配任一模式
  80. """
  81. return any(match_url_with_pattern(url, pattern) for pattern in patterns)
  82. def filter_by_url(
  83. items: List[dict],
  84. current_url: Optional[str],
  85. url_field: str = "url_patterns"
  86. ) -> List[dict]:
  87. """
  88. 根据 URL 过滤项目列表
  89. Args:
  90. items: 项目列表(每个包含 url_patterns 字段)
  91. current_url: 当前 URL(None = 只返回无 URL 限制的项)
  92. url_field: URL 模式字段名
  93. Returns:
  94. 过滤后的项目列表
  95. """
  96. if current_url is None:
  97. # 没有 URL 上下文,只返回无 URL 限制的项
  98. return [item for item in items if not item.get(url_field)]
  99. # 有 URL 上下文,返回匹配的项
  100. filtered = []
  101. for item in items:
  102. patterns = item.get(url_field)
  103. if patterns is None:
  104. # 无 URL 限制,总是包含
  105. filtered.append(item)
  106. elif match_url_with_patterns(current_url, patterns):
  107. # 匹配 URL,包含
  108. filtered.append(item)
  109. return filtered