1003 lines
30 KiB
Ucode
1003 lines
30 KiB
Ucode
#!/usr/bin/env ucode
|
|
|
|
let fs = require("fs");
|
|
|
|
function as_string(value) {
|
|
return value == null ? "" : "" + value;
|
|
}
|
|
|
|
function read_json_file(path) {
|
|
let data = fs.readfile(path);
|
|
if (data == null)
|
|
return null;
|
|
|
|
try {
|
|
return json(data);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function read_stdin() {
|
|
let input = fs.open("/dev/stdin", "r");
|
|
if (!input)
|
|
return "";
|
|
let data = input.read("all");
|
|
input.close();
|
|
return data == null ? "" : data;
|
|
}
|
|
|
|
function read_stdin_json() {
|
|
let data = read_stdin();
|
|
try {
|
|
return json(data);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function write_json(value) {
|
|
print(sprintf("%J", value), "\n");
|
|
}
|
|
|
|
function write_json_file(path, value) {
|
|
return fs.writefile(path, sprintf("%J", value) + "\n");
|
|
}
|
|
|
|
function str_contains(haystack, needle) {
|
|
return index(as_string(haystack), as_string(needle)) >= 0;
|
|
}
|
|
|
|
function str_startswith(value, prefix) {
|
|
value = as_string(value);
|
|
prefix = as_string(prefix);
|
|
return substr(value, 0, length(prefix)) == prefix;
|
|
}
|
|
|
|
function str_endswith(value, suffix) {
|
|
value = as_string(value);
|
|
suffix = as_string(suffix);
|
|
return length(value) >= length(suffix) && substr(value, length(value) - length(suffix)) == suffix;
|
|
}
|
|
|
|
function array_or_empty(value) {
|
|
return type(value) == "array" ? value : [];
|
|
}
|
|
|
|
function object_or_empty(value) {
|
|
return type(value) == "object" ? value : {};
|
|
}
|
|
|
|
function validate_subscription(path) {
|
|
let value = read_json_file(path);
|
|
return type(value) == "object" && type(value.outbounds) == "array" && length(value.outbounds) > 0;
|
|
}
|
|
|
|
function object_has_extra_keys(path) {
|
|
let value = read_json_file(path);
|
|
return type(value) == "object" && length(keys(value)) > 1;
|
|
}
|
|
|
|
function value_length(path) {
|
|
let value = read_json_file(path);
|
|
if (type(value) == "array" || type(value) == "object")
|
|
print(length(value), "\n");
|
|
else
|
|
print("0\n");
|
|
}
|
|
|
|
function response_success() {
|
|
let value = read_stdin_json();
|
|
return type(value) == "object" && value.success === true;
|
|
}
|
|
|
|
function valid_outbound() {
|
|
let value = read_stdin_json();
|
|
return type(value) == "object" && type(value.type) == "string";
|
|
}
|
|
|
|
function stdin_length() {
|
|
let value = read_stdin_json();
|
|
if (type(value) == "array" || type(value) == "object")
|
|
print(length(value), "\n");
|
|
else
|
|
print("0\n");
|
|
}
|
|
|
|
function array_item(index) {
|
|
let value = read_stdin_json();
|
|
index = int(index || 0);
|
|
if (type(value) == "array" && index >= 0 && index < length(value) && value[index] != null)
|
|
print(as_string(value[index]), "\n");
|
|
}
|
|
|
|
function object_get(key) {
|
|
let value = read_stdin_json();
|
|
if (type(value) == "object" && value[key] != null)
|
|
print(as_string(value[key]), "\n");
|
|
}
|
|
|
|
function object_get_default(key, fallback) {
|
|
let value = read_stdin_json();
|
|
if (type(value) == "object" && value[key] != null)
|
|
print(as_string(value[key]), "\n");
|
|
else
|
|
print(as_string(fallback), "\n");
|
|
}
|
|
|
|
function parse_json_or_null(value) {
|
|
try {
|
|
return json(value);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function arg_bool(value) {
|
|
return value === true || value == "true" || value == "1" || value == 1;
|
|
}
|
|
|
|
function arg_number(value) {
|
|
value = as_string(value);
|
|
if (value == "" || match(value, /[^0-9-]/))
|
|
return 0;
|
|
return int(value);
|
|
}
|
|
|
|
function arg_optional_number(value) {
|
|
value = as_string(value);
|
|
if (value == "" || value == "null" || match(value, /[^0-9-]/))
|
|
return null;
|
|
return int(value);
|
|
}
|
|
|
|
function object_json(start) {
|
|
let result = {};
|
|
|
|
for (let i = start; i + 2 < length(ARGV); i += 3) {
|
|
let key = as_string(ARGV[i]);
|
|
let kind = as_string(ARGV[i + 1]);
|
|
let value = ARGV[i + 2];
|
|
|
|
if (kind == "n")
|
|
result[key] = arg_number(value);
|
|
else if (kind == "b")
|
|
result[key] = arg_bool(value);
|
|
else if (kind == "j")
|
|
result[key] = parse_json_or_null(value);
|
|
else if (kind == "a") {
|
|
let parsed = parse_json_or_null(value);
|
|
result[key] = parsed != null ? parsed : as_string(value);
|
|
}
|
|
else
|
|
result[key] = as_string(value);
|
|
}
|
|
|
|
write_json(result);
|
|
}
|
|
|
|
function subscription_ui_metadata() {
|
|
let title = as_string(ARGV[1]);
|
|
let web_page_url = as_string(ARGV[2]);
|
|
let support_url = as_string(ARGV[3]);
|
|
let announce = as_string(ARGV[4]);
|
|
let announce_url = as_string(ARGV[5]);
|
|
let file_name = as_string(ARGV[6]);
|
|
let has_traffic = arg_bool(ARGV[7]);
|
|
let upload = arg_optional_number(ARGV[8]);
|
|
let download = arg_optional_number(ARGV[9]);
|
|
let used = arg_number(ARGV[10]);
|
|
let total = arg_optional_number(ARGV[11]);
|
|
let remaining = arg_optional_number(ARGV[12]);
|
|
let is_unlimited = arg_bool(ARGV[13]);
|
|
let expire = arg_optional_number(ARGV[14]);
|
|
let refill_date = arg_optional_number(ARGV[15]);
|
|
|
|
let result = { version: 1 };
|
|
if (title != "")
|
|
result.title = title;
|
|
if (has_traffic) {
|
|
let traffic = {
|
|
used: used,
|
|
isUnlimited: is_unlimited
|
|
};
|
|
if (upload != null)
|
|
traffic.upload = upload;
|
|
if (download != null)
|
|
traffic.download = download;
|
|
if (total != null && total > 0) {
|
|
traffic.total = total;
|
|
traffic.remaining = remaining != null ? remaining : 0;
|
|
}
|
|
result.traffic = traffic;
|
|
}
|
|
if (expire != null && expire > 0)
|
|
result.expire = expire;
|
|
if (refill_date != null && refill_date > 0)
|
|
result.refillDate = refill_date;
|
|
if (web_page_url != "")
|
|
result.webPageUrl = web_page_url;
|
|
if (support_url != "")
|
|
result.supportUrl = support_url;
|
|
if (announce != "")
|
|
result.announce = announce;
|
|
if (announce_url != "")
|
|
result.announceUrl = announce_url;
|
|
if (file_name != "")
|
|
result.fileName = file_name;
|
|
|
|
if (length(keys(result)) > 1)
|
|
write_json(result);
|
|
}
|
|
|
|
function stdin_json() {
|
|
let value = read_stdin_json();
|
|
if (value == null)
|
|
exit(1);
|
|
write_json(value);
|
|
}
|
|
|
|
function file_json_valid(path) {
|
|
return read_json_file(path) != null;
|
|
}
|
|
|
|
function json_file_field(path, key, fallback) {
|
|
let value = read_json_file(path);
|
|
if (type(value) == "object" && value[key] != null)
|
|
print(as_string(value[key]), "\n");
|
|
else
|
|
print(as_string(fallback), "\n");
|
|
}
|
|
|
|
function github_release_tags(path) {
|
|
for (let release in array_or_empty(read_json_file(path))) {
|
|
if (type(release) != "object")
|
|
continue;
|
|
if (release.draft === true || release.prerelease === true)
|
|
continue;
|
|
let tag = as_string(release.tag_name || "");
|
|
if (tag != "")
|
|
print(tag, "\n");
|
|
}
|
|
}
|
|
|
|
function github_response_ok() {
|
|
let response = read_stdin_json();
|
|
if (response == null)
|
|
return false;
|
|
|
|
if (type(response) == "object") {
|
|
let message = as_string(response.message || "");
|
|
if (match(message, /API rate limit/) || match(message, /rate limit exceeded/) || message == "Not Found")
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function release_by_tag(tag) {
|
|
for (let release in array_or_empty(read_stdin_json())) {
|
|
if (type(release) != "object")
|
|
continue;
|
|
if (release.draft === true || release.prerelease === true)
|
|
continue;
|
|
if (as_string(release.tag_name || "") == tag) {
|
|
write_json(release);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function release_asset_name(prefix, ext) {
|
|
let release = object_or_empty(read_stdin_json());
|
|
for (let asset in array_or_empty(release.assets)) {
|
|
if (type(asset) != "object")
|
|
continue;
|
|
let name = as_string(asset.name || "");
|
|
if ((str_startswith(name, prefix + "_") || str_startswith(name, prefix + "-")) &&
|
|
str_endswith(name, "." + ext)) {
|
|
print(name, "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function release_asset_url(name) {
|
|
let release = object_or_empty(read_stdin_json());
|
|
for (let asset in array_or_empty(release.assets)) {
|
|
if (type(asset) == "object" && as_string(asset.name || "") == name) {
|
|
print(as_string(asset.browser_download_url || ""), "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function release_asset_name_by_suffix(suffix) {
|
|
let release = object_or_empty(read_stdin_json());
|
|
for (let asset in array_or_empty(release.assets)) {
|
|
let name = type(asset) == "object" ? as_string(asset.name || "") : "";
|
|
if (str_endswith(name, suffix)) {
|
|
print(name, "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function release_asset_url_by_suffix(suffix) {
|
|
let release = object_or_empty(read_stdin_json());
|
|
for (let asset in array_or_empty(release.assets)) {
|
|
if (type(asset) != "object")
|
|
continue;
|
|
let name = as_string(asset.name || "");
|
|
if (str_endswith(name, suffix)) {
|
|
print(as_string(asset.browser_download_url || ""), "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function byedpi_asset_matches(name, arch, ext) {
|
|
return (str_startswith(name, "byedpi_") || str_startswith(name, "byedpi-")) &&
|
|
str_endswith(name, "." + ext) &&
|
|
(str_contains(name, "_" + arch + "." + ext) || str_contains(name, "-" + arch + "." + ext));
|
|
}
|
|
|
|
function select_byedpi_asset_from_release(release, asset_ext, arch_candidates) {
|
|
for (let arch in split(as_string(arch_candidates), " ")) {
|
|
if (arch == "")
|
|
continue;
|
|
for (let asset in array_or_empty(release.assets)) {
|
|
if (type(asset) != "object")
|
|
continue;
|
|
let name = as_string(asset.name || "");
|
|
let url = as_string(asset.browser_download_url || "");
|
|
if (url != "" && byedpi_asset_matches(name, arch, asset_ext)) {
|
|
print(arch, "\t", name, "\t", url, "\t", as_string(release.html_url || ""), "\n");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function byedpi_select_asset(series, asset_ext, arch_candidates) {
|
|
let releases = array_or_empty(read_stdin_json());
|
|
|
|
for (let pass = 0; pass < 2; pass++) {
|
|
if (pass == 0 && as_string(series) == "")
|
|
continue;
|
|
|
|
for (let release in releases) {
|
|
if (type(release) != "object")
|
|
continue;
|
|
if (release.draft === true || release.prerelease === true)
|
|
continue;
|
|
if (pass == 0) {
|
|
let tag = as_string(release.tag_name || "");
|
|
let name = as_string(release.name || "");
|
|
if (!str_contains(tag, series) && !str_contains(name, series))
|
|
continue;
|
|
}
|
|
if (select_byedpi_asset_from_release(release, asset_ext, arch_candidates))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function sing_box_extended_release_tag() {
|
|
for (let release in array_or_empty(read_stdin_json())) {
|
|
if (type(release) != "object")
|
|
continue;
|
|
if (release.draft === true || release.prerelease === true)
|
|
continue;
|
|
let tag = as_string(release.tag_name || "");
|
|
let lowered = lc(tag);
|
|
if (tag != "" && !str_contains(lowered, "alpha") && !str_contains(lowered, "beta") && !str_contains(lowered, "rc")) {
|
|
print(tag, "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function system_info_cache_valid(path, podkop_version, luci_app_version, ttl, now) {
|
|
let cache = read_json_file(path);
|
|
if (type(cache) != "object")
|
|
return false;
|
|
|
|
now = arg_number(now);
|
|
ttl = arg_number(ttl);
|
|
let cached_at = arg_number(cache.generated_at || 0);
|
|
|
|
if (now > 0 && cached_at > 0 && ttl > 0 && now - cached_at >= ttl)
|
|
return false;
|
|
|
|
return cache.podkop_version == podkop_version && cache.luci_app_version == luci_app_version;
|
|
}
|
|
|
|
function system_info_json() {
|
|
write_json({
|
|
podkop_version: as_string(ARGV[1]),
|
|
podkop_latest_version: as_string(ARGV[2]),
|
|
luci_app_version: as_string(ARGV[3]),
|
|
sing_box_version: as_string(ARGV[4]),
|
|
sing_box_extended: arg_number(ARGV[5]),
|
|
zapret_version: as_string(ARGV[6]),
|
|
zapret_installed: arg_number(ARGV[7]),
|
|
byedpi_version: as_string(ARGV[8]),
|
|
byedpi_installed: arg_number(ARGV[9]),
|
|
openwrt_version: as_string(ARGV[10]),
|
|
device_model: as_string(ARGV[11]),
|
|
generated_at: arg_number(ARGV[12])
|
|
});
|
|
}
|
|
|
|
function nfqws_strategy_validation(valid, message, needle, needles) {
|
|
let result = [];
|
|
for (let item in split(as_string(needles), "\n")) {
|
|
if (item != "")
|
|
push(result, item);
|
|
}
|
|
|
|
write_json({
|
|
valid: arg_bool(valid),
|
|
message: as_string(message),
|
|
needle: as_string(needle),
|
|
needles: result
|
|
});
|
|
}
|
|
|
|
function url_encode(value) {
|
|
value = as_string(value);
|
|
for (let i = 0; i < length(value); i++) {
|
|
let c = substr(value, i, 1);
|
|
let code = ord(c);
|
|
if ((code >= 48 && code <= 57) ||
|
|
(code >= 65 && code <= 90) ||
|
|
(code >= 97 && code <= 122) ||
|
|
c == "-" || c == "_" || c == "." || c == "~")
|
|
print(c);
|
|
else
|
|
print(sprintf("%%%02X", code));
|
|
}
|
|
print("\n");
|
|
}
|
|
|
|
function job_running_is(path, expected) {
|
|
let value = read_json_file(path);
|
|
let running = type(value) == "object" && value.running === true;
|
|
return running == arg_bool(expected);
|
|
}
|
|
|
|
function updates_set_running_job_pid(path, pid) {
|
|
let value = object_or_empty(read_json_file(path));
|
|
if (value.running === true)
|
|
value.pid = as_string(pid);
|
|
write_json(value);
|
|
}
|
|
|
|
function updates_mark_stale_job_state(path) {
|
|
let value = object_or_empty(read_json_file(path));
|
|
if (value.running === true) {
|
|
value.success = false;
|
|
value.running = false;
|
|
value.message = "Component action job is stale or the worker process exited unexpectedly";
|
|
value.changed = 0;
|
|
value.status = "";
|
|
value.exit_code = null;
|
|
}
|
|
write_json(value);
|
|
}
|
|
|
|
function updates_finish_job_state(path, exit_code, updated_at) {
|
|
let value = read_json_file(path);
|
|
if (value == null)
|
|
exit(1);
|
|
|
|
value.running = false;
|
|
value.exit_code = arg_number(exit_code);
|
|
value.updated_at = arg_number(updated_at);
|
|
write_json(value);
|
|
}
|
|
|
|
function updates_fallback_job_state(component, action, message, exit_code, updated_at) {
|
|
write_json({
|
|
success: false,
|
|
running: false,
|
|
component: as_string(component),
|
|
action: as_string(action),
|
|
message: as_string(message),
|
|
current_version: "",
|
|
latest_version: "",
|
|
changed: 0,
|
|
status: "",
|
|
exit_code: arg_number(exit_code),
|
|
updated_at: arg_number(updated_at)
|
|
});
|
|
}
|
|
|
|
function sing_box_service_pid() {
|
|
let value = read_stdin_json();
|
|
let service = type(value) == "object" ? value["sing-box"] : null;
|
|
let instances = service && type(service.instances) == "object" ? service.instances : {};
|
|
|
|
for (let _, instance in instances) {
|
|
if (type(instance) == "object" && instance.running === true && int(instance.pid || 0) > 0) {
|
|
print(instance.pid, "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function array_append_string(path, value) {
|
|
let result = array_or_empty(read_json_file(path));
|
|
push(result, as_string(value));
|
|
write_json(result);
|
|
}
|
|
|
|
function arrays_concat(first_path, second_path) {
|
|
let result = array_or_empty(read_json_file(first_path));
|
|
for (let value in array_or_empty(read_json_file(second_path)))
|
|
push(result, value);
|
|
write_json(result);
|
|
}
|
|
|
|
function object_set_string(path, key, value) {
|
|
let result = object_or_empty(read_json_file(path));
|
|
result[as_string(key)] = as_string(value);
|
|
write_json(result);
|
|
}
|
|
|
|
function objects_merge(first_path, second_path) {
|
|
let result = object_or_empty(read_json_file(first_path));
|
|
for (let key, value in object_or_empty(read_json_file(second_path)))
|
|
result[key] = value;
|
|
write_json(result);
|
|
}
|
|
|
|
function normalized_country_list() {
|
|
let result = [];
|
|
for (let value in array_or_empty(read_stdin_json())) {
|
|
value = uc(as_string(value));
|
|
if (length(value) == 2)
|
|
push(result, value);
|
|
}
|
|
write_json(result);
|
|
}
|
|
|
|
function contains(values, needle) {
|
|
for (let value in values) {
|
|
if (value == needle)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function urltest_filter(mode, tags_path, names_path, countries_path, names_filter_path, regex_tags_path, countries_filter_path) {
|
|
let tags = array_or_empty(read_json_file(tags_path));
|
|
let names = object_or_empty(read_json_file(names_path));
|
|
let countries = object_or_empty(read_json_file(countries_path));
|
|
let name_filter = array_or_empty(read_json_file(names_filter_path));
|
|
let regex_tags = array_or_empty(read_json_file(regex_tags_path));
|
|
let country_filter = array_or_empty(read_json_file(countries_filter_path));
|
|
let result = [];
|
|
|
|
for (let tag in tags) {
|
|
let name = as_string(names[tag] || tag);
|
|
let country = uc(as_string(countries[tag] || ""));
|
|
let matched = contains(name_filter, name) ||
|
|
contains(name_filter, tag) ||
|
|
(country != "" && contains(country_filter, country)) ||
|
|
contains(regex_tags, tag);
|
|
|
|
if ((mode == "include" && matched) || (mode == "exclude" && !matched))
|
|
push(result, tag);
|
|
}
|
|
|
|
write_json(result);
|
|
}
|
|
|
|
function section_countries(path) {
|
|
let cache = object_or_empty(read_json_file(path));
|
|
write_json(object_or_empty(cache.outboundMetadata && cache.outboundMetadata.countries));
|
|
}
|
|
|
|
function cached_countries_for_servers(servers_path, cache_path) {
|
|
let servers = object_or_empty(read_json_file(servers_path));
|
|
let cache = object_or_empty(read_json_file(cache_path));
|
|
let result = {};
|
|
|
|
for (let tag, _ in servers) {
|
|
let country = as_string(cache[tag] || "");
|
|
if (country != "")
|
|
result[tag] = country;
|
|
}
|
|
|
|
write_json(result);
|
|
}
|
|
|
|
function missing_servers_tsv(servers_path, cache_path) {
|
|
let servers = object_or_empty(read_json_file(servers_path));
|
|
let cache = object_or_empty(read_json_file(cache_path));
|
|
|
|
for (let tag, server in servers) {
|
|
server = as_string(server);
|
|
if (server != "" && as_string(cache[tag] || "") == "")
|
|
print(tag, "\t", server, "\n");
|
|
}
|
|
}
|
|
|
|
function body_error(path) {
|
|
let body = read_json_file(path);
|
|
let result = "";
|
|
|
|
if (type(body) == "object")
|
|
result = as_string((body.error && body.error.code) || body.code || body.error || "");
|
|
|
|
print(result, "\n");
|
|
}
|
|
|
|
function ip_country_tsv(path) {
|
|
for (let item in array_or_empty(read_json_file(path))) {
|
|
if (type(item) != "object")
|
|
continue;
|
|
let ip = as_string(item.ip || "");
|
|
let country = as_string(item.country || "");
|
|
if (ip != "" && country != "")
|
|
print(ip, "\t", country, "\n");
|
|
}
|
|
}
|
|
|
|
function tsv_to_object(path) {
|
|
let result = {};
|
|
let data = fs.readfile(path);
|
|
if (data != null) {
|
|
for (let line in split(data, "\n")) {
|
|
if (line == "")
|
|
continue;
|
|
let parts = split(line, "\t");
|
|
if (length(parts) >= 2)
|
|
result[parts[0]] = parts[1];
|
|
}
|
|
}
|
|
write_json(result);
|
|
}
|
|
|
|
function tsv_second_column_array(path) {
|
|
let seen = {};
|
|
let result = [];
|
|
let data = fs.readfile(path);
|
|
|
|
if (data != null) {
|
|
for (let line in split(data, "\n")) {
|
|
if (line == "")
|
|
continue;
|
|
let parts = split(line, "\t");
|
|
if (length(parts) >= 2 && parts[1] != "" && !seen[parts[1]]) {
|
|
seen[parts[1]] = true;
|
|
push(result, parts[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
sort(result, function(first, second) {
|
|
return first == second ? 0 : (first < second ? -1 : 1);
|
|
});
|
|
write_json(result);
|
|
}
|
|
|
|
function array_slice_file(path, start, end) {
|
|
write_json(slice(array_or_empty(read_json_file(path)), int(start || 0), int(end || 0)));
|
|
}
|
|
|
|
function object_nonempty_stdin() {
|
|
let value = read_stdin_json();
|
|
return (type(value) == "array" || type(value) == "object") && length(value) > 0;
|
|
}
|
|
|
|
function resolved_countries_from_tsv(resolved_path, ip_country_path) {
|
|
let ip_country = object_or_empty(read_json_file(ip_country_path));
|
|
let result = {};
|
|
let data = fs.readfile(resolved_path);
|
|
|
|
if (data != null) {
|
|
for (let line in split(data, "\n")) {
|
|
if (line == "")
|
|
continue;
|
|
let parts = split(line, "\t");
|
|
if (length(parts) < 2)
|
|
continue;
|
|
let country = as_string(ip_country[parts[1]] || "");
|
|
if (country != "")
|
|
result[parts[0]] = country;
|
|
}
|
|
}
|
|
|
|
write_json(result);
|
|
}
|
|
|
|
function outbound_server_by_tag(tag) {
|
|
let config = object_or_empty(read_stdin_json());
|
|
for (let outbound in array_or_empty(config.outbounds)) {
|
|
if (type(outbound) == "object" && outbound.tag == tag) {
|
|
print(as_string(outbound.server || ""), "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function server_users_from_tsv(protocol, path) {
|
|
let result = [];
|
|
let data = fs.readfile(path);
|
|
|
|
if (data != null) {
|
|
for (let line in split(data, "\n")) {
|
|
if (line == "")
|
|
continue;
|
|
|
|
let parts = split(line, "\t");
|
|
let name = as_string(parts[0] || "");
|
|
let credential = as_string(parts[1] || "");
|
|
let flow = as_string(parts[2] || "");
|
|
|
|
if (credential == "")
|
|
continue;
|
|
|
|
if (protocol == "vless") {
|
|
let user = { uuid: credential };
|
|
if (name != "")
|
|
user.name = name;
|
|
if (flow != "")
|
|
user.flow = flow;
|
|
push(result, user);
|
|
}
|
|
else if (protocol == "vmess") {
|
|
let user = { uuid: credential, alterId: int(flow || "0", 10) || 0 };
|
|
if (name != "")
|
|
user.name = name;
|
|
push(result, user);
|
|
}
|
|
else if (protocol == "trojan") {
|
|
let user = { password: credential };
|
|
if (name != "")
|
|
user.name = name;
|
|
push(result, user);
|
|
}
|
|
else if (protocol == "hysteria2") {
|
|
let user = { password: credential };
|
|
if (name != "")
|
|
user.name = name;
|
|
push(result, user);
|
|
}
|
|
else if (protocol == "socks") {
|
|
let user = {
|
|
username: name != "" ? name : "user",
|
|
password: credential
|
|
};
|
|
push(result, user);
|
|
}
|
|
}
|
|
}
|
|
|
|
write_json(result);
|
|
}
|
|
|
|
let masked_sing_box_keys = {
|
|
auth_key: true,
|
|
control_url: true,
|
|
exit_node: true,
|
|
hostname: true,
|
|
listen: true,
|
|
listen_port: true,
|
|
username: true,
|
|
uuid: true,
|
|
server: true,
|
|
server_name: true,
|
|
secret: true,
|
|
password: true,
|
|
private_key: true,
|
|
public_key: true,
|
|
short_id: true,
|
|
fingerprint: true,
|
|
server_port: true,
|
|
server_ports: true,
|
|
advertise_routes: true,
|
|
domain: true,
|
|
domain_suffix: true,
|
|
domain_keyword: true,
|
|
domain_regex: true,
|
|
ip_cidr: true,
|
|
source_ip_cidr: true
|
|
};
|
|
|
|
function mask_sing_box_value(value) {
|
|
if (type(value) == "array") {
|
|
let result = [];
|
|
for (let item in value)
|
|
push(result, mask_sing_box_value(item));
|
|
return result;
|
|
}
|
|
|
|
if (type(value) == "object") {
|
|
let result = {};
|
|
for (let key, item in value)
|
|
result[key] = masked_sing_box_keys[key] ? "MASKED" : mask_sing_box_value(item);
|
|
return result;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function mask_sing_box_config(path) {
|
|
write_json(mask_sing_box_value(read_json_file(path)));
|
|
}
|
|
|
|
function prepare_check_proxy_config(input_path, output_path, cache_path) {
|
|
let config = object_or_empty(read_json_file(input_path));
|
|
config.inbounds = [];
|
|
config.services = [];
|
|
if (type(config.experimental) == "object") {
|
|
if (type(config.experimental.cache_file) == "object")
|
|
config.experimental.cache_file.path = as_string(cache_path);
|
|
delete config.experimental.clash_api;
|
|
}
|
|
|
|
if (!write_json_file(output_path, config))
|
|
exit(1);
|
|
}
|
|
|
|
function check_proxy_outbound_tag(config_path, domain) {
|
|
let config = object_or_empty(read_json_file(config_path));
|
|
for (let rule in array_or_empty(config.route && config.route.rules)) {
|
|
if (type(rule) != "object")
|
|
continue;
|
|
if (contains(array_or_empty(rule.domain), domain)) {
|
|
print(as_string(rule.outbound || ""), "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function dns_route_rule_exists(service_tag, tag) {
|
|
let config = object_or_empty(read_stdin_json());
|
|
for (let rule in array_or_empty(config.dns && config.dns.rules)) {
|
|
if (type(rule) == "object" && rule[service_tag] == tag)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function route_rule_has_resolve_matchers(service_tag, tag) {
|
|
let config = object_or_empty(read_stdin_json());
|
|
for (let rule in array_or_empty(config.route && config.route.rules)) {
|
|
if (type(rule) != "object" || rule[service_tag] != tag)
|
|
continue;
|
|
if (rule.domain != null || rule.domain_suffix != null || rule.domain_keyword != null ||
|
|
rule.domain_regex != null || rule.rule_set != null)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
let mode = ARGV[0] || "";
|
|
|
|
if (mode == "validate-subscription")
|
|
exit(validate_subscription(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "object-has-extra-keys")
|
|
exit(object_has_extra_keys(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "length")
|
|
value_length(ARGV[1]);
|
|
else if (mode == "response-success")
|
|
exit(response_success() ? 0 : 1);
|
|
else if (mode == "valid-outbound")
|
|
exit(valid_outbound() ? 0 : 1);
|
|
else if (mode == "stdin-length")
|
|
stdin_length();
|
|
else if (mode == "array-item")
|
|
array_item(ARGV[1]);
|
|
else if (mode == "object-get")
|
|
object_get(ARGV[1]);
|
|
else if (mode == "object-get-default")
|
|
object_get_default(ARGV[1], ARGV[2]);
|
|
else if (mode == "object-json")
|
|
object_json(1);
|
|
else if (mode == "subscription-ui-metadata")
|
|
subscription_ui_metadata();
|
|
else if (mode == "stdin-json")
|
|
stdin_json();
|
|
else if (mode == "file-json-valid")
|
|
exit(file_json_valid(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "json-file-field")
|
|
json_file_field(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "github-release-tags")
|
|
github_release_tags(ARGV[1]);
|
|
else if (mode == "github-response-ok")
|
|
exit(github_response_ok() ? 0 : 1);
|
|
else if (mode == "release-by-tag")
|
|
release_by_tag(ARGV[1]);
|
|
else if (mode == "release-asset-name")
|
|
release_asset_name(ARGV[1], ARGV[2]);
|
|
else if (mode == "release-asset-url")
|
|
release_asset_url(ARGV[1]);
|
|
else if (mode == "release-asset-name-by-suffix")
|
|
release_asset_name_by_suffix(ARGV[1]);
|
|
else if (mode == "release-asset-url-by-suffix")
|
|
release_asset_url_by_suffix(ARGV[1]);
|
|
else if (mode == "byedpi-select-asset")
|
|
byedpi_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "sing-box-extended-release-tag")
|
|
sing_box_extended_release_tag();
|
|
else if (mode == "system-info-cache-valid")
|
|
exit(system_info_cache_valid(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]) ? 0 : 1);
|
|
else if (mode == "system-info-json")
|
|
system_info_json();
|
|
else if (mode == "nfqws-strategy-validation")
|
|
nfqws_strategy_validation(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
|
|
else if (mode == "url-encode")
|
|
url_encode(ARGV[1]);
|
|
else if (mode == "job-running-is")
|
|
exit(job_running_is(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "updates-set-running-job-pid")
|
|
updates_set_running_job_pid(ARGV[1], ARGV[2]);
|
|
else if (mode == "updates-mark-stale-job-state")
|
|
updates_mark_stale_job_state(ARGV[1]);
|
|
else if (mode == "updates-finish-job-state")
|
|
updates_finish_job_state(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "updates-fallback-job-state")
|
|
updates_fallback_job_state(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]);
|
|
else if (mode == "sing-box-service-pid")
|
|
sing_box_service_pid();
|
|
else if (mode == "array-append-string")
|
|
array_append_string(ARGV[1], ARGV[2]);
|
|
else if (mode == "arrays-concat")
|
|
arrays_concat(ARGV[1], ARGV[2]);
|
|
else if (mode == "object-set-string")
|
|
object_set_string(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "objects-merge")
|
|
objects_merge(ARGV[1], ARGV[2]);
|
|
else if (mode == "normalized-country-list")
|
|
normalized_country_list();
|
|
else if (mode == "urltest-filter")
|
|
urltest_filter(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7]);
|
|
else if (mode == "section-countries")
|
|
section_countries(ARGV[1]);
|
|
else if (mode == "cached-countries-for-servers")
|
|
cached_countries_for_servers(ARGV[1], ARGV[2]);
|
|
else if (mode == "missing-servers-tsv")
|
|
missing_servers_tsv(ARGV[1], ARGV[2]);
|
|
else if (mode == "body-error")
|
|
body_error(ARGV[1]);
|
|
else if (mode == "ip-country-tsv")
|
|
ip_country_tsv(ARGV[1]);
|
|
else if (mode == "tsv-to-object")
|
|
tsv_to_object(ARGV[1]);
|
|
else if (mode == "tsv-second-column-array")
|
|
tsv_second_column_array(ARGV[1]);
|
|
else if (mode == "array-slice-file")
|
|
array_slice_file(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "object-nonempty")
|
|
exit(object_nonempty_stdin() ? 0 : 1);
|
|
else if (mode == "resolved-countries-from-tsv")
|
|
resolved_countries_from_tsv(ARGV[1], ARGV[2]);
|
|
else if (mode == "outbound-server-by-tag")
|
|
outbound_server_by_tag(ARGV[1]);
|
|
else if (mode == "server-users-from-tsv")
|
|
server_users_from_tsv(ARGV[1], ARGV[2]);
|
|
else if (mode == "mask-sing-box-config")
|
|
mask_sing_box_config(ARGV[1]);
|
|
else if (mode == "prepare-check-proxy-config")
|
|
prepare_check_proxy_config(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "check-proxy-outbound-tag")
|
|
check_proxy_outbound_tag(ARGV[1], ARGV[2]);
|
|
else if (mode == "dns-route-rule-exists")
|
|
exit(dns_route_rule_exists(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "route-rule-has-resolve-matchers")
|
|
exit(route_rule_has_resolve_matchers(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else {
|
|
warn("Usage: json_utils.uc <operation> ...\n");
|
|
exit(1);
|
|
}
|