mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 18:24:34 +08:00
🌴 Sync 2026-10-06 02:20:56
This commit is contained in:
1 parent
5c201dd3af
commit
487c4fad50
111 files changed
+4641
-746
No files matched your search
@@ -2,6 +2,16 @@
|
||||
|
||||
module("luci.controller.admin.substore", package.seeall)
|
||||
|
||||
-- 用户可见文案统一走 LuCI 的翻译层:msgid 一律写**英文**,译文放
|
||||
-- po/<lang>/substore.po(编译成 *.lmo 后由 luci.template.parser 按运行时语言加载)。
|
||||
--
|
||||
-- 这里显式取一份 local `_`,不依赖全局:module(..., package.seeall) 的环境里
|
||||
-- `_` 是否存在取决于 LuCI 版本(部分版本只在模板环境里注入),显式定义后
|
||||
-- 控制器在任何上下文里都能翻译,测试里也能用 stub 的 luci.i18n 跑起来。
|
||||
-- 注意必须定义在所有用到它的函数**之前** —— local 只被定义时已可见的闭包捕获。
|
||||
local i18n = require("luci.i18n")
|
||||
local function _(s) return i18n.translate(s) end
|
||||
|
||||
-- 返回列表页。err 非空时把错误带到列表页显示(§18:失败必须让用户看见,
|
||||
-- 不能「失败了却看起来像成功」)。沿用 LuCI 既有的 query + 模板渲染,不引入新 framework。
|
||||
local function back_to_list(err)
|
||||
@@ -85,14 +95,14 @@ local function post_ok()
|
||||
local http = require("luci.http")
|
||||
local tok = fv(http, "token")
|
||||
if tok == nil then
|
||||
post_fail_msg = "请求校验失败(缺少 token),请刷新页面后重试"
|
||||
post_fail_msg = _("Request validation failed (missing token), please refresh the page and try again")
|
||||
return false
|
||||
end
|
||||
local ok, authtoken = pcall(function()
|
||||
return require("luci.dispatcher").context.authtoken
|
||||
end)
|
||||
if ok and type(authtoken) == "string" and authtoken ~= "" and tok ~= authtoken then
|
||||
post_fail_msg = "请求校验失败(token 不匹配),请刷新页面后重试"
|
||||
post_fail_msg = _("Request validation failed (token mismatch), please refresh the page and try again")
|
||||
return false
|
||||
end
|
||||
post_fail_msg = nil
|
||||
@@ -171,14 +181,14 @@ function action_create()
|
||||
if proxy_enable ~= "1" then proxy_enable = "0" end
|
||||
local proxy = (fv(http, "proxy") or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
if name == "" or url == "" then
|
||||
return back_to_list("名称和 URL 不能为空")
|
||||
return back_to_list(_("Name and URL cannot be empty"))
|
||||
end
|
||||
local cron_enable, cron_time = read_cron_fields()
|
||||
-- 规则校验失败(如非法正则)必须回显,否则用户看到的是「规则没生效」
|
||||
local rules, rules_err = read_rules_fields()
|
||||
if not rules then return back_to_list(rules_err or "规则无效") end
|
||||
if not rules then return back_to_list(rules_err or _("Invalid rules")) end
|
||||
local user_agent, ua_err = read_ua_fields()
|
||||
if user_agent == nil then return back_to_list(ua_err or "订阅客户端类型无效") end
|
||||
if user_agent == nil then return back_to_list(ua_err or _("Invalid subscription client type")) end
|
||||
local id, err = core.add(name, url, {
|
||||
proxy_enable = proxy_enable, proxy = proxy, user_agent = user_agent,
|
||||
cron_enable = cron_enable, cron_time = cron_time,
|
||||
@@ -186,7 +196,7 @@ function action_create()
|
||||
keyword_include = rules.keyword_include, keyword_exclude = rules.keyword_exclude,
|
||||
dedup = rules.dedup, rename_map = rules.rename_map,
|
||||
})
|
||||
if not id then return back_to_list(err or "创建订阅失败") end
|
||||
if not id then return back_to_list(err or _("Failed to create subscription")) end
|
||||
core.write_cron()
|
||||
end
|
||||
back_to_list(post_fail_msg)
|
||||
@@ -202,16 +212,16 @@ function action_save()
|
||||
local proxy_enable = fv(http, "proxy_enable") or "0"
|
||||
if proxy_enable ~= "1" then proxy_enable = "0" end
|
||||
local proxy = (fv(http, "proxy") or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
if id == "" then return back_to_list("缺少订阅 ID") end
|
||||
if id == "" then return back_to_list(_("Missing subscription ID")) end
|
||||
if name == "" or url == "" then
|
||||
return back_to_list("名称和 URL 不能为空")
|
||||
return back_to_list(_("Name and URL cannot be empty"))
|
||||
end
|
||||
local cron_enable, cron_time = read_cron_fields()
|
||||
-- 规则校验失败(如非法正则)必须回显,否则用户看到的是「规则没生效」
|
||||
local rules, rules_err = read_rules_fields()
|
||||
if not rules then return back_to_list(rules_err or "规则无效") end
|
||||
if not rules then return back_to_list(rules_err or _("Invalid rules")) end
|
||||
local user_agent, ua_err = read_ua_fields()
|
||||
if user_agent == nil then return back_to_list(ua_err or "订阅客户端类型无效") end
|
||||
if user_agent == nil then return back_to_list(ua_err or _("Invalid subscription client type")) end
|
||||
local ok, err = core.save_meta(id, {
|
||||
name = name, url = url, proxy_enable = proxy_enable, proxy = proxy,
|
||||
user_agent = user_agent,
|
||||
@@ -220,7 +230,7 @@ function action_save()
|
||||
keyword_include = rules.keyword_include, keyword_exclude = rules.keyword_exclude,
|
||||
dedup = rules.dedup, rename_map = rules.rename_map,
|
||||
})
|
||||
if not ok then return back_to_list(err or "保存失败") end
|
||||
if not ok then return back_to_list(err or _("Save failed")) end
|
||||
core.write_cron()
|
||||
end
|
||||
back_to_list(post_fail_msg)
|
||||
@@ -235,10 +245,10 @@ function action_local_create()
|
||||
local local_mode = fv(http, "local_mode") or "text"
|
||||
-- 规则校验失败(如非法正则)必须回显,否则用户看到的是「规则没生效」
|
||||
local rules, rules_err = read_rules_fields()
|
||||
if not rules then return back_to_list(rules_err or "规则无效") end
|
||||
if not rules then return back_to_list(rules_err or _("Invalid rules")) end
|
||||
-- §19:空名称 / 空内容必须明确报错,不能无声创建无效订阅
|
||||
if name == "" then return back_to_list("名称不能为空") end
|
||||
if content == "" then return back_to_list("订阅内容不能为空") end
|
||||
if name == "" then return back_to_list(_("Name cannot be empty")) end
|
||||
if content == "" then return back_to_list(_("Subscription content cannot be empty")) end
|
||||
local id, err = core.add_local(name, content, local_mode, {
|
||||
rules_enable = rules.rules_enable,
|
||||
proto_filter = rules.proto_filter,
|
||||
@@ -246,7 +256,7 @@ function action_local_create()
|
||||
keyword_exclude = rules.keyword_exclude,
|
||||
dedup = rules.dedup,
|
||||
})
|
||||
if not id then return back_to_list(err or "创建本地订阅失败") end
|
||||
if not id then return back_to_list(err or _("Failed to create local subscription")) end
|
||||
end
|
||||
back_to_list(post_fail_msg)
|
||||
end
|
||||
@@ -261,10 +271,10 @@ function action_local_save()
|
||||
local local_mode = fv(http, "local_mode") or "text"
|
||||
-- 规则校验失败(如非法正则)必须回显,否则用户看到的是「规则没生效」
|
||||
local rules, rules_err = read_rules_fields()
|
||||
if not rules then return back_to_list(rules_err or "规则无效") end
|
||||
if id == "" then return back_to_list("缺少订阅 ID") end
|
||||
if name == "" then return back_to_list("名称不能为空") end
|
||||
if content == "" then return back_to_list("订阅内容不能为空") end
|
||||
if not rules then return back_to_list(rules_err or _("Invalid rules")) end
|
||||
if id == "" then return back_to_list(_("Missing subscription ID")) end
|
||||
if name == "" then return back_to_list(_("Name cannot be empty")) end
|
||||
if content == "" then return back_to_list(_("Subscription content cannot be empty")) end
|
||||
local ok, err = core.save_meta(id, {
|
||||
name = name,
|
||||
raw_content = content,
|
||||
@@ -275,10 +285,10 @@ function action_local_save()
|
||||
keyword_exclude = rules.keyword_exclude,
|
||||
dedup = rules.dedup,
|
||||
})
|
||||
if not ok then return back_to_list(err or "保存失败") end
|
||||
if not ok then return back_to_list(err or _("Save failed")) end
|
||||
-- 解析失败会写进 meta.error(列表页 Status 列可见),此处再明确提示一次
|
||||
local sok, serr = core.sync(id)
|
||||
if not sok then return back_to_list(serr or "解析订阅内容失败") end
|
||||
if not sok then return back_to_list(serr or _("Failed to parse subscription content")) end
|
||||
end
|
||||
back_to_list(post_fail_msg)
|
||||
end
|
||||
@@ -298,7 +308,7 @@ function action_delete()
|
||||
for s in tostring(fv(http, "id") or ""):gmatch("[^,%s]+") do
|
||||
ids[#ids + 1] = s
|
||||
end
|
||||
if #ids == 0 then return back_to_list("未指定要删除的订阅") end
|
||||
if #ids == 0 then return back_to_list(_("No subscription selected for deletion")) end
|
||||
local removed, first_err = 0, nil
|
||||
for _, id in ipairs(ids) do
|
||||
local ok, err = core.remove(id)
|
||||
@@ -318,9 +328,9 @@ function action_delete()
|
||||
if removed > 0 then core.write_cron() end
|
||||
if removed < #ids then
|
||||
if removed == 0 then
|
||||
return back_to_list(first_err or "删除失败:订阅不存在")
|
||||
return back_to_list(first_err or _("Delete failed: subscription not found"))
|
||||
end
|
||||
return back_to_list(string.format("已删除 %d 个,另有 %d 个删除失败",
|
||||
return back_to_list(string.format(_("Deleted %d, %d failed"),
|
||||
removed, #ids - removed))
|
||||
end
|
||||
end
|
||||
@@ -357,22 +367,22 @@ function action_node_save()
|
||||
-- 原来只在成功分支里做事,其余情况一律静默重定向,
|
||||
-- 用户提交了坏数据却看到「已保存」的样子。
|
||||
if not idx or not nodes[idx] then
|
||||
back_to_nodes(http, "节点不存在或下标无效")
|
||||
back_to_nodes(http, _("Node not found or invalid index"))
|
||||
return
|
||||
end
|
||||
if content == "" then
|
||||
back_to_nodes(http, "提交内容为空")
|
||||
back_to_nodes(http, _("Submitted content is empty"))
|
||||
return
|
||||
end
|
||||
local res, perr = parser.parse_local(content, "form")
|
||||
local newn = res and res.nodes and res.nodes[1]
|
||||
if not newn then
|
||||
back_to_nodes(http, perr or "表单数据解析失败")
|
||||
back_to_nodes(http, perr or _("Failed to parse form data"))
|
||||
return
|
||||
end
|
||||
nodes[idx] = core.merge_form_node(nodes[idx], newn)
|
||||
if not core.write_nodes(id, nodes) then
|
||||
back_to_nodes(http, "写入节点数据失败")
|
||||
back_to_nodes(http, _("Failed to write node data"))
|
||||
return
|
||||
end
|
||||
core.refresh_combos(id)
|
||||
@@ -394,7 +404,7 @@ function action_node_delete()
|
||||
end
|
||||
-- §18:什么都没删掉时必须说清楚,不能静默跳回列表。
|
||||
if #idxs == 0 then
|
||||
back_to_nodes(http, "未指定要删除的节点")
|
||||
back_to_nodes(http, _("No node selected for deletion"))
|
||||
return
|
||||
end
|
||||
-- 倒序删除,避免 table.remove 后下标偏移
|
||||
@@ -407,11 +417,11 @@ function action_node_delete()
|
||||
end
|
||||
end
|
||||
if not removed then
|
||||
back_to_nodes(http, "节点不存在或已被删除")
|
||||
back_to_nodes(http, _("Node not found or already deleted"))
|
||||
return
|
||||
end
|
||||
if not core.write_nodes(id, nodes) then
|
||||
back_to_nodes(http, "写入节点数据失败")
|
||||
back_to_nodes(http, _("Failed to write node data"))
|
||||
return
|
||||
end
|
||||
core.save_meta(id, { node_count = #nodes })
|
||||
@@ -465,10 +475,10 @@ function action_combo_save()
|
||||
end
|
||||
-- §18:此前名称留空、或一个来源都没勾选时,这里整段跳过、直接跳回列表 ——
|
||||
-- 用户填了表单却什么都没发生,页面上也没有任何提示。
|
||||
if name == "" then return back_to_list("名称不能为空") end
|
||||
if name == "" then return back_to_list(_("Name cannot be empty")) end
|
||||
-- 规则校验失败(如非法正则)必须回显,否则用户看到的是「规则没生效」
|
||||
local rules, rules_err = read_rules_fields()
|
||||
if not rules then return back_to_list(rules_err or "规则无效") end
|
||||
if not rules then return back_to_list(rules_err or _("Invalid rules")) end
|
||||
local o = {
|
||||
rules_enable = rules.rules_enable, proto_filter = rules.proto_filter,
|
||||
keyword_include = rules.keyword_include, keyword_exclude = rules.keyword_exclude,
|
||||
@@ -482,7 +492,7 @@ function action_combo_save()
|
||||
else
|
||||
nid, err = core.save_combo(id, name, sources, o)
|
||||
end
|
||||
if not nid then return back_to_list(err or "保存组合订阅失败") end
|
||||
if not nid then return back_to_list(err or _("Failed to save combination")) end
|
||||
end
|
||||
back_to_list(post_fail_msg)
|
||||
end
|
||||
@@ -499,7 +509,7 @@ function action_update()
|
||||
end
|
||||
-- §18:订阅不存在(ID 拼错 / 已被删除)时此前静默跳回列表,
|
||||
-- 用户点了「更新」却看不到任何反馈。
|
||||
if not meta then return back_to_list("订阅不存在") end
|
||||
if not meta then return back_to_list(_("Subscription not found")) end
|
||||
-- pcall 只保证「不抛异常」;core.sync 的失败是「返回 nil, err」而非抛错,
|
||||
-- 因此必须同时检查两层结果,否则失败永远不会写回列表状态。
|
||||
local ok, res, err = pcall(core.sync, id)
|
||||
@@ -508,7 +518,7 @@ function action_update()
|
||||
core.save_meta(id, { error = tostring(res), last_update = os.time() })
|
||||
elseif not res then
|
||||
-- 正常返回但失败:第三个返回值是错误信息
|
||||
core.save_meta(id, { error = tostring(err or "更新失败"), last_update = os.time() })
|
||||
core.save_meta(id, { error = tostring(err or _("Update failed")), last_update = os.time() })
|
||||
end
|
||||
end
|
||||
back_to_list(post_fail_msg)
|
||||
|
||||
@@ -145,9 +145,11 @@ var FIELD_LABELS = {
|
||||
"net": "Network",
|
||||
"headerType": "Header Type",
|
||||
"flow": "Flow",
|
||||
"fp": "<%:Fingerprint%>",
|
||||
"path": "Path",
|
||||
"host": "Host",
|
||||
"sni": "SNI",
|
||||
"alpn": "<%:ALPN%>",
|
||||
"tls": "TLS",
|
||||
"obfs": "Obfs",
|
||||
"obfs-param": "Obfs Param",
|
||||
@@ -161,6 +163,8 @@ var FIELD_LABELS = {
|
||||
"private-key": "Private Key",
|
||||
"peer-public-key": "Peer Public Key",
|
||||
"public-key": "Public Key",
|
||||
"short-id": "<%:Short ID%>",
|
||||
"spider-x": "<%:Spider X%>",
|
||||
"pre-shared-key": "Pre Shared Key",
|
||||
"ip": "IP",
|
||||
"ipv6": "IPv6",
|
||||
|
||||
@@ -59,9 +59,11 @@ var FIELD_LABELS = {
|
||||
"net": "Network",
|
||||
"headerType": "Header Type",
|
||||
"flow": "Flow",
|
||||
"fp": "<%:Fingerprint%>",
|
||||
"path": "Path",
|
||||
"host": "Host",
|
||||
"sni": "SNI",
|
||||
"alpn": "<%:ALPN%>",
|
||||
"tls": "TLS",
|
||||
"obfs": "Obfs",
|
||||
"obfs-param": "Obfs Param",
|
||||
@@ -75,6 +77,8 @@ var FIELD_LABELS = {
|
||||
"private-key": "Private Key",
|
||||
"peer-public-key": "Peer Public Key",
|
||||
"public-key": "Public Key",
|
||||
"short-id": "<%:Short ID%>",
|
||||
"spider-x": "<%:Spider X%>",
|
||||
"pre-shared-key": "Pre Shared Key",
|
||||
"ip": "IP",
|
||||
"ipv6": "IPv6",
|
||||
|
||||
@@ -64,7 +64,12 @@
|
||||
<label><%:Type%>:
|
||||
<select name="proto">
|
||||
<option value=""><%:All%></option>
|
||||
<% for _, p in ipairs({"vmess","vless","trojan","shadowsocks","ssr","hysteria2","tuic","hysteria","wireguard","socks"}) do %>
|
||||
<%
|
||||
-- 协议清单来自 node.PROTOS,不在这里另抄一份:抄一份就会漂移,
|
||||
-- 症状是节点页的「类型」下拉永远选不到新协议(勾了没用、不报错)。
|
||||
-- 另外三个表单模板早已统一用 core.RULE_PROTOS(= node.PROTOS)。
|
||||
%>
|
||||
<% for _, p in ipairs(node.PROTOS) do %>
|
||||
<option value="<%=p%>" <%=proto==p and "selected" or ""%>><%=p%></option>
|
||||
<% end %>
|
||||
</select>
|
||||
|
||||
Reference in new issue
Block a user