use crate::models::RulesetContent;
use crate::utils::string::{find_str, starts_with, to_lower};
use crate::utils::trim;
use crate::Settings;
use log::warn;
use serde_json::{json, Map, Value};
use super::convert_ruleset::convert_ruleset;
use super::ruleset::SINGBOX_RULE_TYPES;
pub fn ruleset_to_sing_box(
base_rule: &mut Value,
ruleset_content_array: &[RulesetContent],
overwrite_original_rules: bool,
) {
let settings = Settings::current();
let mut rules = Value::Array(Vec::new());
if !overwrite_original_rules {
if let Some(route) = base_rule.get("route") {
if let Some(existing_rules) = route.get("rules") {
if existing_rules.is_array() {
rules = existing_rules.clone();
}
}
}
}
if settings.singbox_add_clash_modes {
let global_object = json!({
"clash_mode": "Global",
"outbound": "GLOBAL"
});
let direct_object = json!({
"clash_mode": "Direct",
"outbound": "DIRECT"
});
if let Some(rules_array) = rules.as_array_mut() {
rules_array.push(global_object);
rules_array.push(direct_object);
}
}
let dns_object = json!({
"protocol": "dns",
"outbound": "dns-out"
});
if let Some(rules_array) = rules.as_array_mut() {
rules_array.push(dns_object);
}
let mut total_rules = 0;
let mut final_rule = String::new();
for ruleset in ruleset_content_array {
if settings.max_allowed_rules > 0 && total_rules >= settings.max_allowed_rules {
break;
}
let rule_group = &ruleset.group;
let retrieved_rules = ruleset.get_rule_content();
if retrieved_rules.is_empty() {
warn!(
"Failed to fetch ruleset or ruleset is empty: '{}'!",
ruleset.rule_path
);
continue;
}
if starts_with(&retrieved_rules, "[]") {
let str_line = &retrieved_rules[2..];
if starts_with(str_line, "FINAL") || starts_with(str_line, "MATCH") {
final_rule = rule_group.clone();
continue;
}
let parts: Vec<&str> = str_line.split(',').collect();
if parts.len() < 2 {
continue;
}
let rule_type = to_lower(parts[0]);
let rule_value = to_lower(parts[1]);
let mut rule_obj = Map::new();
let rule_type = rule_type
.replace("-", "_")
.replace("ip_cidr6", "ip_cidr")
.replace("src_", "source_");
if rule_type == "match" || rule_type == "final" {
rule_obj.insert("outbound".to_string(), Value::String(rule_value));
} else {
rule_obj.insert(rule_type, Value::String(rule_value));
rule_obj.insert(
"outbound".to_string(),
Value::String(rule_group.to_string()),
);
}
if let Some(rules_array) = rules.as_array_mut() {
rules_array.push(Value::Object(rule_obj));
total_rules += 1;
}
continue;
}
let converted_rules = convert_ruleset(&retrieved_rules, ruleset.rule_type);
let mut rule_obj = Map::new();
for line in converted_rules.lines() {
if settings.max_allowed_rules > 0 && total_rules >= settings.max_allowed_rules {
break;
}
let mut str_line = trim(line).to_string();
let line_size = str_line.len();
if line_size == 0
|| (line_size >= 1 && (str_line.starts_with(';') || str_line.starts_with('#')))
|| (line_size >= 2 && str_line.starts_with("//"))
{
continue;
}
if let Some(comment_pos) = find_str(&str_line, "//") {
str_line = str_line[..comment_pos].to_string();
str_line = trim(&str_line).to_string();
}
let rule_parts: Vec<&str> = str_line.split(',').collect();
if rule_parts.len() < 2 {
continue;
}
let rule_type = rule_parts[0];
if !SINGBOX_RULE_TYPES.contains(rule_type) {
continue;
}
let real_type = to_lower(rule_type)
.replace("-", "_")
.replace("ip_cidr6", "ip_cidr")
.replace("src_", "source_");
let rule_value = to_lower(rule_parts[1]);
let values = rule_obj
.entry(real_type)
.or_insert_with(|| Value::Array(Vec::new()));
if let Value::Array(ref mut arr) = values {
arr.push(Value::String(rule_value));
total_rules += 1;
}
}
if !rule_obj.is_empty() {
rule_obj.insert("outbound".to_string(), Value::String(rule_group.clone()));
if let Some(rules_array) = rules.as_array_mut() {
rules_array.push(Value::Object(rule_obj));
}
}
}
if base_rule.get("route").is_none() {
base_rule["route"] = json!({});
}
if let Some(route) = base_rule.get_mut("route") {
if let Some(route_obj) = route.as_object_mut() {
route_obj.insert("rules".to_string(), rules);
route_obj.insert("final".to_string(), Value::String(final_rule));
}
}
}