Merge pull request #810 from wumode/clashruleprovider

This commit is contained in:
jxxghp
2025-06-18 16:45:10 +08:00
committed by GitHub
7 changed files with 382 additions and 278 deletions

View File

@@ -412,7 +412,7 @@
},
"ToBypassTrackers": {
"name": "绕过Trackers",
"description": "提供tracker服务器IP地址列表帮助IPv6连接绕过OpenClash",
"description": "提供tracker服务器IP地址列表帮助IPv6连接绕过OpenClash",
"labels": "工具",
"version": "1.4.1",
"icon": "Clash_A.png",
@@ -448,11 +448,12 @@
"name": "Clash Rule Provider",
"description": "随时为Clash添加一些额外的规则。",
"labels": "工具",
"version": "1.0.0",
"version": "1.0.1",
"icon": "Mihomo_Meta_A.png",
"author": "wumode",
"level": 1,
"history": {
"v1.0.1": "支持规则搜索, 优化细节",
"v1.0.0": "支持: 规则分页; 导入规则; 代理组; 附加出站代理; 按区域分组",
"v0.1.0": "新增ClashRuleProvider"
}

View File

@@ -32,7 +32,7 @@ class ClashRuleProvider(_PluginBase):
# 插件图标
plugin_icon = "Mihomo_Meta_A.png"
# 插件版本
plugin_version = "1.0.0"
plugin_version = "1.0.1"
# 插件作者
plugin_author = "wumode"
# 作者主页
@@ -62,11 +62,12 @@ class ClashRuleProvider(_PluginBase):
_retry_times = 3
_filter_keywords = []
_auto_update_subscriptions = True
_ruleset_prefix = '📂<-'
_group_by_region = False
_ruleset_prefix: str = '📂<-'
_group_by_region: bool = False
_refresh_delay: int = 5
# 插件数据
_clash_config = None
_clash_config: Optional[Dict[str, Any]] = None
_top_rules: List[str] = []
_ruleset_rules: List[str] = []
_rule_provider: Dict[str, Any] = {}
@@ -97,7 +98,7 @@ class ClashRuleProvider(_PluginBase):
self._enabled = config.get("enabled")
self._proxy = config.get("proxy")
self._notify = config.get("notify"),
self._sub_links = config.get("sub_links")
self._sub_links = config.get("sub_links") or []
self._clash_dashboard_url = config.get("clash_dashboard_url")
self._clash_dashboard_secret = config.get("clash_dashboard_secret")
self._movie_pilot_url = config.get("movie_pilot_url")
@@ -110,6 +111,7 @@ class ClashRuleProvider(_PluginBase):
self._ruleset_prefix = config.get("ruleset_prefix", "Custom_")
self._auto_update_subscriptions = config.get("auto_update_subscriptions")
self._group_by_region = config.get("group_by_region")
self._refresh_delay = config.get("refresh_delay") or 5
self._clash_rule_parser = ClashRuleParser()
self._ruleset_rule_parser = ClashRuleParser()
if self._enabled:
@@ -121,6 +123,9 @@ class ClashRuleProvider(_PluginBase):
self.__parse_config()
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
self._scheduler.start()
# 更新订阅
self._scheduler.add_job(self.__refresh_subscription, "date",
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=5))
def get_state(self) -> bool:
return self._enabled
@@ -213,11 +218,11 @@ class ClashRuleProvider(_PluginBase):
},
{
"path": "/subscription",
"endpoint": self.update_subscription,
"endpoint": self.refresh_subscription,
"methods": ["PUT"],
"auth": "bear",
"summary": "update clash rules",
"description": "update clash rules"
"summary": "refresh clash configuration",
"description": "refresh clash configuration"
},
{
"path": "/rule-providers",
@@ -503,13 +508,13 @@ class ClashRuleProvider(_PluginBase):
return schemas.Response(success=False, message=f"Invalid subscription links: {self._sub_links}")
return schemas.Response(success=True, data={"url": self._sub_links[0]})
def update_subscription(self, params: Dict[str, Any]):
def refresh_subscription(self, params: Dict[str, Any]):
if not self._enabled:
return schemas.Response(success=False, message="")
url = params.get('url')
if not url:
return schemas.Response(success=False, message="missing params")
res = self.__update_subscription()
res = self.__refresh_subscription()
if not res:
return schemas.Response(success=False, message=f"订阅链接 {self._sub_links[0]} 更新失败")
return schemas.Response(success=True, message='订阅更新成功败')
@@ -693,13 +698,13 @@ class ClashRuleProvider(_PluginBase):
return res
@staticmethod
def format_bytes(bytes):
if bytes == 0:
def format_bytes(value_bytes):
if value_bytes == 0:
return '0 B'
k = 1024
sizes = ['B', 'KB', 'MB', 'GB', 'TB']
i = math.floor(math.log(bytes) / math.log(k))
return f"{bytes / math.pow(k, i):.2f} {sizes[i]}"
i = math.floor(math.log(value_bytes) / math.log(k))
return f"{value_bytes / math.pow(k, i):.2f} {sizes[i]}"
@staticmethod
def format_expire_time(timestamp):
@@ -708,7 +713,7 @@ class ClashRuleProvider(_PluginBase):
return f"{days}天后过期" if days > 0 else "已过期"
def update_subscription_service(self):
res = self.__update_subscription()
res = self.__refresh_subscription()
if res:
used = self._subscription_info['download'] + self._subscription_info['upload']
remaining = self._subscription_info['total'] - used
@@ -725,10 +730,12 @@ class ClashRuleProvider(_PluginBase):
text=f"{message}"
)
def __update_subscription(self) -> bool:
def __refresh_subscription(self) -> bool:
if not self._sub_links:
logger.error(f"Invalid links: {self._sub_links}")
return False
url = self._sub_links[0]
logger.info(f"Refreshing: {url}")
ret = RequestUtils(accept_type="text/html",
proxies=settings.PROXY if self._proxy else None
).get_res(url)
@@ -748,6 +755,8 @@ class ClashRuleProvider(_PluginBase):
self._subscription_info['total'] = variables['total']
self._subscription_info['expire'] = variables['expire']
self._subscription_info["last_update"] = int(time.time())
self._proxy_groups_by_region = ClashRuleProvider.__group_by_region(self._countries,
self._clash_config.get('proxies'))
self.save_data('subscription_info', self._subscription_info)
self.save_data('clash_config', self._clash_config)
return True
@@ -805,7 +814,8 @@ class ClashRuleProvider(_PluginBase):
def __add_notification_job(self, ruleset: str):
if ruleset in self._rule_provider:
self._scheduler.add_job(self.notify_clash, "date",
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=30),
run_date=datetime.now(
tz=pytz.timezone(settings.TZ)) + timedelta(seconds=self._refresh_delay),
args=[ruleset],
id='CRP-notify-clash',
replace_existing=True

View File

@@ -78,6 +78,7 @@ const defaultConfig = {
auto_update_subscriptions: true,
ruleset_prefix: '📂<-',
group_by_region: false,
refresh_delay: 5,
};
// 响应式配置对象
@@ -291,7 +292,7 @@ return (_ctx, _cache) => {
}, {
default: _withCtx(() => [
_createVNode(_component_v_icon, { left: "" }, {
default: _withCtx(() => _cache[19] || (_cache[19] = [
default: _withCtx(() => _cache[20] || (_cache[20] = [
_createTextVNode("mdi-close")
])),
_: 1
@@ -302,7 +303,7 @@ return (_ctx, _cache) => {
]),
default: _withCtx(() => [
_createVNode(_component_v_card_title, null, {
default: _withCtx(() => _cache[18] || (_cache[18] = [
default: _withCtx(() => _cache[19] || (_cache[19] = [
_createTextVNode("Clash Rule Provider 插件配置")
])),
_: 1
@@ -328,11 +329,11 @@ return (_ctx, _cache) => {
ref_key: "form",
ref: form,
modelValue: isFormValid.value,
"onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((isFormValid).value = $event)),
"onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ((isFormValid).value = $event)),
onSubmit: _withModifiers(saveConfig, ["prevent"])
}, {
default: _withCtx(() => [
_cache[29] || (_cache[29] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "基本设置", -1)),
_cache[31] || (_cache[31] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "基本设置", -1)),
_createVNode(_component_v_row, null, {
default: _withCtx(() => [
_createVNode(_component_v_col, {
@@ -389,7 +390,7 @@ return (_ctx, _cache) => {
]),
_: 1
}),
_cache[30] || (_cache[30] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "订阅配置", -1)),
_cache[32] || (_cache[32] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "订阅配置", -1)),
_createVNode(_component_v_row, null, {
default: _withCtx(() => [
_createVNode(_component_v_col, { cols: "12" }, {
@@ -402,7 +403,7 @@ return (_ctx, _cache) => {
multiple: "",
chips: "",
"closable-chips": "",
hint: "添加一个Clash订阅链接",
hint: "添加一个Clash订阅链接, 按回车确认输入",
"persistent-hint": "",
rules: [validateSubLinks]
}, {
@@ -455,7 +456,7 @@ return (_ctx, _cache) => {
]),
_: 1
}),
_cache[31] || (_cache[31] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "Clash 面板设置", -1)),
_cache[33] || (_cache[33] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "Clash 面板设置", -1)),
_createVNode(_component_v_row, null, {
default: _withCtx(() => [
_createVNode(_component_v_col, { cols: "12" }, {
@@ -472,7 +473,7 @@ return (_ctx, _cache) => {
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "primary" }, {
default: _withCtx(() => _cache[20] || (_cache[20] = [
default: _withCtx(() => _cache[21] || (_cache[21] = [
_createTextVNode("mdi-web")
])),
_: 1
@@ -499,7 +500,7 @@ return (_ctx, _cache) => {
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "warning" }, {
default: _withCtx(() => _cache[21] || (_cache[21] = [
default: _withCtx(() => _cache[22] || (_cache[22] = [
_createTextVNode("mdi-key")
])),
_: 1
@@ -513,7 +514,7 @@ return (_ctx, _cache) => {
]),
_: 1
}),
_cache[32] || (_cache[32] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "MoviePilot 设置", -1)),
_cache[34] || (_cache[34] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "MoviePilot 设置", -1)),
_createVNode(_component_v_row, null, {
default: _withCtx(() => [
_createVNode(_component_v_col, { cols: "12" }, {
@@ -530,7 +531,7 @@ return (_ctx, _cache) => {
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "success" }, {
default: _withCtx(() => _cache[22] || (_cache[22] = [
default: _withCtx(() => _cache[23] || (_cache[23] = [
_createTextVNode("mdi-movie")
])),
_: 1
@@ -544,7 +545,7 @@ return (_ctx, _cache) => {
]),
_: 1
}),
_cache[33] || (_cache[33] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "执行设置", -1)),
_cache[35] || (_cache[35] = _createElementVNode("div", { class: "text-subtitle-1 font-weight-bold mt-4 mb-2" }, "执行设置", -1)),
_createVNode(_component_v_row, null, {
default: _withCtx(() => [
_createVNode(_component_v_col, { cols: "12" }, {
@@ -584,7 +585,7 @@ return (_ctx, _cache) => {
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "info" }, {
default: _withCtx(() => _cache[23] || (_cache[23] = [
default: _withCtx(() => _cache[24] || (_cache[24] = [
_createTextVNode("mdi-clock-outline")
])),
_: 1
@@ -616,7 +617,7 @@ return (_ctx, _cache) => {
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "warning" }, {
default: _withCtx(() => _cache[24] || (_cache[24] = [
default: _withCtx(() => _cache[25] || (_cache[25] = [
_createTextVNode("mdi-timer")
])),
_: 1
@@ -647,7 +648,7 @@ return (_ctx, _cache) => {
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "info" }, {
default: _withCtx(() => _cache[25] || (_cache[25] = [
default: _withCtx(() => _cache[26] || (_cache[26] = [
_createTextVNode("mdi-refresh")
])),
_: 1
@@ -671,12 +672,12 @@ return (_ctx, _cache) => {
_createVNode(_component_v_expansion_panel_title, null, {
default: _withCtx(() => [
_createVNode(_component_v_icon, { class: "mr-2" }, {
default: _withCtx(() => _cache[26] || (_cache[26] = [
default: _withCtx(() => _cache[27] || (_cache[27] = [
_createTextVNode("mdi-cog")
])),
_: 1
}),
_cache[27] || (_cache[27] = _createTextVNode(" 高级选项 "))
_cache[28] || (_cache[28] = _createTextVNode(" 高级选项 "))
]),
_: 1
}),
@@ -717,8 +718,16 @@ return (_ctx, _cache) => {
}, null, 8, ["modelValue"])
]),
_: 1
}),
_createVNode(_component_v_col, { cols: "12" }, {
})
]),
_: 1
}),
_createVNode(_component_v_row, null, {
default: _withCtx(() => [
_createVNode(_component_v_col, {
cols: "12",
md: "6"
}, {
default: _withCtx(() => [
_createVNode(_component_v_text_field, {
modelValue: config.ruleset_prefix,
@@ -726,19 +735,52 @@ return (_ctx, _cache) => {
label: "规则集前缀",
variant: "outlined",
placeholder: "📂<-",
rules: [v => !!v || '规则集前缀不能为空'],
hint: "为生成的规则集添加前缀",
"persistent-hint": ""
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "info" }, {
default: _withCtx(() => _cache[28] || (_cache[28] = [
default: _withCtx(() => _cache[29] || (_cache[29] = [
_createTextVNode("mdi-prefix")
])),
_: 1
})
]),
_: 1
}, 8, ["modelValue"])
}, 8, ["modelValue", "rules"])
]),
_: 1
}),
_createVNode(_component_v_col, {
cols: "12",
md: "6"
}, {
default: _withCtx(() => [
_createVNode(_component_v_text_field, {
modelValue: config.refresh_delay,
"onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ((config.refresh_delay) = $event)),
modelModifiers: { number: true },
label: "刷新延迟",
variant: "outlined",
type: "number",
min: "1",
max: "30",
suffix: "秒",
hint: "通知Clash刷新规则集的延迟时间",
"persistent-hint": "",
rules: [v => v >= 0 || '刷新延迟不能为负数']
}, {
"prepend-inner": _withCtx(() => [
_createVNode(_component_v_icon, { color: "info" }, {
default: _withCtx(() => _cache[30] || (_cache[30] = [
_createTextVNode("mdi-clock-outline")
])),
_: 1
})
]),
_: 1
}, 8, ["modelValue", "rules"])
]),
_: 1
})
@@ -768,12 +810,12 @@ return (_ctx, _cache) => {
}, {
default: _withCtx(() => [
_createVNode(_component_v_icon, { left: "" }, {
default: _withCtx(() => _cache[34] || (_cache[34] = [
default: _withCtx(() => _cache[36] || (_cache[36] = [
_createTextVNode("mdi-view-dashboard-edit")
])),
_: 1
}),
_cache[35] || (_cache[35] = _createTextVNode(" 规则 "))
_cache[37] || (_cache[37] = _createTextVNode(" 规则 "))
]),
_: 1
}),
@@ -781,7 +823,7 @@ return (_ctx, _cache) => {
color: "secondary",
onClick: resetForm
}, {
default: _withCtx(() => _cache[36] || (_cache[36] = [
default: _withCtx(() => _cache[38] || (_cache[38] = [
_createTextVNode("重置")
])),
_: 1
@@ -791,7 +833,7 @@ return (_ctx, _cache) => {
onClick: testConnection,
loading: testing.value
}, {
default: _withCtx(() => _cache[37] || (_cache[37] = [
default: _withCtx(() => _cache[39] || (_cache[39] = [
_createTextVNode("测试连接")
])),
_: 1
@@ -803,7 +845,7 @@ return (_ctx, _cache) => {
onClick: saveConfig,
loading: saving.value
}, {
default: _withCtx(() => _cache[38] || (_cache[38] = [
default: _withCtx(() => _cache[40] || (_cache[40] = [
_createTextVNode(" 保存配置 ")
])),
_: 1
@@ -818,7 +860,7 @@ return (_ctx, _cache) => {
variant: "tonal",
closable: "",
class: "ma-4 mt-0",
"onClick:close": _cache[17] || (_cache[17] = $event => (testResult.show = false))
"onClick:close": _cache[18] || (_cache[18] = $event => (testResult.show = false))
}, {
default: _withCtx(() => [
_createElementVNode("div", _hoisted_2, [
@@ -845,6 +887,6 @@ return (_ctx, _cache) => {
}
};
const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-e9acef13"]]);
const ConfigComponent = /*#__PURE__*/_export_sfc(_sfc_main, [['__scopeId',"data-v-52f6e9ed"]]);
export { ConfigComponent as default };

View File

@@ -1,5 +1,5 @@
.plugin-config[data-v-e9acef13] {
.plugin-config[data-v-52f6e9ed] {
max-width: 800px;
margin: 0 auto;
}

View File

@@ -1,14 +1,14 @@
.plugin-page[data-v-d5896cff] {
.plugin-page[data-v-783fd7ef] {
max-width: 1200px;
margin: 0 auto;
}
/* 使卡片等宽并适应移动端 */
.d-flex.flex-wrap[data-v-d5896cff] {
.d-flex.flex-wrap[data-v-783fd7ef] {
gap: 16px;
}
.url-display[data-v-d5896cff] {
.url-display[data-v-783fd7ef] {
word-break: break-all;
padding: 8px;
background: rgba(0, 0, 0, 0.05);
@@ -17,19 +17,19 @@
/* 移动端堆叠布局 */
@media (max-width: 768px) {
.d-flex.flex-wrap[data-v-d5896cff] {
.d-flex.flex-wrap[data-v-783fd7ef] {
flex-direction: column;
}
}
/* Add visual distinction between sections */
.ruleset-section[data-v-d5896cff] {
.ruleset-section[data-v-783fd7ef] {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
background-color: #f5f5f5;
}
.top-section[data-v-d5896cff] {
.top-section[data-v-783fd7ef] {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
@@ -37,12 +37,12 @@
}
/* Optional: Add different border colors to further distinguish */
.ruleset-section[data-v-d5896cff] {
.ruleset-section[data-v-783fd7ef] {
border-left: 4px solid #2196F3; /* Blue accent */
}
.top-section[data-v-d5896cff] {
.top-section[data-v-783fd7ef] {
border-left: 4px solid #4CAF50; /* Green accent */
}
.drag-handle[data-v-d5896cff] {
.drag-handle[data-v-783fd7ef] {
cursor: move;
}

View File

@@ -2,11 +2,11 @@ const currentImports = {};
const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']);
let moduleMap = {
"./Page":()=>{
dynamicLoadingCss(["__federation_expose_Page-BiV11X52.css"], false, './Page');
return __federation_import('./__federation_expose_Page-DSmFC_QV.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
dynamicLoadingCss(["__federation_expose_Page-_CExF_DI.css"], false, './Page');
return __federation_import('./__federation_expose_Page-C2_enJ99.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
"./Config":()=>{
dynamicLoadingCss(["__federation_expose_Config-C_eVGIzn.css"], false, './Config');
return __federation_import('./__federation_expose_Config-BK6LRC9E.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
dynamicLoadingCss(["__federation_expose_Config-nL3Pv4Qs.css"], false, './Config');
return __federation_import('./__federation_expose_Config-DZF0yyTH.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
"./Dashboard":()=>{
dynamicLoadingCss([], false, './Dashboard');
return __federation_import('./__federation_expose_Dashboard-DKtydfsT.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},};