🦄 Sync 2026-06-05 01:27:09

This commit is contained in:
github-actions[bot] committed 2026-06-05 01:27:09 +08:00
1 parent 2f80691f7d
commit f8db3e79f6
64 files changed
+75620 -81561

No files matched your search

File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,3 +1,21 @@
rule-providers:
## google:
## type: http
## path: ./rule1.yaml
## url: "https://raw.githubusercontent.com/../Google.yaml"
## interval: 600
## proxy: DIRECT
## behavior: classical
## format: yaml
## size-limit: 0
## header:
## User-Agent:
## - "mihomo/1.18.3"
## Authorization:
## - 'token 1231231'
## payload:
## - 'DOMAIN-SUFFIX,google.com'
rules:
##- PROCESS-NAME,curl,DIRECT #匹配路由自身进程(curl直连)
##- DOMAIN-SUFFIX,google.com,Proxy #匹配域名后缀(交由Proxy代理服务器组)
@@ -1,3 +1,20 @@
rule-providers:
## google:
## type: http
## path: ./rule1.yaml
## url: "https://raw.githubusercontent.com/../Google.yaml"
## interval: 600
## proxy: DIRECT
## behavior: classical
## format: yaml
## size-limit: 0
## header:
## User-Agent:
## - "mihomo/1.18.3"
## Authorization:
## - 'token 1231231'
## payload:
## - 'DOMAIN-SUFFIX,google.com'
rules:
##- DOMAIN-SUFFIX,google.com,Proxy #匹配域名后缀(交由Proxy代理服务器组)
##- DOMAIN-KEYWORD,google,Proxy #匹配域名关键字(交由Proxy代理服务器组)
@@ -73,7 +73,6 @@
# ENABLE_TCP_CONCURRENT (启用 TCP 并发, 0: 插件不覆写, 1: 启用)
# FIND_PROCESS_MODE (进程查找模式, 0: 插件不覆写, off: 关闭, always, strict)
# GLOBAL_CLIENT_FINGERPRINT (全局客户端指纹, none/random/chrome/firefox/safari/ios/android/edge/360/qq/0: 插件不覆写)
# ENABLE_UNIFIED_DELAY (启用统一延迟计算, 0: 插件不覆写, 1: 启用)
# ENABLE_RULE_PROXY (仅代理命中规则流量, 0: 禁用, 1: 启用)
@@ -117,6 +116,7 @@
# GEOASN_CUSTOM_URL (GeoASN 数据自定义更新地址, URL or default)
# --- 其它功能 ---
# GLOBAL_UA (全局 User-Agent, 0:不覆写,或具体 UA)
# SMALL_FLASH_MEMORY (小闪存模式, 0: 禁用, 1: 启用)
# DISABLE_QUIC_GO_GSO (禁用 QUIC GO GSO, 0: 插件不覆写, 1: 启用)
# DELAY_START (延时启动, 0: 禁用 or 秒数)
@@ -209,6 +209,298 @@
#指定展示订阅信息的 URL 地址
#SUB_INFO_URL = https://default-demo.yaml
# ==========================================================
# ==== 以下为 YAML 块覆写示例 ====
# ==========================================================
# YAML 覆写块识别标记
# [YAML]
# ----------------------------------------------------------
# 操作符速查表:
# key → 默认合并(Hash 递归合并,其他直接覆盖)
# key! → 强制覆盖整个值
# key+ → 数组后置追加
# +key → 数组前置插入
# key- → 数组差集删除 / 非数组则删除该键
# key* → 批量条件更新(需配合 where/set)
# <key>后缀 → 同上(+, !, *, -),适用于键名含特殊字符时
# +<key> → 数组前置插入,适用于键名含特殊字符时
# ----------------------------------------------------------
# 1. 默认合并 - 合并哈希键值,键不存在则添加,Hash 值递归合并
# 示例:修改 dns.enable 并添加新键
# dns:
# enable: true
# cache-algorithm: lru
# 示例:直接替换标量值
# mixed-port: 10802
# allow-lan: false
# 示例:合并 tun 配置(仅修改指定字段,其余保留)
# tun:
# enable: true
# stack: gvisor
# ----------------------------------------------------------
# 2. 强制覆盖 (key!) - 强制替换整个值,不做递归合并
# 示例:替换整个 dns.fake-ip-filter 数组
# dns:
# fake-ip-filter!:
# - '*.lan'
# - 'new.domain.com'
# 示例:强制覆盖整个 rules 数组
# rules!:
# - DOMAIN-SUFFIX,example.com,DIRECT
# - MATCH,PROXY
# 示例:使用 <> 强制覆盖整个 dns 配置(键名含特殊字符时)
# <dns>!:
# enable: false
# nameserver:
# - '114.114.114.114'
# ----------------------------------------------------------
# 3. 数组后置 (key+) - 将新元素追加到数组末尾
# 示例:向 dns.nameserver 末尾追加 DNS
# dns:
# nameserver+:
# - '1.1.1.1'
# - '8.8.8.8'
# 示例:向 rules 末尾追加规则
# rules+:
# - DOMAIN-SUFFIX,example.com,REJECT
# 示例:使用 <> 后置
# dns:
# <nameserver>+:
# - '8.8.8.8'
# ----------------------------------------------------------
# 4. 数组前置 (+key / +<key>) - 将新元素插入到数组开头
# 示例:向 dns.nameserver 开头插入 DNS
# dns:
# +nameserver:
# - '223.5.5.5'
# - '119.29.29.29'
# 示例:向 rules 开头插入规则(优先匹配)
# +rules:
# - DOMAIN-SUFFIX,priority.com,DIRECT
# 示例:使用 +<> 前置插入(键名含特殊字符时)
# dns:
# +<nameserver>:
# - '119.29.29.29'
# ----------------------------------------------------------
# 5. 数组删除 (key-) - 从数组中移除指定元素,非数组则删除整个键
# 示例:从 dns.nameserver 中移除指定 DNS
# dns:
# nameserver-:
# - '8.8.8.8'
# - '8.8.4.4'
# 示例:从 rules 中移除指定规则
# rules-:
# - DOMAIN-SUFFIX,old.com,REJECT
# 示例:删除整个键(值为 null/~ 或空)
# dns:
# cache-algorithm-:
# ----------------------------------------------------------
# 6. 批量条件更新 (key*) - 按 where 条件匹配集合元素,用 set 子句更新
#
# 支持的集合类型:
# - 数组(元素为 Hash 或 String)
# - Hash(值为 Hash)
#
# where 条件支持:
# - 普通值相等: type: select
# - 正则匹配: name: '/^HK/'
# - 数组包含: proxies: ['proxy1'] (检查数组是否包含所有指定元素)
# - Hash 键匹配: key: 'some-key' (仅用于 Hash 类型集合)
# - 字符串匹配: value: 'some-string' (仅用于字符串数组)
#
# set 子句支持的操作符(同顶层操作符):
# field → 直接覆盖该字段
# field! → 强制覆盖该字段
# field+ → 数组后置追加
# +field → 数组前置插入
# field- → 数组差集删除 / 非数组则删除该键
# value → 替换字符串数组中的匹配项
# value: ~ → 删除字符串数组中的匹配项
# key: ~ → 删除 Hash 中的匹配键(仅 Hash 集合)
# key-: → 同上
# 示例:按 type 匹配,替换 proxy-groups 的 proxies
# proxy-groups*:
# where:
# type: select
# set:
# proxies:
# - 'new-proxy1'
# - 'new-proxy2'
# 示例:按 type 匹配,向 proxies 末尾追加节点
# proxy-groups*:
# where:
# type: select
# set:
# proxies+:
# - 'new-proxy3'
# 示例:按 type 匹配,向 proxies 开头插入节点
# proxy-groups*:
# where:
# type: url-test
# set:
# +proxies:
# - 'new-proxy1'
# 示例:按 type 匹配,从 proxies 中移除指定节点
# proxy-groups*:
# where:
# type: select
# set:
# proxies-:
# - 'old-proxy1'
# - 'old-proxy2'
# 示例:使用数组包含条件(proxies 中必须包含指定元素才匹配)
# proxy-groups*:
# where:
# type: select
# proxies:
# - 'old-proxy1'
# set:
# proxies:
# - 'new-proxy1'
# - 'new-proxy2'
# 示例:使用正则匹配 name 字段
# proxy-groups*:
# where:
# name: '/^HK/'
# set:
# +proxies:
# - 'hk-new-proxy'
# 示例:删除 proxy-groups 中某个字段(set value 为 null/~)
# proxy-groups*:
# where:
# name: Proxy
# set:
# icon-:
# 示例:按 type 匹配修改 proxies 中 socks5 节点端口
# proxies*:
# where:
# type: socks5
# set:
# port: 1080
# 示例:更新 hosts Hash 中的指定键
# hosts*:
# where:
# key: '*.mihomo.dev'
# set:
# '*.mihomo.dev': '::1'
# 示例:删除 hosts Hash 中的匹配键
# hosts*:
# where:
# key: '*.old.dev'
# set:
# key-:
# 示例:替换字符串数组中的匹配项
# rules*:
# where:
# value: 'DOMAIN-SUFFIX,old.com,REJECT'
# set:
# value: 'DOMAIN-SUFFIX,new.com,DIRECT'
# 示例:使用正则匹配并删除字符串数组中的规则
# rules*:
# where:
# value: '/,REJECT$/'
# set:
# value:
# 示例:删除 fake-ip-filter 中的指定项
# dns:
# fake-ip-filter*:
# where:
# value: '+.localhost.ptlogin2.qq.com'
# set:
# value:
# 示例:使用 <> 批量更新
# <proxy-groups>*:
# where:
# type: url-test
# set:
# interval: 300
# ----------------------------------------------------------
# 7. 组合操作 - 在同一块内同时使用多个操作符
# 示例:同时前置和后置追加 DNS(必须写在同一 dns 块内)
# dns:
# nameserver+:
# - '1.0.0.1'
# +nameserver:
# - '119.29.29.29'
# 示例:同时删除旧 DNS 并前置插入新 DNS
# dns:
# nameserver-:
# - '8.8.8.8'
# +nameserver:
# - '223.5.5.5'
# ----------------------------------------------------------
# 8. <key> 语法补充 - 适用于键名含特殊字符时
# 后缀支持:+(后置)、-(删除)、!(强制覆盖)、*(批量更新),空串为默认合并
# 前置插入请使用 +<key> 写法(前缀形式)
# 示例:使用 <> 后置追加
# dns:
# <nameserver>+:
# - '8.8.8.8'
# 示例:使用 <> 删除数组元素
# dns:
# <nameserver>-:
# - '8.8.8.8'
# 示例:使用 <> 强制覆盖
# dns:
# <nameserver>!:
# - '114.114.114.114'
# 示例:使用 <> 批量更新(等同于 key*)
# <proxy-groups>*:
# where:
# type: url-test
# set:
# interval: 300
# 示例:使用 +<> 前置插入(键名含特殊字符时的前置写法)
# dns:
# +<nameserver>:
# - '119.29.29.29'
# ==========================================================
# ==== 以下为仅修改插件设置示例 ====
# ==========================================================
@@ -174,8 +174,10 @@ if [ -f "/tmp/openclash.bak" ]; then
cp -rf "/tmp/openclash/." "/etc/openclash/" >/dev/null 2>&1
#ui
if [ -d "/tmp/openclash_ui/" ]; then
rm -rf "/usr/share/openclash/ui/" >/dev/null 2>&1
cp -rf "/tmp/openclash_ui/." "/usr/share/openclash/ui/" >/dev/null 2>&1
if [ -d "/tmp/openclash_ui/metacubexd/" ] || [ -d "/tmp/openclash_ui/zashboard/" ] || [ -d "/tmp/openclash_ui/yacd/" ] || [ -d "/tmp/openclash_ui/dashboard/" ]; then
rm -rf "/usr/share/openclash/ui/" >/dev/null 2>&1
cp -rf "/tmp/openclash_ui/." "/usr/share/openclash/ui/" >/dev/null 2>&1
fi
rm -rf "/tmp/openclash_ui/" >/dev/null 2>&1
fi
#pac
@@ -264,6 +266,6 @@ sed -i '/.*kB maximum content size*/c\export let HTTP_MAX_CONTENT = 1024*10240;
/etc/init.d/uhttpd restart >/dev/null 2>&1
rm -f /tmp/luci-indexcache >/dev/null 2>&1
rm -f /tmp/luci-indexcache.* >/dev/null 2>&1
rm -rf /tmp/luci-indexcache /tmp/luci-indexcache.* /var/luci-indexcache /var/luci-indexcache.* >/dev/null 2>&1
rm -rf /tmp/luci-modulecache /var/luci-modulecache >/dev/null 2>&1
exit 0
@@ -1,119 +1,457 @@
module YAML
class << self
alias_method :load, :unsafe_load if YAML.respond_to? :unsafe_load
alias_method :original_dump, :dump
alias_method :original_load_file, :load_file
end
class << self
alias_method :load, :unsafe_load if YAML.respond_to? :unsafe_load
alias_method :original_dump, :dump
alias_method :original_load_file, :load_file
end
def self.LOG(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Info] " + "#{info}"
end
def self.LOG(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Info] " + "#{info}"
end
def self.LOG_ERROR(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Error] " + "#{info}"
end
def self.LOG_ERROR(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Error] " + "#{info}"
end
def self.LOG_WARN(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Warning] " + "#{info}"
end
def self.LOG_WARN(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Warning] " + "#{info}"
end
def self.LOG_TIP(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Tip] " + "#{info}"
end
def self.LOG_TIP(info)
puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + " [Tip] " + "#{info}"
end
# Keep `short-id` as string before YAML parsing so leading zeros are preserved.
# This is required for REALITY short-id values like `00000000`.
def self.load_file(filename, *args, **kwargs)
yaml_content = File.read(filename)
processed_content = fix_short_id_quotes(yaml_content)
def self.load_file(filename, *args, **kwargs)
yaml_content = File.read(filename)
processed_content = fix_short_id_quotes(yaml_content)
if kwargs.empty?
load(processed_content, *args)
else
load(processed_content, *args, **kwargs)
end
end
load(processed_content, *args, **kwargs)
end
def self.dump(obj, io = nil, **options)
begin
if io.nil?
yaml_content = original_dump(obj, **options)
fix_short_id_quotes(yaml_content)
elsif io.respond_to?(:write)
require 'stringio'
temp_io = StringIO.new
original_dump(obj, temp_io, **options)
yaml_content = temp_io.string
processed_content = fix_short_id_quotes(yaml_content)
io.write(processed_content)
io
else
yaml_content = original_dump(obj, io, **options)
fix_short_id_quotes(yaml_content)
end
rescue => e
LOG_ERROR("Write file failed:【%s】" % [e.message])
nil
end
end
def self.dump(obj, io = nil, **options)
begin
yaml_content = original_dump(obj, **options)
processed = fix_short_id_quotes(yaml_content)
if io.nil?
processed
elsif io.respond_to?(:write)
io.write(processed)
io
else
processed
end
rescue => e
LOG_ERROR("Write file failed:【%s】" % [e.message])
nil
end
end
private
private
SHORT_ID_REGEX = /^(\s*)short-id:\s*(.*)$/
LIST_ITEM_REGEX = /^(\s*)-\s*(.*)$/
KEY_REGEX = /^(\s*)([a-zA-Z0-9_-]+):\s*(.*)$/
QUOTED_VALUE_REGEX = /^["'].*["']$/
# fix_short_id_quotes:
# Purpose: ensure YAML `short-id` values are emitted with the intended
# representation when dumping or re-writing YAML files.
# Behavior:
# - Non-null, non-empty scalar `short-id` values (and items inside
# `short-id` sequences) are written as double-quoted strings.
# - Empty strings are preserved as empty quoted strings ("" remains "").
# - Null values are preserved and may be emitted explicitly (e.g. `! "").
# Lastly, use gsub to clean up any `! ""` tags
# Examples:
# Input: short-id: 00000000 -> Output: short-id: "00000000"
# Input: short-id: "" -> Output: short-id: ""
# Input: short-id: "abc123" -> Output: short-id: "abc123"
# Input: short-id: "1600e237" -> Output: short-id: "1600e237"
# Input: short-id: null -> Output: short-id: ""
# Inline map support, e.g. reality-opts: { ..., short-id: 00000000 }
INLINE_SHORT_ID_REGEX = /(short-id:\s*)(?!["'\[])([^\s,"'{}\[\]\n\r]+)(?=\s*(?:[,}\]\n\r]|$))/m.freeze
def self.fix_short_id_quotes(yaml_content)
return yaml_content unless yaml_content.include?('short-id:')
def self.fix_short_id_quotes(yaml_content)
return yaml_content unless yaml_content.include?('short-id:')
begin
stream = Psych.parse_stream(yaml_content)
begin
# First, normalize inline-map style unquoted short-id.
processed = yaml_content.gsub(INLINE_SHORT_ID_REGEX) do
"#{$1}\"#{$2}\""
end
traverse = lambda do |node|
case node
when Psych::Nodes::Mapping
children = node.children || []
i = 0
while i < children.length
key = children[i]
val = children[i + 1]
if key.is_a?(Psych::Nodes::Scalar) && key.value == 'short-id'
if val.is_a?(Psych::Nodes::Scalar)
is_null_scalar = (val.tag == 'tag:yaml.org,2002:null') || (val.tag == '!!null') || (val.value =~ /^\s*(~|null|NULL|Null)\s*$/)
unless is_null_scalar
val.tag = nil
val.style = defined?(Psych::Nodes::Scalar::DOUBLE_QUOTED) ? Psych::Nodes::Scalar::DOUBLE_QUOTED : 2
end
elsif val.is_a?(Psych::Nodes::Sequence)
val.children.each do |child|
if child.is_a?(Psych::Nodes::Scalar)
is_null_child = (child.tag == 'tag:yaml.org,2002:null') || (child.tag == '!!null') || (child.value =~ /^\s*(~|null|NULL|Null)\s*$/)
unless is_null_child
child.tag = nil
child.style = defined?(Psych::Nodes::Scalar::DOUBLE_QUOTED) ? Psych::Nodes::Scalar::DOUBLE_QUOTED : 2
end
end
end
end
else
traverse.call(key) if key.respond_to?(:children)
traverse.call(val) if val.respond_to?(:children)
end
i += 2
end
when Psych::Nodes::Sequence
(node.children || []).each { |c| traverse.call(c) }
when Psych::Nodes::Document, Psych::Nodes::Stream
(node.children || []).each { |c| traverse.call(c) }
else
if node.respond_to?(:children)
(node.children || []).each { |c| traverse.call(c) }
end
end
end
lines = processed.lines
short_id_indices = lines.each_index.select { |i| lines[i] =~ SHORT_ID_REGEX }
short_id_indices.each do |short_id_index|
line = lines[short_id_index]
if line =~ SHORT_ID_REGEX
indent = $1
value = $2.strip
if value.empty?
(short_id_index + 1...lines.size).each do |i|
line = lines[i]
next if line.strip.empty?
if line[/^\s*/].length <= short_id_indent_len
break
end
if line =~ LIST_ITEM_REGEX
indent = $1
value = $2.strip
if value =~ KEY_REGEX
break
end
if value !~ QUOTED_VALUE_REGEX
lines[i] = "#{indent}- \"#{value}\"\n"
end
elsif line =~ KEY_REGEX
break
end
end
else
if value !~ QUOTED_VALUE_REGEX
lines[short_id_index] = "#{indent}short-id: \"#{value}\"\n"
end
end
end
end
lines.join
rescue => e
LOG_ERROR("Fix short-id values type failed:【%s】" % [e.message])
yaml_content
end
end
end
stream.children.each do |doc_node|
if doc_node.is_a?(Psych::Nodes::Document)
traverse.call(doc_node.root) if doc_node.root
end
end
if stream.respond_to?(:to_yaml)
processed_yaml = stream.to_yaml
processed_yaml = processed_yaml.gsub(/^([ \t]*short-id:\s*)!\s*/, "\\1")
processed_yaml
else
yaml_content
end
rescue => e
LOG_ERROR("Fix short-id values type failed:【%s】" % [e.message])
yaml_content
end
end
def self.overwrite(base, override)
return override if base.nil?
return base if override.nil?
current_key = nil
current_operation = nil
begin
case override
when Hash
result = base.is_a?(Hash) ? base.dup : {}
override.each do |key, value|
current_key = key
processed_key, operation = parse_key(key)
current_operation = operation
applied = apply_operation(result[processed_key], value, operation)
if applied.equal?(DELETED_SENTINEL)
result.delete(processed_key)
else
result[processed_key] = applied
end
end
result
else
override
end
rescue => e
LOG_ERROR("YAML overwrite failed:【key: %s, operation: %s, error: %s】" % [current_key, current_operation, e.message])
base
end
end
private
def self.parse_key(key)
key_str = key.to_s
# +<key>
if key_str.start_with?('+<') && key_str.include?('>')
close_idx = key_str.index('>')
inner_key = key_str[2...close_idx]
return inner_key, :prepend_array
end
# <key>suffix
if key_str.start_with?('<') && key_str.include?('>')
close_idx = key_str.index('>')
inner_key = key_str[1...close_idx]
suffix = key_str[(close_idx + 1)..-1]
return inner_key, determine_operation(suffix)
end
# 前缀 +key
if key_str.start_with?('+')
return key_str[1..-1], :prepend_array
end
# 尾部(支持 +, !, *, -)
if key_str =~ /^(.*?)([+!*\-])$/
return Regexp.last_match(1), determine_operation(Regexp.last_match(2))
end
[key_str, :merge]
end
def self.determine_operation(suffix)
case suffix
when '+'
:append_array
when '-'
:delete
when '!'
:force_overwrite
when '*'
:batch_update
else
:merge
end
end
def self.match_value(target, condition)
return false if target.nil? || condition.nil?
begin
if condition.is_a?(String) && condition.start_with?('/') && condition.end_with?('/')
pattern = condition[1...-1]
regexp = Regexp.new(pattern)
if target.is_a?(Array)
target.any? { |item| item.to_s =~ regexp }
else
target.to_s =~ regexp
end
elsif condition.is_a?(Array) && target.is_a?(Array)
condition.all? { |c| target.include?(c) }
else
target == condition
end
rescue => e
LOG_ERROR("YAML overwrite failed:【(match value) => target: %s, condition: %s, error: %s】" % [target, condition, e.message])
false
end
end
def self.deep_dup(obj)
case obj
when Array
obj.map { |x| deep_dup(x) }
when Hash
obj.transform_values { |v| deep_dup(v) }
else
obj.dup rescue obj
end
end
def self.merge_hash(base, value, prepend: false)
if prepend
result = {}
value.each do |k, v|
if base.key?(k)
result[k] = apply_operation(base[k], v, :merge)
else
result[k] = deep_dup(v)
end
end
base.each do |k, v|
result[k] = deep_dup(v) unless result.key?(k)
end
result
else
result = deep_dup(base)
value.each do |k, v|
if result.key?(k)
result[k] = apply_operation(result[k], v, :merge)
else
result[k] = deep_dup(v)
end
end
result
end
end
def self.delete_from_hash(base, value)
result = deep_dup(base)
case value
when Array
value.each { |k| result.delete(k) }
when Hash
value.each do |k, v|
if v.nil? || v == true
result.delete(k)
elsif result[k].is_a?(Hash) && v.is_a?(Hash)
nested = apply_operation(result[k], v, :delete)
if nested.equal?(DELETED_SENTINEL)
result.delete(k)
else
result[k] = nested
end
else
result.delete(k)
end
end
else
result.delete(value)
end
result
end
DELETED_SENTINEL = Object.new.freeze
def self.apply_operation(base, value, operation)
case operation
when :delete
if base.is_a?(Array) && value.is_a?(Array)
base - value
elsif base.is_a?(Array) && !value.nil?
base - [value]
elsif base.is_a?(Hash)
delete_from_hash(base, value)
else
DELETED_SENTINEL
end
when :force_overwrite
deep_dup(value)
when :prepend_array
if base.is_a?(Array) && value.is_a?(Array)
(deep_dup(value) + base).uniq
elsif base.is_a?(Hash) && value.is_a?(Hash)
merge_hash(base, value, prepend: true)
else
deep_dup(value)
end
when :append_array
if base.is_a?(Array) && value.is_a?(Array)
base_dup = base.dup
deep_dup(value).each { |v| base_dup.delete(v) }
base_dup + deep_dup(value)
elsif base.is_a?(Hash) && value.is_a?(Hash)
merge_hash(base, value, prepend: false)
else
deep_dup(value)
end
when :batch_update
batch_update_items(base, value)
when :merge
if base.is_a?(Hash) && value.is_a?(Hash)
overwrite(base, value)
elsif value.nil?
base
else
deep_dup(value)
end
else
deep_dup(value)
end
end
def self.apply_set_fields(item, set_values)
keys_to_delete = []
set_values.each do |k, v|
processed_key, operation = parse_key(k)
result = apply_operation(item[processed_key], v, operation)
if result.equal?(DELETED_SENTINEL)
keys_to_delete << processed_key
else
item[processed_key] = result
end
end
keys_to_delete.each { |k| item.delete(k) }
end
def self.match_item(item, where_conditions, key = nil)
where_conditions.all? do |k, v|
if k == 'key' && !key.nil?
match_value(key, v)
elsif item.is_a?(Hash)
match_value(item[k] || item[k.to_s], v)
elsif item.is_a?(String) && k == 'value'
match_value(item, v)
else
false
end
end
end
def self.batch_update_items(collection, update_spec)
return collection unless update_spec.is_a?(Hash)
begin
where_conditions = update_spec['where'] || {}
set_values = update_spec['set'] || {}
if collection.is_a?(Array)
result = collection.dup
delete_indices = []
result.each_with_index do |item, index|
match = match_item(item, where_conditions)
if match
if item.is_a?(Hash)
apply_set_fields(item, set_values)
elsif item.is_a?(String) && set_values.key?('value')
new_value = set_values['value']
if new_value.nil?
delete_indices << index
else
result[index] = deep_dup(new_value)
end
end
end
end
delete_indices.reverse_each { |i| result.delete_at(i) }
result
elsif collection.is_a?(Hash)
if where_conditions.any? { |k, _| k != 'key' } &&
match_item(collection, where_conditions)
result = collection.dup
apply_set_fields(result, set_values)
result
else
result = collection.dup
keys_to_delete = []
result.each do |key, value|
next unless value.is_a?(Hash)
match = match_item(value, where_conditions, key)
if match
if set_values.key?('key-') || (set_values.key?('key') && set_values['key'].nil?)
keys_to_delete << key
else
apply_set_fields(value, set_values)
end
end
end
keys_to_delete.each { |k| result.delete(k) }
result
end
elsif collection.nil?
nil
else
collection
end
rescue => e
LOG_ERROR("YAML overwrite failed:【(batch update) => update_spec: %s, error: %s】" % [update_spec, e.message])
collection
end
end
end
@@ -86,17 +86,6 @@ DOWNLOAD_RESULT=$?
config_cus_up()
{
if [ -z "$CONFIG_PATH" ]; then
for file_name in /etc/openclash/config/*
do
if [ -f "$file_name" ]; then
CONFIG_PATH=$file_name
break
fi
done
uci -q set openclash.config.config_path="$CONFIG_PATH"
uci commit openclash
fi
if [ -z "$subscribe_url_param" ]; then
if [ -n "$key_match_param" ] || [ -n "$key_ex_match_param" ]; then
LOG_OUT "Config File【$name】Start Picking Nodes..."
@@ -192,6 +181,10 @@ config_su_check()
mv "$CFG_FILE" "$CONFIG_FILE" 2>/dev/null
LOG_OUT "Config File【$name】Update Successful!"
fi
if [ -z "$CONFIG_PATH" ]; then
uci -q set openclash.config.config_path="$CONFIG_FILE"
uci commit openclash
fi
if [ "$CONFIG_FILE" == "$CONFIG_PATH" ]; then
restart=1
fi
@@ -335,13 +328,13 @@ sub_info_get()
config_get "udp" "$section" "udp" ""
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
config_get "sort" "$section" "sort" ""
config_get "convert_address" "$section" "convert_address" ""
config_get "convert_address" "$section" "convert_address" "https://api.asailor.org/sub"
config_get "template" "$section" "template" ""
config_get "node_type" "$section" "node_type" ""
config_get "rule_provider" "$section" "rule_provider" ""
config_get "custom_template_url" "$section" "custom_template_url" ""
config_get "de_ex_keyword" "$section" "de_ex_keyword" ""
config_get "sub_ua" "$section" "sub_ua" "clash.meta"
config_get "sub_ua" "$section" "sub_ua" "clash-verge/v2.4.5"
CONFIG_FILE="/etc/openclash/config/$name.yaml"
CFG_FILE="/tmp/$name.yaml"
@@ -74,7 +74,7 @@ DOWNLOAD_FILE_CURL() {
fi
else
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 60 --speed-time 30 --speed-limit 1 --retry 2 -H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" -o "$DOWNLOAD_PATH" 2>&1)
EXIR_CODE=${PIPESTATUS[0]}
EXIR_CODE=$?
HTTP_CODE=$(echo "$CURL_OUTPUT" | tail -n1)
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" -ne 200 ]; then
@@ -0,0 +1,179 @@
#!/bin/bash
. /usr/share/openclash/openclash_ps.sh
. /usr/share/openclash/log.sh
. /usr/share/openclash/openclash_curl.sh
. /usr/share/openclash/uci.sh
set_lock() {
exec 888>"/tmp/lock/openclash_update_databases.lock" 2>/dev/null
flock -x 888 2>/dev/null
}
del_lock() {
flock -u 888 2>/dev/null
rm -rf "/tmp/lock/openclash_update_databases.lock" 2>/dev/null
}
set_lock
inc_job_counter
small_flash_memory=$(uci_get_config "small_flash_memory")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
restart=0
update_one() {
local type="$1"
local custom_key="$2"
local tmpfile="$3"
local path="$4"
local name="$5"
local min_size=10240
local download_url=""
local custom_url=$(uci_get_config "$custom_key")
if [ "$small_flash_memory" != "1" ]; then
mkdir -p /etc/openclash
else
mkdir -p /tmp/etc/openclash
fi
case "$type" in
geoip)
if [ -z "$custom_url" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
download_url="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
else
download_url="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
fi
else
download_url="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
fi
else
download_url="$custom_url"
fi
;;
geosite)
if [ -z "$custom_url" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
download_url="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
else
download_url="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
fi
else
download_url="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
fi
else
download_url="$custom_url"
fi
;;
geoasn)
if [ -z "$custom_url" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
download_url="${github_address_mod}gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb"
else
download_url="${github_address_mod}https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
fi
else
download_url="https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
fi
else
download_url="$custom_url"
fi
;;
ipdb)
if [ -z "$custom_url" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
download_url="${github_address_mod}gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb"
else
download_url="${github_address_mod}https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
fi
else
download_url="https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
fi
else
download_url="$custom_url"
fi
;;
*)
return 1
;;
esac
LOG_OUT "【$name】Start Downloading Database..."
DOWNLOAD_FILE_CURL "$download_url" "/tmp/$tmpfile" "$path"
DOWNLOAD_RESULT=$?
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/$tmpfile" ]; then
if head -c 512 "/tmp/$tmpfile" | grep -qiE "<!doctype|<html|<head|<body"; then
LOG_OUT "【$name】Download Failed: HTML Response Detected, Abort Update..."
rm -rf "/tmp/$tmpfile"
elif [ $(du -b "/tmp/$tmpfile" 2>/dev/null | awk '{print $1}' || echo "$min_size") -lt "$min_size" ]; then
LOG_OUT "【$name】Download Failed: File Size Too Small, Abort Update..."
rm -rf "/tmp/$tmpfile"
else
LOG_OUT "【$name】Download Success, Check Updated..."
cmp -s "/tmp/$tmpfile" "$path"
if [ "$?" -ne 0 ]; then
LOG_OUT "【$name】Has Been Updated, Starting To Replace The Old Version..."
case "$type" in
geoip)
rm -rf "/etc/openclash/geoip.dat"
;;
geosite)
rm -rf "/etc/openclash/geosite.dat"
;;
geoasn)
rm -rf "/etc/openclash/GeoLite2-ASN.mmdb"
;;
esac
mv "/tmp/$tmpfile" "$path" >/dev/null 2>&1
LOG_OUT "【$name】Update Successful!"
restart=1
else
LOG_OUT "【$name】No Change, Do Nothing..."
fi
fi
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
LOG_OUT "【$name】No Change, Do Nothing..."
else
LOG_OUT "【$name】Update Error, Please Try Again Later..."
fi
rm -rf "/tmp/$tmpfile" >/dev/null 2>&1
}
case "$1" in
all)
update_one geoip "geoip_custom_url" "GeoIP.dat" "/etc/openclash/GeoIP.dat" "GeoIP.dat"
update_one geosite "geosite_custom_url" "GeoSite.dat" "/etc/openclash/GeoSite.dat" "GeoSite.dat"
update_one geoasn "geoasn_custom_url" "GeoLite2-ASN.mmdb" "/etc/openclash/ASN.mmdb" "ASN.mmdb"
update_one ipdb "geo_custom_url" "Country.mmdb" "/etc/openclash/Country.mmdb" "Country.mmdb"
;;
geoip)
update_one geoip "geoip_custom_url" "GeoIP.dat" "/etc/openclash/GeoIP.dat" "GeoIP.dat"
;;
geosite)
update_one geosite "geosite_custom_url" "GeoSite.dat" "/etc/openclash/GeoSite.dat" "GeoSite.dat"
;;
geoasn)
update_one geoasn "geoasn_custom_url" "GeoLite2-ASN.mmdb" "/etc/openclash/ASN.mmdb" "ASN.mmdb"
;;
ipdb)
update_one ipdb "geo_custom_url" "Country.mmdb" "/etc/openclash/Country.mmdb" "Country.mmdb"
;;
*)
echo "Usage: $0 {all|geoip|geosite|geoasn|ipdb}"
del_lock
dec_job_counter_and_restart "$restart"
exit 1
;;
esac
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
del_lock
@@ -1,70 +0,0 @@
#!/bin/bash
. /usr/share/openclash/openclash_ps.sh
. /usr/share/openclash/log.sh
. /usr/share/openclash/openclash_curl.sh
. /usr/share/openclash/uci.sh
set_lock() {
exec 874>"/tmp/lock/openclash_geoasn.lock" 2>/dev/null
flock -x 874 2>/dev/null
}
del_lock() {
flock -u 874 2>/dev/null
rm -rf "/tmp/lock/openclash_geoasn.lock" 2>/dev/null
}
set_lock
inc_job_counter
small_flash_memory=$(uci_get_config "small_flash_memory")
GEOASN_CUSTOM_URL=$(uci_get_config "geoasn_custom_url")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
restart=0
if [ "$small_flash_memory" != "1" ]; then
geoasn_path="/etc/openclash/ASN.mmdb"
mkdir -p /etc/openclash
else
geoasn_path="/tmp/etc/openclash/ASN.mmdb"
mkdir -p /tmp/etc/openclash
fi
LOG_OUT "Start Downloading Geo ASN Database..."
if [ -z "$GEOASN_CUSTOM_URL" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb"
else
DOWNLOAD_URL="${github_address_mod}https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
fi
else
DOWNLOAD_URL="https://github.com/xishang0128/geoip/releases/latest/download/GeoLite2-ASN.mmdb"
fi
else
DOWNLOAD_URL=$GEOASN_CUSTOM_URL
fi
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/GeoLite2-ASN.mmdb" "$geoasn_path"
DOWNLOAD_RESULT=$?
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/GeoLite2-ASN.mmdb" ]; then
LOG_OUT "Geo ASN Database Download Success, Check Updated..."
cmp -s /tmp/GeoLite2-ASN.mmdb "$geoasn_path"
if [ "$?" -ne "0" ]; then
LOG_OUT "Geo ASN Database Has Been Updated, Starting To Replace The Old Version..."
rm -rf "/etc/openclash/GeoLite2-ASN.mmdb"
mv /tmp/GeoLite2-ASN.mmdb "$geoasn_path" >/dev/null 2>&1
LOG_OUT "Geo ASN Database Update Successful!"
restart=1
else
LOG_OUT "Updated Geo ASN Database No Change, Do Nothing..."
fi
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
LOG_OUT "Updated Geo ASN Database No Change, Do Nothing..."
else
LOG_OUT "Geo ASN Database Update Error, Please Try Again Later..."
fi
rm -rf /tmp/GeoLite2-ASN.mmdb >/dev/null 2>&1
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
del_lock
@@ -1,70 +0,0 @@
#!/bin/bash
. /usr/share/openclash/openclash_ps.sh
. /usr/share/openclash/log.sh
. /usr/share/openclash/openclash_curl.sh
. /usr/share/openclash/uci.sh
set_lock() {
exec 873>"/tmp/lock/openclash_geoip.lock" 2>/dev/null
flock -x 873 2>/dev/null
}
del_lock() {
flock -u 873 2>/dev/null
rm -rf "/tmp/lock/openclash_geoip.lock" 2>/dev/null
}
set_lock
inc_job_counter
small_flash_memory=$(uci_get_config "small_flash_memory")
GEOIP_CUSTOM_URL=$(uci_get_config "geoip_custom_url")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
restart=0
if [ "$small_flash_memory" != "1" ]; then
geoip_path="/etc/openclash/GeoIP.dat"
mkdir -p /etc/openclash
else
geoip_path="/tmp/etc/openclash/GeoIP.dat"
mkdir -p /tmp/etc/openclash
fi
LOG_OUT "Start Downloading GeoIP Dat..."
if [ -z "$GEOIP_CUSTOM_URL" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
else
DOWNLOAD_URL="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
fi
else
DOWNLOAD_URL="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
fi
else
DOWNLOAD_URL=$GEOIP_CUSTOM_URL
fi
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/GeoIP.dat" "$geoip_path"
DOWNLOAD_RESULT=$?
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/GeoIP.dat" ]; then
LOG_OUT "GeoIP Dat Download Success, Check Updated..."
cmp -s /tmp/GeoIP.dat "$geoip_path"
if [ "$?" -ne "0" ]; then
LOG_OUT "GeoIP Dat Has Been Updated, Starting To Replace The Old Version..."
rm -rf "/etc/openclash/geoip.dat"
mv /tmp/GeoIP.dat "$geoip_path" >/dev/null 2>&1
LOG_OUT "GeoIP Dat Update Successful!"
restart=1
else
LOG_OUT "Updated GeoIP Dat No Change, Do Nothing..."
fi
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
LOG_OUT "Updated GeoIP Dat No Change, Do Nothing..."
else
LOG_OUT "GeoIP Dat Update Error, Please Try Again Later..."
fi
rm -rf /tmp/GeoIP.dat >/dev/null 2>&1
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
del_lock
@@ -1,70 +0,0 @@
#!/bin/bash
. /usr/share/openclash/openclash_ps.sh
. /usr/share/openclash/log.sh
. /usr/share/openclash/openclash_curl.sh
. /usr/share/openclash/uci.sh
set_lock() {
exec 874>"/tmp/lock/openclash_geosite.lock" 2>/dev/null
flock -x 874 2>/dev/null
}
del_lock() {
flock -u 874 2>/dev/null
rm -rf "/tmp/lock/openclash_geosite.lock" 2>/dev/null
}
set_lock
inc_job_counter
small_flash_memory=$(uci_get_config "small_flash_memory")
GEOSITE_CUSTOM_URL=$(uci_get_config "geosite_custom_url")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
restart=0
if [ "$small_flash_memory" != "1" ]; then
geosite_path="/etc/openclash/GeoSite.dat"
mkdir -p /etc/openclash
else
geosite_path="/tmp/etc/openclash/GeoSite.dat"
mkdir -p /tmp/etc/openclash
fi
LOG_OUT "Start Downloading GeoSite Database..."
if [ -z "$GEOSITE_CUSTOM_URL" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
else
DOWNLOAD_URL="${github_address_mod}https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
fi
else
DOWNLOAD_URL="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
fi
else
DOWNLOAD_URL=$GEOSITE_CUSTOM_URL
fi
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/GeoSite.dat" "$geosite_path"
DOWNLOAD_RESULT=$?
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/GeoSite.dat" ]; then
LOG_OUT "GeoSite Database Download Success, Check Updated..."
cmp -s /tmp/GeoSite.dat "$geosite_path"
if [ "$?" -ne "0" ]; then
LOG_OUT "GeoSite Database Has Been Updated, Starting To Replace The Old Version..."
rm -rf "/etc/openclash/geosite.dat"
mv /tmp/GeoSite.dat "$geosite_path" >/dev/null 2>&1
LOG_OUT "GeoSite Database Update Successful!"
restart=1
else
LOG_OUT "Updated GeoSite Database No Change, Do Nothing..."
fi
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
LOG_OUT "Updated GeoSite Database No Change, Do Nothing..."
else
LOG_OUT "GeoSite Database Update Error, Please Try Again Later..."
fi
rm -rf /tmp/GeoSite.dat >/dev/null 2>&1
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
del_lock
@@ -1,69 +0,0 @@
#!/bin/bash
. /usr/share/openclash/openclash_ps.sh
. /usr/share/openclash/log.sh
. /usr/share/openclash/openclash_curl.sh
. /usr/share/openclash/uci.sh
set_lock() {
exec 880>"/tmp/lock/openclash_ipdb.lock" 2>/dev/null
flock -x 880 2>/dev/null
}
del_lock() {
flock -u 880 2>/dev/null
rm -rf "/tmp/lock/openclash_ipdb.lock" 2>/dev/null
}
set_lock
inc_job_counter
small_flash_memory=$(uci_get_config "small_flash_memory")
GEOIP_CUSTOM_URL=$(uci_get_config "geo_custom_url")
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
restart=0
if [ "$small_flash_memory" != "1" ]; then
geoip_path="/etc/openclash/Country.mmdb"
mkdir -p /etc/openclash
else
geoip_path="/tmp/etc/openclash/Country.mmdb"
mkdir -p /tmp/etc/openclash
fi
LOG_OUT "Start Downloading Geoip Database..."
if [ -z "$GEOIP_CUSTOM_URL" ]; then
if [ "$github_address_mod" != "0" ]; then
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
DOWNLOAD_URL="${github_address_mod}gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb"
else
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
fi
else
DOWNLOAD_URL="https://raw.githubusercontent.com/alecthw/mmdb_china_ip_list/release/lite/Country.mmdb"
fi
else
DOWNLOAD_URL=$GEOIP_CUSTOM_URL
fi
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "/tmp/Country.mmdb" "$geoip_path"
DOWNLOAD_RESULT=$?
if [ "$DOWNLOAD_RESULT" -eq 0 ] && [ -s "/tmp/Country.mmdb" ]; then
LOG_OUT "Geoip Database Download Success, Check Updated..."
cmp -s /tmp/Country.mmdb "$geoip_path"
if [ "$?" -ne 0 ]; then
LOG_OUT "Geoip Database Has Been Updated, Starting To Replace The Old Version..."
mv /tmp/Country.mmdb "$geoip_path" >/dev/null 2>&1
LOG_OUT "Geoip Database Update Successful!"
restart=1
else
LOG_OUT "Updated Geoip Database No Change, Do Nothing..."
fi
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
LOG_OUT "Updated Geoip Database No Change, Do Nothing..."
else
LOG_OUT "Geoip Database Update Error, Please Try Again Later..."
fi
rm -rf /tmp/Country.mmdb >/dev/null 2>&1
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
del_lock
@@ -46,7 +46,7 @@ function unlock_auto_select()
local no_old_region_unlock = {}
local full_support_list = {}
local fallback_select = {}
local gorup_i18 = "Group:"
local gorup_i18 = "【"..type.."】Group:"
local no_group_find = "failed to search based on keywords and automatically obtain the group, please confirm the validity of the regex!"
local full_support_no_area = "full support"
local full_support = "full support, area:"
@@ -119,6 +119,10 @@ function unlock_auto_select()
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_google_not_cn") or "google|谷歌"
elseif type == "OpenAI" then
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_openai") or "OpenAI|ChatGPT"
elseif type == "Claude" then
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_claude") or "Claude"
elseif type == "Gemini" then
key_group = FS.uci_get_config("config", "stream_auto_select_group_key_gemini") or "Gemini"
end
if not key_group then key_group = type end
else
@@ -148,9 +152,9 @@ function unlock_auto_select()
--test now proxy
region = proxy_unlock_test()
if table_include(groups, now_name) then
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.."】"
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.."】"
else
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
end
if status ~= 2 and status ~= 4 then
region = proxy_unlock_test()
@@ -244,7 +248,7 @@ function unlock_auto_select()
end
end
if #(value.all) == 0 then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..no_nodes_filter)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..no_nodes_filter)
break
end
--loop proxy test
@@ -274,16 +278,16 @@ function unlock_auto_select()
end
end
if #(proxies) == 0 then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.."】"..no_nodes_filter)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.."】"..no_nodes_filter)
break
end
for p = 1, #(proxies) do
proxy = proxies[p]
if table_include(groups, proxy) then
group_now = get_group_now(info, proxy)
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..group_now.."】"
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..group_now.."】"
else
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..proxy.."】"
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..proxy.."】"
end
--skip tested proxy
while true do
@@ -303,7 +307,7 @@ function unlock_auto_select()
table.insert(full_support_list, {value.all[i], group_name, proxy, region})
if not all_test then
print(now..full_support.."【"..region.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】"..area_i18.."【"..region.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】"..area_i18.."【"..region.."】")
else
print(now..full_support.."【"..region.."】")
end
@@ -312,7 +316,7 @@ function unlock_auto_select()
table.insert(full_support_list, {value.all[i], group_name, proxy})
if not all_test then
print(now..full_support_no_area)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..proxy.."】")
else
print(now..full_support_no_area)
end
@@ -372,16 +376,16 @@ function unlock_auto_select()
end
region = proxy_unlock_test()
if table_include(groups, now_name) then
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.."】"
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.."】"
else
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
now = os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..group_show.." ➟ "..now_name.."】"
end
if status == 2 then
if region ~= "" then
table.insert(full_support_list, {value.all[i], group_name, value.all[i], region})
if not all_test then
print(now..full_support.."【"..region.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】"..area_i18.."【"..region.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】"..area_i18.."【"..region.."】")
else
print(now..full_support.."【"..region.."】")
end
@@ -390,7 +394,7 @@ function unlock_auto_select()
table.insert(full_support_list, {value.all[i], group_name, value.all[i]})
if not all_test then
print(now..full_support_no_area)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, now_name).."】")
else
print(now..full_support_no_area)
end
@@ -469,24 +473,24 @@ function unlock_auto_select()
group_now = group_now .. area_i18 .. "【"..v[4].."】"
end
if #full_support_list > 0 then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_full_support..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_full_support..group_now)
elseif #no_old_region_unlock > 0 then
if not all_test then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success_no_old_region..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success_no_old_region..group_now)
else
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_no_old_region..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_no_old_region..group_now)
end
elseif #other_region_unlock > 0 then
if not all_test then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_faild_other_region..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_faild_other_region..group_now)
else
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_other_region..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_other_region..group_now)
end
else
if not all_test then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_faild..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_faild..group_now)
else
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_all_faild..group_now)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_all_faild..group_now)
end
end
close_connections()
@@ -499,7 +503,7 @@ function unlock_auto_select()
else
group_now = value.name.." ➟ "..proxy_default
end
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_faild.."【"..group_now.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_faild.."【"..group_now.."】")
end
end
elseif #nodes_filter(get_group_now(info, value.name), info) ~= 0 then
@@ -509,12 +513,12 @@ function unlock_auto_select()
if not all_test then
print(now..full_support.."【"..region.."】")
end
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】"..area_i18.."【"..region.."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】"..area_i18.."【"..region.."】")
else
if not all_test then
print(now..full_support_no_area)
end
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】")
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..value.name.."】"..select_success.."【"..get_group_now(info, value.name).."】")
end
if not all_test then
break
@@ -548,7 +552,7 @@ function unlock_auto_select()
if status == 2 and not all_test then break end
end
if not group_match and not auto_get_group then
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." "..gorup_i18.."【"..key_group.."】"..no_group_find)
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..gorup_i18.."【"..key_group.."】"..no_group_find)
end
--write cache
FS.writefile(unlock_cache_file, JSON.stringify(unlock_cache_info))
@@ -817,6 +821,10 @@ function nodes_filter(t, info)
regex = FS.uci_get_config("config", "stream_auto_select_node_key_google_not_cn") or ""
elseif type == "OpenAI" then
regex = FS.uci_get_config("config", "stream_auto_select_node_key_openai") or ""
elseif type == "Claude" then
regex = FS.uci_get_config("config", "stream_auto_select_node_key_claude") or ""
elseif type == "Gemini" then
regex = FS.uci_get_config("config", "stream_auto_select_node_key_gemini") or ""
end
if class_type(t) == "table" then
@@ -877,6 +885,10 @@ function proxy_unlock_test()
region = google_not_cn_test()
elseif type == "OpenAI" then
region = openai_unlock_test()
elseif type == "Claude" then
region = claude_unlock_test()
elseif type == "Gemini" then
region = gemini_unlock_test()
end
return region
end
@@ -908,6 +920,10 @@ function auto_get_policy_group(passwd, ip, port)
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://timeline.google.com &')
elseif type == "OpenAI" then
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://chatgpt.com/ &')
elseif type == "Claude" then
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://claude.ai/ &')
elseif type == "Gemini" then
SYS.call('curl -sL -m 5 --limit-rate 1k -o /dev/null https://gemini.google.com/ &')
end
os.execute("sleep 1")
con = SYS.exec(string.format('curl -sL -m 5 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://%s:%s/connections', passwd, ip, port))
@@ -976,6 +992,16 @@ function auto_get_policy_group(passwd, ip, port)
auto_get_group = con.connections[i].chains[#(con.connections[i].chains)]
break
end
elseif type == "Claude" then
if string.match(con.connections[i].metadata.host, "claude%.ai") then
auto_get_group = con.connections[i].chains[#(con.connections[i].chains)]
break
end
elseif type == "Gemini" then
if string.match(con.connections[i].metadata.host, "gemini%.google%.com") then
auto_get_group = con.connections[i].chains[#(con.connections[i].chains)]
break
end
end
end
end
@@ -1571,6 +1597,82 @@ function openai_unlock_test()
return region
end
function claude_unlock_test()
status = 0
local url = "https://claude.ai/"
local region_url = "https://claude.ai/cdn-cgi/trace"
local regex = FS.uci_get_config("config", "stream_auto_select_region_key_claude") or ""
local region = ""
local old_region = get_old_region()
local old_regex = get_old_regex()
local data = SYS.exec(string.format("curl -sIL --connect-timeout 5 -m 5 --speed-time 5 --speed-limit 1 --retry 2 -H 'Accept-Language: en' -H 'Content-Type: application/json' -H 'User-Agent: %s' '%s'", UA, url))
if data then
if string.find(data, "App unavailable in region") then
status = 1
else
status = 2
local region_data = SYS.exec(string.format("curl -sL --connect-timeout 5 -m 5 --speed-time 5 --speed-limit 1 --retry 2 -H 'Accept-Language: en' -H 'Content-Type: application/json' -H 'User-Agent: %s' '%s'", UA, region_url))
if region_data and string.match(region_data, "loc=%a+") then
region = string.upper(string.sub(string.match(region_data, "loc=%a+"), 5, -1))
if not datamatch(region, regex) then
status = 3
elseif old_regex ~= regex and not all_test then
status = 2
elseif old_region ~= "" and region ~= old_region and not all_test then
status = 4
end
if status == 2 and not all_test and region ~= old_region then
write_cache(type, "old_region", region)
end
if status == 2 and not all_test and regex ~= old_regex then
write_cache(type, "old_regex", regex)
end
end
end
end
return region
end
function gemini_unlock_test()
status = 0
local url = "https://gemini.google.com/"
local regex = FS.uci_get_config("config", "stream_auto_select_region_key_gemini") or ""
local region = ""
local old_region = get_old_region()
local old_regex = get_old_regex()
local data = SYS.exec(string.format("curl -sL --connect-timeout 5 -m 5 --speed-time 5 --speed-limit 1 --retry 2 -H 'Accept-Language: en' -H 'Content-Type: application/json' -H 'User-Agent: %s' '%s'", UA, url))
if not data or string.sub(data, 1, 4) == "curl" then
status = 1
return region
end
if string.find(data, "45631641,null,true", 1, true) then
local cc = string.match(data, ',2,1,200,"([A-Z][A-Z][A-Z])"')
if cc and cc ~= "" then
region = cc
end
status = 2
if region ~= "" then
if not datamatch(region, regex) then
status = 3
elseif old_regex ~= regex and not all_test then
status = 2
elseif old_region ~= "" and region ~= old_region and not all_test then
status = 4
end
if status == 2 and not all_test and region ~= old_region then
write_cache(type, "old_region", region)
end
if status == 2 and not all_test and regex ~= old_regex then
write_cache(type, "old_regex", regex)
end
end
else
status = 1
end
return region
end
function network_test()
local test_url1 = "https://www.gstatic.com/generate_204"
local test_url2 = "https://cp.cloudflare.com/generate_204"
@@ -16,7 +16,6 @@ del_lock() {
set_lock
inc_job_counter
restart=0
if [ -n "$1" ] && [ "$1" != "one_key_update" ]; then
/usr/share/openclash/openclash_version.sh "$1" 2>/dev/null
@@ -29,7 +28,7 @@ fi
if [ ! -f "/tmp/openclash_last_version" ]; then
LOG_ERROR "Failed to get version information, please try again later..."
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
dec_job_counter_and_restart "0"
del_lock
exit 0
fi
@@ -57,7 +56,7 @@ LAST_VER=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
if [ -x "/bin/opkg" ]; then
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
elif [ -x "/usr/bin/apk" ]; then
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
fi
OP_LV=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
@@ -69,14 +68,12 @@ if [ "$1" = "one_key_update" ]; then
LOG_TIP "If the download fails, try setting the CDN in Overwrite Settings - General Settings - Github Address Modify Options"
fi
if [ -n "$2" ]; then
/usr/share/openclash/openclash_core.sh "Meta" "$1" "$2" >/dev/null 2>&1 &
/usr/share/openclash/openclash_core.sh "Meta" "$1" "$2" >/dev/null 2>&1
github_address_mod="$2"
else
/usr/share/openclash/openclash_core.sh "Meta" "$1" >/dev/null 2>&1 &
/usr/share/openclash/openclash_core.sh "Meta" "$1" >/dev/null 2>&1
github_address_mod=0
fi
wait
else
if [ "$github_address_mod" = "0" ]; then
LOG_TIP "If the download fails, try setting the CDN in Overwrite Settings - General Settings - Github Address Modify Options"
@@ -175,9 +172,8 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
elif [ -x "/usr/bin/apk" ]; then
LOG_ERROR "【OpenClash - v$LAST_VER】Pre update test failed after 3 attempts, the file is saved in /tmp/openclash.apk, please try to update manually with【apk add -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk】"
fi
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
dec_job_counter_and_restart "0"
del_lock
exit 0
fi
@@ -191,8 +187,8 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
LOG_ERROR "【OpenClash - v$LAST_VER】Download Failed after 3 attempts, please check the network or try again later!"
rm -rf /tmp/openclash.ipk >/dev/null 2>&1
rm -rf /tmp/openclash.apk >/dev/null 2>&1
dec_job_counter_and_restart "0"
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
del_lock
exit 0
fi
@@ -200,9 +196,8 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
done
cat > /tmp/openclash_update.sh <<"EOF"
#!/bin/sh
START_LOG="/tmp/openclash_start.log"
LOG_FILE="/tmp/openclash.log"
LOGTIME=$(date "+%Y-%m-%d %H:%M:%S")
. /usr/share/openclash/log.sh
. /usr/share/openclash/openclash_ps.sh
UPDATE_LOCK="/tmp/lock/openclash_update_install.lock"
mkdir -p /tmp/lock
@@ -224,27 +219,6 @@ fi
trap 'del_update_lock; exit' INT TERM EXIT
LOG_ERROR()
{
if [ -n "${1}" ]; then
echo -e "${1}" > $START_LOG
echo -e "${LOGTIME} [Error] ${1}" >> $LOG_FILE
fi
}
LOG_TIP()
{
if [ -n "${1}" ]; then
echo -e "${1}" > $START_LOG
echo -e "${LOGTIME} [Tip] ${1}" >> $LOG_FILE
fi
}
SLOG_CLEAN()
{
echo "" > $START_LOG
}
check_install_success()
{
local target_version="$1"
@@ -253,7 +227,7 @@ check_install_success()
if [ -x "/bin/opkg" ]; then
current_version=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
elif [ -x "/usr/bin/apk" ]; then
current_version=$(apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
current_version=$(apk list luci-app-openclash 2>/dev/null |grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
fi
if [ -n "$current_version" ] && [ "$current_version" = "$target_version" ]; then
@@ -266,41 +240,39 @@ check_install_success()
install_missing_packages() {
local installed_before="$1"
if [ -x "/bin/opkg" ]; then
if [ -n "$installed_before" ]; then
for pkg in $installed_before; do
if ! opkg status "$pkg" >/dev/null 2>&1; then
local retry_count=0
local max_retries=3
while [ $retry_count -lt $max_retries ]; do
retry_count=$((retry_count + 1))
local retry_count=0
local max_retries=3
if [ -x "/bin/opkg" ]; then
if opkg status "$pkg" >/dev/null 2>&1; then
continue
fi
elif [ -x "/usr/bin/apk" ]; then
if apk list "$pkg" |grep "installed" >/dev/null 2>&1; then
continue
fi
fi
LOG_TIP "【$pkg】depended package reinstalling..."
while [ $retry_count -lt $max_retries ]; do
retry_count=$((retry_count + 1))
if [ -x "/bin/opkg" ]; then
opkg install "$pkg"
if [ $? -eq 0 ]; then
break
else
if [ $retry_count -lt $max_retries ]; then
sleep 2
fi
fi
done
fi
done
elif [ -x "/usr/bin/apk" ]; then
for pkg in $installed_before; do
if ! apk info "$pkg" >/dev/null 2>&1; then
local retry_count=0
local max_retries=3
while [ $retry_count -lt $max_retries ]; do
retry_count=$((retry_count + 1))
elif [ -x "/usr/bin/apk" ]; then
apk add "$pkg"
if [ $? -eq 0 ]; then
break
fi
if [ $? -eq 0 ]; then
break
else
if [ $retry_count -lt $max_retries ]; then
sleep 2
else
if [ $retry_count -lt $max_retries ]; then
sleep 2
fi
LOG_ERROR "【$pkg】failed to install, please try to install it manually..."
fi
done
fi
fi
done
done
fi
}
@@ -324,7 +296,7 @@ while [ $install_retry_count -lt $max_install_retries ]; do
opkg install /tmp/openclash.ipk
elif [ -x "/usr/bin/apk" ]; then
for pkg in $packages_to_check; do
if apk info "$pkg" >/dev/null 2>&1; then
if apk list "$pkg" |grep "installed" >/dev/null 2>&1; then
installed_before="$installed_before $pkg"
fi
done
@@ -357,9 +329,9 @@ else
elif [ -x "/usr/bin/apk" ]; then
LOG_ERROR "OpenClash update failed after 3 attempts, the file is saved in /tmp/openclash.apk, please try to update manually with【apk add -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk】"
fi
SLOG_CLEAN
fi
dec_job_counter_and_restart "0"
SLOG_CLEAN
del_update_lock
EOF
chmod 4755 /tmp/openclash_update.sh
@@ -367,6 +339,8 @@ EOF
if [ ! -f "/tmp/openclash_update.sh" ] || [ ! -s "/tmp/openclash_update.sh" ] || [ ! -x "/tmp/openclash_update.sh" ]; then
LOG_ERROR "Failed to create update script!"
rm -rf /tmp/openclash_update.sh
dec_job_counter_and_restart "0"
SLOG_CLEAN
del_lock
exit 1
fi
@@ -405,8 +379,8 @@ else
else
LOG_TIP "OpenClash has not been updated, stop continuing!"
fi
dec_job_counter_and_restart "0"
SLOG_CLEAN
dec_job_counter_and_restart "$restart"
fi
del_lock
@@ -5,20 +5,10 @@
. /usr/share/openclash/uci.sh
LOG_FILE="/tmp/openclash.log"
CONFIG_FILE="/etc/openclash/$(uci_get_config "config_path" |awk -F '/' '{print $5}' 2>/dev/null)"
ipv6_enable=$(uci_get_config "ipv6_enable" || echo 0)
enable_redirect_dns=$(uci_get_config "enable_redirect_dns")
dns_port=$(uci_get_config "dns_port")
disable_masq_cache=$(uci_get_config "disable_masq_cache")
cfg_update_interval=$(uci_get_config "config_update_interval" || echo 60)
log_size=$(uci_get_config "log_size" || echo 1024)
router_self_proxy=$(uci_get_config "router_self_proxy" || echo 1)
stream_auto_select_interval=$(uci_get_config "stream_auto_select_interval" || echo 30)
skip_proxy_address=$(uci_get_config "skip_proxy_address" || echo 0)
CFG_UPDATE_INT=1
CFG_UPDATE_INT=0
SKIP_PROXY_ADDRESS=1
SKIP_PROXY_ADDRESS_INTERVAL=30
STREAM_AUTO_SELECT=1
STREAM_AUTO_SELECT=0
FIREWALL_RELOAD=0
MAX_FIREWALL_RELOAD=3
FW4=$(command -v fw4)
@@ -155,11 +145,20 @@ end" 2>/dev/null >> $LOG_FILE
while :;
do
CONFIG_FILE="/etc/openclash/$(uci_get_config "config_path" |awk -F '/' '{print $5}' 2>/dev/null)"
ipv6_enable=$(uci_get_config "ipv6_enable" || echo 0)
enable_redirect_dns=$(uci_get_config "enable_redirect_dns")
dns_port=$(uci_get_config "dns_port")
disable_masq_cache=$(uci_get_config "disable_masq_cache")
log_size=$(uci_get_config "log_size" || echo 1024)
router_self_proxy=$(uci_get_config "router_self_proxy" || echo 1)
skip_proxy_address=$(uci_get_config "skip_proxy_address" || echo 0)
cfg_update=$(uci_get_config "auto_update")
cfg_update_mode=$(uci_get_config "config_auto_update_mode")
cfg_update_interval_now=$(uci_get_config "config_update_interval" || echo 60)
cfg_update_interval=$(uci_get_config "config_update_interval" || echo 60)
stream_auto_select=$(uci_get_config "stream_auto_select" || echo 0)
stream_auto_select_interval_now=$(uci_get_config "stream_auto_select_interval" || echo 30)
stream_auto_select_interval=$(uci_get_config "stream_auto_select_interval" || echo 30)
stream_auto_select_netflix=$(uci_get_config "stream_auto_select_netflix" || echo 0)
stream_auto_select_disney=$(uci_get_config "stream_auto_select_disney" || echo 0)
stream_auto_select_hbo_max=$(uci_get_config "stream_auto_select_hbo_max" || echo 0)
@@ -172,6 +171,8 @@ do
stream_auto_select_bilibili=$(uci_get_config "stream_auto_select_bilibili" || echo 0)
stream_auto_select_google_not_cn=$(uci_get_config "stream_auto_select_google_not_cn" || echo 0)
stream_auto_select_openai=$(uci_get_config "stream_auto_select_openai" || echo 0)
stream_auto_select_claude=$(uci_get_config "stream_auto_select_claude" || echo 0)
stream_auto_select_gemini=$(uci_get_config "stream_auto_select_gemini" || echo 0)
upnp_lease_file=$(uci -q get upnpd.config.upnp_lease_file)
#wait for core start complete
@@ -362,66 +363,72 @@ fi
## 配置文件循环更新
if [ "$cfg_update" -eq 1 ] && [ "$cfg_update_mode" -eq 1 ]; then
[ "$cfg_update_interval" -ne "$cfg_update_interval_now" ] && CFG_UPDATE_INT=0 && cfg_update_interval="$cfg_update_interval_now"
if [ "$CFG_UPDATE_INT" -ne 0 ]; then
[ "$(expr "$CFG_UPDATE_INT" % "$cfg_update_interval_now")" -eq 0 ] && /usr/share/openclash/openclash.sh
[ "$(expr "$CFG_UPDATE_INT" % "$cfg_update_interval")" -eq 0 ] && /usr/share/openclash/openclash.sh
fi
CFG_UPDATE_INT=$(expr "$CFG_UPDATE_INT" + 1)
fi
##STREAMING_UNLOCK_CHECK
if [ "$stream_auto_select" -eq 1 ] && [ "$router_self_proxy" -eq 1 ]; then
[ "$stream_auto_select_interval" -ne "$stream_auto_select_interval_now" ] && STREAM_AUTO_SELECT=1 && stream_auto_select_interval="$stream_auto_select_interval_now"
if [ "$STREAM_AUTO_SELECT" -ne 0 ]; then
if [ "$(expr "$STREAM_AUTO_SELECT" % "$stream_auto_select_interval_now")" -eq 0 ] || [ "$STREAM_AUTO_SELECT" -eq 1 ]; then
if [ "$(expr "$STREAM_AUTO_SELECT" % "$stream_auto_select_interval")" -eq 0 ] || [ "$STREAM_AUTO_SELECT" -eq 1 ]; then
if [ "$stream_auto_select_netflix" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Netflix Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Netflix】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Netflix" >> $LOG_FILE
fi
if [ "$stream_auto_select_disney" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Disney Plus Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Disney Plus】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Disney Plus" >> $LOG_FILE
fi
if [ "$stream_auto_select_google_not_cn" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Google Not CN Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Google Not CN】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Google" >> $LOG_FILE
fi
if [ "$stream_auto_select_ytb" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For YouTube Premium Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【YouTube Premium】..."
/usr/share/openclash/openclash_streaming_unlock.lua "YouTube Premium" >> $LOG_FILE
fi
if [ "$stream_auto_select_prime_video" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Amazon Prime Video Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Amazon Prime Video】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Amazon Prime Video" >> $LOG_FILE
fi
if [ "$stream_auto_select_hbo_max" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For HBO Max Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【HBO Max】..."
/usr/share/openclash/openclash_streaming_unlock.lua "HBO Max" >> $LOG_FILE
fi
if [ "$stream_auto_select_tvb_anywhere" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For TVB Anywhere+ Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【TVB Anywhere+】..."
/usr/share/openclash/openclash_streaming_unlock.lua "TVB Anywhere+" >> $LOG_FILE
fi
if [ "$stream_auto_select_dazn" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For DAZN Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【DAZN】..."
/usr/share/openclash/openclash_streaming_unlock.lua "DAZN" >> $LOG_FILE
fi
if [ "$stream_auto_select_paramount_plus" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Paramount Plus Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Paramount Plus】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Paramount Plus" >> $LOG_FILE
fi
if [ "$stream_auto_select_discovery_plus" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Discovery Plus Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Discovery Plus】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Discovery Plus" >> $LOG_FILE
fi
if [ "$stream_auto_select_bilibili" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For Bilibili Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【Bilibili】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Bilibili" >> $LOG_FILE
fi
if [ "$stream_auto_select_openai" -eq 1 ]; then
LOG_TIP "Start Auto Select Proxy For OpenAI Unlock..."
LOG_TIP "Start Auto Select Unlock Proxy For【OpenAI】..."
/usr/share/openclash/openclash_streaming_unlock.lua "OpenAI" >> $LOG_FILE
fi
if [ "$stream_auto_select_claude" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Claude】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Claude" >> $LOG_FILE
fi
if [ "$stream_auto_select_gemini" -eq 1 ]; then
LOG_TIP "Start Auto Select Unlock Proxy For【Gemini】..."
/usr/share/openclash/openclash_streaming_unlock.lua "Gemini" >> $LOG_FILE
fi
fi
fi
STREAM_AUTO_SELECT=$(expr "$STREAM_AUTO_SELECT" + 1)
@@ -672,6 +672,7 @@ proxies: # socks5
grpc-opts:
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# ip-version: ipv4
# vless
@@ -762,6 +763,7 @@ proxies: # socks5
grpc-opts:
grpc-service-name: "grpc"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
@@ -790,6 +792,69 @@ proxies: # socks5
# v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "vless-xhttp"
type: vless
server: server
port: 443
uuid: uuid
udp: true
tls: true
network: xhttp
alpn:
- h2
# ech-opts: ...
# reality-opts: ...
# skip-cert-verify: false
# fingerprint: ...
# certificate: ...
# private-key: ...
servername: xxx.com
client-fingerprint: chrome
encryption: ""
xhttp-opts:
path: "/"
host: xxx.com
# mode: "stream-one" # Available: "stream-one", "stream-up" or "packet-up"
# headers:
# X-Forwarded-For: ""
# no-grpc-header: false
# x-padding-bytes: "100-1000"
# reuse-settings: # aka XMUX
# max-connections: "16-32"
# max-concurrency: "0"
# c-max-reuse-times: "0"
# h-max-request-times: "600-900"
# h-max-reusable-secs: "1800-3000"
# download-settings:
# ## xhttp part
# path: "/"
# host: xxx.com
# headers:
# X-Forwarded-For: ""
# no-grpc-header: false
# x-padding-bytes: "100-1000"
# reuse-settings: # aka XMUX
# max-connections: "16-32"
# max-concurrency: "0"
# c-max-reuse-times: "0"
# h-max-request-times: "600-900"
# h-max-reusable-secs: "1800-3000"
# ## proxy part
# server: server
# port: 443
# tls: true
# alpn:
# - h2
# ech-opts: ...
# reality-opts: ...
# skip-cert-verify: false
# fingerprint: ...
# certificate: ...
# private-key: ...
# servername: xxx.com
# client-fingerprint: chrome
# Trojan
- name: "trojan"
type: trojan
@@ -833,6 +898,7 @@ proxies: # socks5
grpc-opts:
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
- name: trojan-ws
server: server
@@ -1080,6 +1146,8 @@ proxies: # socks5
# multiplexing: MULTIPLEXING_LOW
# 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT,否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD
# handshake-mode: HANDSHAKE_STANDARD
# 一个 base64 字符串用于微调网络行为
# traffic-pattern: ""
# sudoku
- name: sudoku
@@ -1090,15 +1158,17 @@ proxies: # socks5
aead-method: chacha20-poly1305 # 可选:chacha20-poly1305、aes-128-gcm、none(不建议;且 enable-pure-downlink=false 时不可用)
padding-min: 2 # 最小填充率(0-100)
padding-max: 7 # 最大填充率(0-100,必须 >= padding-min)
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table
http-mask: true # 是否启用http掩码
# http-mask-mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll);stream/poll/auto 支持走 CDN/反代
# http-mask-tls: true # 可选:仅在 http-mask-mode 为 stream/poll/auto 时生效;true 强制 https;false 强制 http(不会根据端口自动推断)
# http-mask-host: "" # 可选:覆盖 Host/SNI(支持 example.com 或 example.com:443);仅在 http-mask-mode 为 stream/poll/auto 时生效
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload
# http-mask-multiplex: off # 可选:off(默认)、auto(复用底层 HTTP 连接,减少建链 RTT)、on(Sudoku mux 单隧道多目标;仅在 http-mask-mode=stream/poll/auto 生效)
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy、up_ascii_down_entropy、up_entropy_down_ascii
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合;只对 entropy 方向生效
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),非空时覆盖 custom-table
# 推荐:使用 httpmask 对象统一管理 HTTPMask 相关字段:
httpmask:
disable: false # true 禁用所有 HTTP 伪装/隧道
mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll)、ws(WebSocket 隧道)
# tls: true # 可选:按需开启 HTTPS/WSS
# host: "" # 可选:覆盖 Host/SNI(支持 example.com 或 example.com:443);仅在 mode 为 stream/poll/auto/ws 时生效
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
# multiplex: "off" # 可选字符串:off(默认)、auto(复用底层 HTTP 连接,减少建链 RTT)、on(Sudoku mux 单隧道多目标;仅在 mode=stream/poll/auto 生效;ws 强制 off)
enable-pure-downlink: false # 可选:false=带宽优化下行(更快,要求 aead-method != none);true=纯 Sudoku 下行
# anytls
@@ -1118,6 +1188,23 @@ proxies: # socks5
# - http/1.1
# skip-cert-verify: true
# trusttunnel
- name: trusttunnel
type: trusttunnel
server: 1.2.3.4
port: 443
username: username
password: password
# client-fingerprint: chrome
health-check: true
udp: true
# sni: "example.com"
# alpn:
# - h2
# skip-cert-verify: true
# quic: true # 默认为false
# congestion-controller: bbr
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
- name: "dns-out"
type: dns
@@ -1555,6 +1642,12 @@ listeners:
flow: xtls-rprx-vision
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
# xhttp-config: # 如果不为空则开启 xhttp 传输层
# path: "/"
# host: ""
# mode: auto # Available: "stream-one", "stream-up" or "packet-up"
# no-sse-header: false
# sc-stream-up-server-secs: "20-80"
# -------------------------
# vless encryption服务端配置:
# (原生外观 / 只 XOR 公钥 / 全随机数。1-RTT 每次下发随机 300 到 600 秒的 ticket 以便 0-RTT 复用 / 只允许 1-RTT)
@@ -1631,6 +1724,8 @@ listeners:
users:
username1: password1
username2: password2
# 一个 base64 字符串用于微调网络行为
# traffic-pattern: ""
- name: sudoku-in-1
type: sudoku
@@ -1640,14 +1735,18 @@ listeners:
aead-method: chacha20-poly1305 # 可选:chacha20-poly1305、aes-128-gcm、none(不建议;且 enable-pure-downlink=false 时不可用)
padding-min: 1 # 最小填充率(0-100)
padding-max: 15 # 最大填充率(0-100,必须 >= padding-min)
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy`
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table
table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy、up_ascii_down_entropy、up_entropy_down_ascii
# custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合;只对 entropy 方向生效
# custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于多表轮换;非空时覆盖 custom-table
handshake-timeout: 5 # 可选(秒)
enable-pure-downlink: false # 可选:false=带宽优化下行(更快,要求 aead-method != none);true=纯 Sudoku 下行
disable-http-mask: false # 可选:禁用 http 掩码/隧道(默认为 false)
# http-mask-mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll);stream/poll/auto 支持走 CDN/反代
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload
# 推荐:使用 httpmask 对象统一管理 HTTPMask 相关字段:
httpmask:
disable: false # true 禁用所有 HTTP 伪装/隧道
mode: legacy # 可选:legacy(默认)、stream(split-stream)、poll、auto(先 stream 再 poll)、ws(WebSocket 隧道)
# path-root: "" # 可选:HTTP 隧道端点一级路径前缀(双方需一致),例如 "aabbcc" 或 "/aabbcc/" => /aabbcc/session、/aabbcc/stream、/aabbcc/api/v1/upload、/aabbcc/ws
#
# fallback: "127.0.0.1:80" # 可选:用于可连接请求的回落转发,可与其他服务共端口
@@ -1734,6 +1833,30 @@ listeners:
# masquerade: http://127.0.0.1:8080 #作为反向代理
# masquerade: https://127.0.0.1:8080 #作为反向代理
- name: trusttunnel-in-1
type: trusttunnel
port: 10821 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
users:
- username: 1
password: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
private-key: ./server.key # 证书对应的私钥 PEM 格式,或者私钥路径
network: ["tcp", "udp"] # http2+http3
congestion-controller: bbr
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
# client-auth-type: "" # 可选值:""、"request"、"require-any"、"verify-if-given"、"require-and-verify"
# client-auth-cert: string # 证书 PEM 格式,或者 证书的路径
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
# 注意,listeners中的tun仅提供给高级用户使用,普通用户应使用顶层配置中的tun
- name: tun-in-1
type: tun
@@ -41,7 +41,7 @@ if [ "$1" = "fake-ip" ] && [ "$enable_redirect_dns" != "2" ]; then
awk -v mode="$fake_ip_filter_mode" '
!/^$/ && !/^#/ {
# 跳过IPv4和IPv6地址
if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ || $0 ~ /:/) {
if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(\/[0-9]+)?$/ || $0 ~ /:/) {
next
}
if (mode == "blacklist") {
@@ -185,7 +185,7 @@ set_disable_qtype()
yml_dns_get()
{
local section="$1" regex='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$'
local enabled port type ip group dns_type dns_address interface specific_group node_resolve http3 ecs_subnet ecs_override disable_qtype_param
local enabled port type ip group dns_type dns_address interface specific_group node_resolve direct_nameserver http3 skip_cert_verify ecs_subnet ecs_override disable_qtype_param disable_qtype disable_ipv4 disable_ipv6 disable_reuse
config_get_bool "enabled" "$section" "enabled" "1"
[ "$enabled" = "0" ] && return
@@ -378,6 +378,8 @@ begin
smart_collect = '${44}' == '1'
smart_collect_size = '${45}'
fake_ip_range6 = '${46}'
fake_ip_range6_enable = '${47}' == '1'
global_ua = '${48}'
default_dashboard = '$default_dashboard'
yacd_type = '$yacd_type'
dashboard_type = '$dashboard_type'
@@ -408,6 +410,7 @@ begin
Value['external-controller'] = '0.0.0.0:' + controller_port
Value['secret'] = secret
Value['bind-address'] = '*'
Value['global-ua'] = global_ua if global_ua != '0'
Value['external-ui'] = '/usr/share/openclash/ui'
Value['external-ui-name'] = default_dashboard
case default_dashboard
@@ -480,7 +483,7 @@ begin
else
Value['dns']['enhanced-mode'] = 'fake-ip'
Value['dns']['fake-ip-range'] = fake_ip_range
if Value['dns']['ipv6']
if Value['dns']['ipv6'] and fake_ip_range6_enable
Value['dns']['fake-ip-range6'] = fake_ip_range6
end
end
@@ -657,14 +660,13 @@ begin
end
if fake_ip_mode == 'fake-ip' && (china_ip_route || china_ip6_route)
filter_mode = Value.dig('dns', 'fake-ip-filter-mode')
filters = Value.dig('dns', 'fake-ip-filter') || []
deleted_filters = filters.select { |f| f =~ /(geosite:?|rule-set:?).*(@cn|:cn|,cn|:china)/i }
if filter_mode == 'blacklist' || filter_mode.nil?
if !deleted_filters.any?
(Value['dns']['fake-ip-filter'] ||= []) << 'geosite:cn'
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【geosite:cn】...')
end
else
filter_rule = 'rule-set:oc-cn-domain'
(Value['dns']['fake-ip-filter'] ||= []) << filter_rule
end
if filter_mode == 'whitelist'
filters = Value.dig('dns', 'fake-ip-filter') || []
deleted_filters = filters.select { |f| f =~ /(geosite:?|rule-set:?).*(@cn|:cn|,cn|:china)/i }
if deleted_filters.any?
Value['dns']['fake-ip-filter'] -= deleted_filters
deleted_filters.each do |f|
@@ -673,8 +675,14 @@ begin
end
end
if filter_mode == 'rule'
(Value['dns']['fake-ip-filter'] ||= []).unshift('GEOSITE,cn,real-ip')
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【GEOSITE,cn,real-ip】...')
filter_rule = 'RULE-SET,oc-cn-domain,real-ip'
(Value['dns']['fake-ip-filter'] ||= []).unshift(filter_rule)
end
Value['dns']['fake-ip-filter'].uniq!
if filter_mode != 'whitelist'
rule_set_hash = {'rule-providers+'=>{'oc-cn-domain'=>{'type'=>'http', 'interval'=>43200, 'behavior'=>'domain', 'format'=>'mrs', 'url'=>'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/cn.mrs', 'path'=> './rule_provider/oc-cn-domain.mrs'}}}
Value = YAML.overwrite(Value, rule_set_hash)
YAML.LOG_TIP('Because Need Ensure Bypassing IP Option Work, Added The Fake-IP-Filter Rule【%s】...' % [filter_rule])
end
end
rescue Exception => e
@@ -592,7 +592,7 @@ ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
};
end;
#Mieru
#Mieru
if x['type'] == 'mieru' then
threads << Thread.new{
#port-range
@@ -1138,6 +1138,19 @@ ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
uci_commands << uci_set + 'reality_short_id=\"' + x['reality-opts']['short-id'].to_s + '\"'
end
end
if x.key?('encryption') then
uci_commands << uci_set + 'vless_encryption=\"' + x['encryption'].to_s + '\"'
end
elsif x['network'].to_s == 'xhttp'
uci_commands << uci_set + 'obfs_vless=xhttp'
if x.key?('xhttp-opts') then
if x['xhttp-opts'].key?('path') then
uci_commands << uci_set + 'xhttp_opts_path=\"' + x['xhttp-opts']['path'].to_s + '\"'
end
if x['xhttp-opts'].key?('host') then
uci_commands << uci_set + 'xhttp_opts_host=\"' + x['xhttp-opts']['host'].to_s + '\"'
end
end
end
end
};
@@ -1424,6 +1437,132 @@ ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "
};
end;
if x['type'] == 'masque' then
threads << Thread.new{
#private-key
if x.key?('private-key') then
uci_commands << uci_set + 'masque_private_key=\"' + x['private-key'].to_s + '\"'
end
};
threads << Thread.new{
#public-key
if x.key?('public-key') then
uci_commands << uci_set + 'masque_public_key=\"' + x['public-key'].to_s + '\"'
end
};
threads << Thread.new{
#ip
if x.key?('ip') then
uci_commands << uci_set + 'masque_ip=\"' + x['ip'].to_s + '\"'
end
};
threads << Thread.new{
#ipv6
if x.key?('ipv6') then
uci_commands << uci_set + 'masque_ipv6=\"' + x['ipv6'].to_s + '\"'
end
};
threads << Thread.new{
#mtu
if x.key?('mtu') then
uci_commands << uci_set + 'masque_mtu=\"' + x['mtu'].to_s + '\"'
end
};
threads << Thread.new{
#remote-dns-resolve
if x.key?('remote-dns-resolve') then
uci_commands << uci_set + 'masque_remote_dns_resolve=\"' + x['remote-dns-resolve'].to_s + '\"'
end
};
threads << Thread.new{
#dns
if x.key?('dns') then
dns = uci_del + 'dns >/dev/null 2>&1'
system(dns)
x['dns'].each{
|x|
uci_commands << uci_add + 'masque_dns=\"' + x.to_s + '\"'
}
end
};
end;
if x['type'] == 'trusttunnel' then
threads << Thread.new{
#username
if x.key?('username') then
uci_commands << uci_set + 'trusttunnel_username=\"' + x['username'].to_s + '\"'
end
};
threads << Thread.new{
#password
if x.key?('password') then
uci_commands << uci_set + 'trusttunnel_password=\"' + x['password'].to_s + '\"'
end
};
threads << Thread.new{
#health-check
if x.key?('health-check') then
uci_commands << uci_set + 'trusttunnel_health_check=\"' + x['health-check'].to_s + '\"'
end
};
threads << Thread.new{
#quic
if x.key?('quic') then
uci_commands << uci_set + 'trusttunnel_quic=\"' + x['quic'].to_s + '\"'
end
};
threads << Thread.new{
#congestion-controller
if x.key?('congestion-controller') then
uci_commands << uci_set + 'trusttunnel_congestion_controller=\"' + x['congestion-controller'].to_s + '\"'
end
};
#alpn
threads << Thread.new{
if x.key?('alpn') then
alpn = uci_del + 'alpn >/dev/null 2>&1'
system(alpn)
x['alpn'].each{
|x|
uci_commands << uci_add + 'alpn=\"' + x.to_s + '\"'
}
end
};
#sni
threads << Thread.new{
if x.key?('sni') then
uci_commands << uci_set + 'sni=\"' + x['sni'].to_s + '\"'
end
};
#skip-cert-verify
threads << Thread.new{
if x.key?('skip-cert-verify') then
uci_commands << uci_set + 'skip_cert_verify=\"' + x['skip-cert-verify'].to_s + '\"'
end
};
#client_fingerprint
threads << Thread.new{
if x.key?('client-fingerprint') then
uci_commands << uci_set + 'client_fingerprint=\"' + x['client-fingerprint'].to_s + '\"'
end
};
end;
#加入策略组
threads << Thread.new{
#加入策略组
@@ -186,135 +186,6 @@ yml_servers_set()
config_get "name" "$section" "name" ""
config_get "server" "$section" "server" ""
config_get "port" "$section" "port" ""
config_get "dialer_proxy" "$section" "dialer_proxy" ""
config_get "cipher" "$section" "cipher" ""
config_get "cipher_ssr" "$section" "cipher_ssr" ""
config_get "password" "$section" "password" ""
config_get "securitys" "$section" "securitys" ""
config_get "udp" "$section" "udp" ""
config_get "obfs" "$section" "obfs" ""
config_get "obfs_ssr" "$section" "obfs_ssr" ""
config_get "obfs_param" "$section" "obfs_param" ""
config_get "obfs_vmess" "$section" "obfs_vmess" ""
config_get "obfs_trojan" "$section" "obfs_trojan" ""
config_get "protocol" "$section" "protocol" ""
config_get "protocol_param" "$section" "protocol_param" ""
config_get "host" "$section" "host" ""
config_get "mux" "$section" "mux" ""
config_get "custom" "$section" "custom" ""
config_get "tls" "$section" "tls" ""
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
config_get "path" "$section" "path" ""
config_get "alterId" "$section" "alterId" ""
config_get "uuid" "$section" "uuid" ""
config_get "auth_name" "$section" "auth_name" ""
config_get "auth_pass" "$section" "auth_pass" ""
config_get "psk" "$section" "psk" ""
config_get "obfs_snell" "$section" "obfs_snell" ""
config_get "snell_version" "$section" "snell_version" ""
config_get "sni" "$section" "sni" ""
config_get "alpn" "$section" "alpn" ""
config_get "http_path" "$section" "http_path" ""
config_get "keep_alive" "$section" "keep_alive" ""
config_get "servername" "$section" "servername" ""
config_get "h2_path" "$section" "h2_path" ""
config_get "h2_host" "$section" "h2_host" ""
config_get "grpc_service_name" "$section" "grpc_service_name" ""
config_get "ws_opts_path" "$section" "ws_opts_path" ""
config_get "ws_opts_headers" "$section" "ws_opts_headers" ""
config_get "max_early_data" "$section" "max_early_data" ""
config_get "early_data_header_name" "$section" "early_data_header_name" ""
config_get "trojan_ws_path" "$section" "trojan_ws_path" ""
config_get "trojan_ws_headers" "$section" "trojan_ws_headers" ""
config_get "interface_name" "$section" "interface_name" ""
config_get "routing_mark" "$section" "routing_mark" ""
config_get "obfs_vless" "$section" "obfs_vless" ""
config_get "vless_flow" "$section" "vless_flow" ""
config_get "http_headers" "$section" "http_headers" ""
config_get "hysteria_protocol" "$section" "hysteria_protocol" ""
config_get "hysteria2_protocol" "$section" "hysteria2_protocol" ""
config_get "hysteria_up" "$section" "hysteria_up" ""
config_get "hysteria_down" "$section" "hysteria_down" ""
config_get "hysteria_alpn" "$section" "hysteria_alpn" ""
config_get "hysteria_obfs" "$section" "hysteria_obfs" ""
config_get "hysteria_auth" "$section" "hysteria_auth" ""
config_get "hysteria_auth_str" "$section" "hysteria_auth_str" ""
config_get "hysteria_ca" "$section" "hysteria_ca" ""
config_get "hysteria_ca_str" "$section" "hysteria_ca_str" ""
config_get "recv_window_conn" "$section" "recv_window_conn" ""
config_get "recv_window" "$section" "recv_window" ""
config_get "disable_mtu_discovery" "$section" "disable_mtu_discovery" ""
config_get "initial_stream_receive_window" "$section" "initial_stream_receive_window" ""
config_get "max_stream_receive_window" "$section" "max_stream_receive_window" ""
config_get "initial_connection_receive_window" "$section" "initial_connection_receive_window" ""
config_get "max_connection_receive_window" "$section" "max_connection_receive_window" ""
config_get "xudp" "$section" "xudp" ""
config_get "packet_encoding" "$section" "packet_encoding" ""
config_get "global_padding" "$section" "global_padding" ""
config_get "authenticated_length" "$section" "authenticated_length" ""
config_get "wg_ip" "$section" "wg_ip" ""
config_get "wg_ipv6" "$section" "wg_ipv6" ""
config_get "private_key" "$section" "private_key" ""
config_get "public_key" "$section" "public_key" ""
config_get "preshared_key" "$section" "preshared_key" ""
config_get "wg_dns" "$section" "wg_dns" ""
config_get "public_key" "$section" "public_key" ""
config_get "preshared_key" "$section" "preshared_key" ""
config_get "wg_mtu" "$section" "wg_mtu" ""
config_get "tc_ip" "$section" "tc_ip" ""
config_get "tc_token" "$section" "tc_token" ""
config_get "tc_uuid" "$section" "tc_uuid" ""
config_get "tc_password" "$section" "tc_password" ""
config_get "udp_relay_mode" "$section" "udp_relay_mode" ""
config_get "congestion_controller" "$section" "congestion_controller" ""
config_get "tc_alpn" "$section" "tc_alpn" ""
config_get "disable_sni" "$section" "disable_sni" ""
config_get "reduce_rtt" "$section" "reduce_rtt" ""
config_get "heartbeat_interval" "$section" "heartbeat_interval" ""
config_get "request_timeout" "$section" "request_timeout" ""
config_get "max_udp_relay_packet_size" "$section" "max_udp_relay_packet_size" ""
config_get "fast_open" "$section" "fast_open" ""
config_get "fingerprint" "$section" "fingerprint" ""
config_get "ports" "$section" "ports" ""
config_get "hop_interval" "$section" "hop_interval" ""
config_get "max_open_streams" "$section" "max_open_streams" ""
config_get "obfs_password" "$section" "obfs_password" ""
config_get "packet_addr" "$section" "packet_addr" ""
config_get "client_fingerprint" "$section" "client_fingerprint" ""
config_get "ip_version" "$section" "ip_version" ""
config_get "tfo" "$section" "tfo" ""
config_get "udp_over_tcp" "$section" "udp_over_tcp" ""
config_get "reality_public_key" "$section" "reality_public_key" ""
config_get "reality_short_id" "$section" "reality_short_id" ""
config_get "obfs_version_hint" "$section" "obfs_version_hint" ""
config_get "obfs_restls_script" "$section" "obfs_restls_script" ""
config_get "multiplex" "$section" "multiplex" ""
config_get "multiplex_protocol" "$section" "multiplex_protocol" ""
config_get "multiplex_max_connections" "$section" "multiplex_max_connections" ""
config_get "multiplex_min_streams" "$section" "multiplex_min_streams" ""
config_get "multiplex_max_streams" "$section" "multiplex_max_streams" ""
config_get "multiplex_padding" "$section" "multiplex_padding" ""
config_get "multiplex_statistic" "$section" "multiplex_statistic" ""
config_get "multiplex_only_tcp" "$section" "multiplex_only_tcp" ""
config_get "other_parameters" "$section" "other_parameters" ""
config_get "hysteria_obfs_password" "$section" "hysteria_obfs_password" ""
config_get "port_range" "$section" "port_range" ""
config_get "username" "$section" "username" ""
config_get "transport" "$section" "transport" "TCP"
config_get "multiplexing" "$section" "multiplexing" "MULTIPLEXING_LOW"
config_get "private_key" "$section" "private_key" ""
config_get "private_key_passphrase" "$section" "private_key_passphrase" ""
config_get "host_key" "$section" "host_key" ""
config_get "host_key_algorithms" "$section" "host_key_algorithms" ""
config_get "idle_session_check_interval" "$section" "idle_session_check_interval" ""
config_get "idle_session_timeout" "$section" "idle_session_timeout" ""
config_get "min_idle_session" "$section" "min_idle_session" ""
config_get "sudoku_key" "$section" "sudoku_key" ""
config_get "aead_method" "$section" "aead_method" "none"
config_get "padding_min" "$section" "padding_min" ""
config_get "padding_max" "$section" "padding_max" ""
config_get "table_type" "$section" "table_type" "prefer_ascii"
config_get "http_mask" "$section" "http_mask" "true"
if [ "$enabled" = "0" ]; then
return
@@ -336,11 +207,12 @@ yml_servers_set()
return
fi
if [ -z "$password" ]; then
if [ "$type" = "ss" ] || [ "$type" = "trojan" ] || [ "$type" = "ssr" ]; then
return
fi
fi
if [ "$type" = "ss" ] || [ "$type" = "trojan" ] || [ "$type" = "ssr" ]; then
config_get "password" "$section" "password" ""
if [ -z "$password" ]; then
return
fi
fi
if [ ! -z "$config" ] && [ "$config" != "$CONFIG_NAME" ] && [ "$config" != "all" ]; then
return
@@ -364,59 +236,27 @@ yml_servers_set()
fi
LOG_OUT "Start Writing【$CONFIG_NAME - $type - $name】Proxy To Config File..."
if [ "$obfs" != "none" ] && [ -n "$obfs" ]; then
if [ "$obfs" = "websocket" ]; then
obfss="plugin: v2ray-plugin"
elif [ "$obfs" = "shadow-tls" ]; then
obfss="plugin: shadow-tls"
elif [ "$obfs" = "restls" ]; then
obfss="plugin: restls"
else
obfss="plugin: obfs"
fi
else
obfss=""
fi
if [ "$obfs_vless" = "ws" ]; then
obfs_vless="network: ws"
fi
if [ "$obfs_vless" = "grpc" ]; then
obfs_vless="network: grpc"
fi
if [ "$obfs_vless" = "tcp" ]; then
obfs_vless="network: tcp"
fi
if [ "$obfs_vmess" = "websocket" ]; then
obfs_vmess="network: ws"
fi
if [ "$obfs_vmess" = "http" ]; then
obfs_vmess="network: http"
fi
if [ "$obfs_vmess" = "h2" ]; then
obfs_vmess="network: h2"
fi
if [ "$obfs_vmess" = "grpc" ]; then
obfs_vmess="network: grpc"
fi
if [ ! -z "$custom" ] && [ "$type" = "vmess" ]; then
custom="Host: \"$custom\""
fi
if [ ! -z "$path" ]; then
if [ "$type" != "vmess" ]; then
path="path: \"$path\""
elif [ "$obfs_vmess" = "network: ws" ]; then
path="ws-path: \"$path\""
fi
fi
config_get "dialer_proxy" "$section" "dialer_proxy" ""
config_get "udp" "$section" "udp" ""
config_get "skip_cert_verify" "$section" "skip_cert_verify" ""
config_get "tls" "$section" "tls" ""
config_get "sni" "$section" "sni" ""
config_get "alpn" "$section" "alpn" ""
config_get "fingerprint" "$section" "fingerprint" ""
config_get "client_fingerprint" "$section" "client_fingerprint" ""
config_get "ip_version" "$section" "ip_version" ""
config_get "tfo" "$section" "tfo" ""
config_get "multiplex" "$section" "multiplex" ""
config_get "multiplex_protocol" "$section" "multiplex_protocol" ""
config_get "multiplex_max_connections" "$section" "multiplex_max_connections" ""
config_get "multiplex_min_streams" "$section" "multiplex_min_streams" ""
config_get "multiplex_max_streams" "$section" "multiplex_max_streams" ""
config_get "multiplex_padding" "$section" "multiplex_padding" ""
config_get "multiplex_statistic" "$section" "multiplex_statistic" ""
config_get "multiplex_only_tcp" "$section" "multiplex_only_tcp" ""
config_get "interface_name" "$section" "interface_name" ""
config_get "routing_mark" "$section" "routing_mark" ""
config_get "other_parameters" "$section" "other_parameters" ""
if [ "$client_fingerprint" = "none" ]; then
client_fingerprint=""
@@ -428,6 +268,35 @@ yml_servers_set()
#ss
if [ "$type" = "ss" ]; then
config_get "cipher" "$section" "cipher" ""
config_get "obfs" "$section" "obfs" ""
config_get "host" "$section" "host" ""
config_get "mux" "$section" "mux" ""
config_get "custom" "$section" "custom" ""
config_get "path" "$section" "path" ""
config_get "obfs_password" "$section" "obfs_password" ""
config_get "obfs_version_hint" "$section" "obfs_version_hint" ""
config_get "obfs_restls_script" "$section" "obfs_restls_script" ""
config_get "udp_over_tcp" "$section" "udp_over_tcp" ""
if [ "$obfs" != "none" ] && [ -n "$obfs" ]; then
if [ "$obfs" = "websocket" ]; then
obfss="plugin: v2ray-plugin"
elif [ "$obfs" = "shadow-tls" ]; then
obfss="plugin: shadow-tls"
elif [ "$obfs" = "restls" ]; then
obfss="plugin: restls"
else
obfss="plugin: obfs"
fi
else
obfss=""
fi
if [ ! -z "$path" ]; then
path="path: \"$path\""
fi
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -528,6 +397,12 @@ fi
#ssr
if [ "$type" = "ssr" ]; then
config_get "cipher_ssr" "$section" "cipher_ssr" ""
config_get "obfs_ssr" "$section" "obfs_ssr" ""
config_get "protocol" "$section" "protocol" ""
config_get "obfs_param" "$section" "obfs_param" ""
config_get "protocol_param" "$section" "protocol_param" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -557,6 +432,48 @@ fi
#vmess
if [ "$type" = "vmess" ]; then
config_get "uuid" "$section" "uuid" ""
config_get "alterId" "$section" "alterId" ""
config_get "securitys" "$section" "securitys" ""
config_get "xudp" "$section" "xudp" ""
config_get "packet_encoding" "$section" "packet_encoding" ""
config_get "global_padding" "$section" "global_padding" ""
config_get "authenticated_length" "$section" "authenticated_length" ""
config_get "servername" "$section" "servername" ""
config_get "obfs_vmess" "$section" "obfs_vmess" ""
config_get "custom" "$section" "custom" ""
config_get "path" "$section" "path" ""
config_get "ws_opts_path" "$section" "ws_opts_path" ""
config_get "ws_opts_headers" "$section" "ws_opts_headers" ""
config_get "max_early_data" "$section" "max_early_data" ""
config_get "early_data_header_name" "$section" "early_data_header_name" ""
config_get "http_path" "$section" "http_path" ""
config_get "keep_alive" "$section" "keep_alive" ""
config_get "h2_path" "$section" "h2_path" ""
config_get "h2_host" "$section" "h2_host" ""
config_get "grpc_service_name" "$section" "grpc_service_name" ""
if [ "$obfs_vmess" = "websocket" ]; then
obfs_vmess="network: ws"
fi
if [ "$obfs_vmess" = "http" ]; then
obfs_vmess="network: http"
fi
if [ "$obfs_vmess" = "h2" ]; then
obfs_vmess="network: h2"
fi
if [ "$obfs_vmess" = "grpc" ]; then
obfs_vmess="network: grpc"
fi
if [ ! -z "$custom" ]; then
custom="Host: \"$custom\""
fi
if [ ! -z "$path" ] && [ "$obfs_vmess" = "network: ws" ]; then
path="ws-path: \"$path\""
fi
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -702,6 +619,11 @@ fi
#anytls
if [ "$type" = "anytls" ]; then
config_get "password" "$section" "password" ""
config_get "idle_session_check_interval" "$section" "idle_session_check_interval" ""
config_get "idle_session_timeout" "$section" "idle_session_timeout" ""
config_get "min_idle_session" "$section" "min_idle_session" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -758,6 +680,11 @@ fi
#Mieru
if [ "$type" = "mieru" ]; then
config_get "port_range" "$section" "port_range" ""
config_get "username" "$section" "username" ""
config_get "transport" "$section" "transport" "TCP"
config_get "multiplexing" "$section" "multiplexing" "MULTIPLEXING_LOW"
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -788,6 +715,21 @@ fi
#Tuic
if [ "$type" = "tuic" ]; then
config_get "tc_ip" "$section" "tc_ip" ""
config_get "tc_token" "$section" "tc_token" ""
config_get "tc_uuid" "$section" "tc_uuid" ""
config_get "tc_password" "$section" "tc_password" ""
config_get "udp_relay_mode" "$section" "udp_relay_mode" ""
config_get "congestion_controller" "$section" "congestion_controller" ""
config_get "tc_alpn" "$section" "tc_alpn" ""
config_get "disable_sni" "$section" "disable_sni" ""
config_get "reduce_rtt" "$section" "reduce_rtt" ""
config_get "fast_open" "$section" "fast_open" ""
config_get "heartbeat_interval" "$section" "heartbeat_interval" ""
config_get "request_timeout" "$section" "request_timeout" ""
config_get "max_udp_relay_packet_size" "$section" "max_udp_relay_packet_size" ""
config_get "max_open_streams" "$section" "max_open_streams" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -874,6 +816,14 @@ fi
#WireGuard
if [ "$type" = "wireguard" ]; then
config_get "wg_ip" "$section" "wg_ip" ""
config_get "wg_ipv6" "$section" "wg_ipv6" ""
config_get "private_key" "$section" "private_key" ""
config_get "public_key" "$section" "public_key" ""
config_get "preshared_key" "$section" "preshared_key" ""
config_get "wg_dns" "$section" "wg_dns" ""
config_get "wg_mtu" "$section" "wg_mtu" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -905,7 +855,7 @@ cat >> "$SERVER_FILE" <<-EOF
preshared-key: "$preshared_key"
EOF
fi
if [ -n "$preshared_key" ]; then
if [ -n "$wg_dns" ]; then
cat >> "$SERVER_FILE" <<-EOF
dns:
EOF
@@ -925,6 +875,22 @@ fi
#hysteria
if [ "$type" = "hysteria" ]; then
config_get "hysteria_protocol" "$section" "hysteria_protocol" ""
config_get "hysteria_up" "$section" "hysteria_up" ""
config_get "hysteria_down" "$section" "hysteria_down" ""
config_get "hysteria_alpn" "$section" "hysteria_alpn" ""
config_get "hysteria_obfs" "$section" "hysteria_obfs" ""
config_get "hysteria_auth" "$section" "hysteria_auth" ""
config_get "hysteria_auth_str" "$section" "hysteria_auth_str" ""
config_get "hysteria_ca" "$section" "hysteria_ca" ""
config_get "hysteria_ca_str" "$section" "hysteria_ca_str" ""
config_get "recv_window_conn" "$section" "recv_window_conn" ""
config_get "recv_window" "$section" "recv_window" ""
config_get "disable_mtu_discovery" "$section" "disable_mtu_discovery" ""
config_get "fast_open" "$section" "fast_open" ""
config_get "ports" "$section" "ports" ""
config_get "hop_interval" "$section" "hop_interval" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1029,6 +995,22 @@ fi
#hysteria2
if [ "$type" = "hysteria2" ]; then
config_get "password" "$section" "password" ""
config_get "hysteria_up" "$section" "hysteria_up" ""
config_get "hysteria_down" "$section" "hysteria_down" ""
config_get "hysteria_alpn" "$section" "hysteria_alpn" ""
config_get "hysteria_obfs" "$section" "hysteria_obfs" ""
config_get "hysteria_obfs_password" "$section" "hysteria_obfs_password" ""
config_get "hysteria_ca" "$section" "hysteria_ca" ""
config_get "hysteria_ca_str" "$section" "hysteria_ca_str" ""
config_get "initial_stream_receive_window" "$section" "initial_stream_receive_window" ""
config_get "max_stream_receive_window" "$section" "max_stream_receive_window" ""
config_get "initial_connection_receive_window" "$section" "initial_connection_receive_window" ""
config_get "max_connection_receive_window" "$section" "max_connection_receive_window" ""
config_get "ports" "$section" "ports" ""
config_get "hysteria2_protocol" "$section" "hysteria2_protocol" ""
config_get "hop_interval" "$section" "hop_interval" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1096,7 +1078,7 @@ EOF
fi
if [ -n "$max_stream_receive_window" ]; then
cat >> "$SERVER_FILE" <<-EOF
max_stream_receive_window: "$max_stream_receive_window"
max-stream-receive-window: "$max_stream_receive_window"
EOF
fi
if [ -n "$initial_connection_receive_window" ]; then
@@ -1133,6 +1115,35 @@ fi
#vless
if [ "$type" = "vless" ]; then
config_get "uuid" "$section" "uuid" ""
config_get "xudp" "$section" "xudp" ""
config_get "packet_addr" "$section" "packet_addr" ""
config_get "packet_encoding" "$section" "packet_encoding" ""
config_get "servername" "$section" "servername" ""
config_get "obfs_vless" "$section" "obfs_vless" ""
config_get "ws_opts_path" "$section" "ws_opts_path" ""
config_get "ws_opts_headers" "$section" "ws_opts_headers" ""
config_get "grpc_service_name" "$section" "grpc_service_name" ""
config_get "reality_public_key" "$section" "reality_public_key" ""
config_get "reality_short_id" "$section" "reality_short_id" ""
config_get "vless_flow" "$section" "vless_flow" ""
config_get "xhttp_opts_path" "$section" "xhttp_opts_path" ""
config_get "xhttp_opts_host" "$section" "xhttp_opts_host" ""
config_get "vless_encryption" "$section" "vless_encryption" ""
if [ "$obfs_vless" = "ws" ]; then
obfs_vless="network: ws"
fi
if [ "$obfs_vless" = "grpc" ]; then
obfs_vless="network: grpc"
fi
if [ "$obfs_vless" = "tcp" ]; then
obfs_vless="network: tcp"
fi
if [ "$obfs_vless" = "xhttp" ]; then
obfs_vless="network: xhttp"
fi
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1232,6 +1243,11 @@ EOF
if [ ! -z "$vless_flow" ]; then
cat >> "$SERVER_FILE" <<-EOF
flow: "$vless_flow"
EOF
fi
if [ -n "$vless_encryption" ]; then
cat >> "$SERVER_FILE" <<-EOF
encryption: "$vless_encryption"
EOF
fi
if [ -n "$reality_public_key" ] || [ -n "$reality_short_id" ]; then
@@ -1247,6 +1263,21 @@ EOF
if [ -n "$reality_short_id" ]; then
cat >> "$SERVER_FILE" <<-EOF
short-id: "$reality_short_id"
EOF
fi
fi
if [ "$obfs_vless" = "network: xhttp" ]; then
cat >> "$SERVER_FILE" <<-EOF
xhttp-opts:
EOF
if [ -n "$xhttp_opts_path" ]; then
cat >> "$SERVER_FILE" <<-EOF
path: "$xhttp_opts_path"
EOF
fi
if [ -n "$xhttp_opts_host" ]; then
cat >> "$SERVER_FILE" <<-EOF
host: "$xhttp_opts_host"
EOF
fi
fi
@@ -1276,6 +1307,13 @@ fi
#ssh
if [ "$type" = "ssh" ]; then
config_get "auth_name" "$section" "auth_name" ""
config_get "auth_pass" "$section" "auth_pass" ""
config_get "private_key" "$section" "private_key" ""
config_get "private_key_passphrase" "$section" "private_key_passphrase" ""
config_get "host_key" "$section" "host_key" ""
config_get "host_key_algorithms" "$section" "host_key_algorithms" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1318,6 +1356,9 @@ fi
#socks5
if [ "$type" = "socks5" ]; then
config_get "auth_name" "$section" "auth_name" ""
config_get "auth_pass" "$section" "auth_pass" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1358,6 +1399,10 @@ fi
#http
if [ "$type" = "http" ]; then
config_get "auth_name" "$section" "auth_name" ""
config_get "auth_pass" "$section" "auth_pass" ""
config_get "http_headers" "$section" "http_headers" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1399,6 +1444,11 @@ fi
#trojan
if [ "$type" = "trojan" ]; then
config_get "grpc_service_name" "$section" "grpc_service_name" ""
config_get "obfs_trojan" "$section" "obfs_trojan" ""
config_get "trojan_ws_path" "$section" "trojan_ws_path" ""
config_get "trojan_ws_headers" "$section" "trojan_ws_headers" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1467,6 +1517,11 @@ fi
#snell
if [ "$type" = "snell" ]; then
config_get "psk" "$section" "psk" ""
config_get "snell_version" "$section" "snell_version" ""
config_get "obfs_snell" "$section" "obfs_snell" ""
config_get "host" "$section" "host" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1490,6 +1545,13 @@ fi
#Sudoku
if [ "$type" = "sudoku" ]; then
config_get "sudoku_key" "$section" "sudoku_key" ""
config_get "aead_method" "$section" "aead_method" "none"
config_get "padding_min" "$section" "padding_min" ""
config_get "padding_max" "$section" "padding_max" ""
config_get "table_type" "$section" "table_type" "prefer_ascii"
config_get "http_mask" "$section" "http_mask" "true"
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
@@ -1528,6 +1590,132 @@ EOF
fi
fi
#MASQUE
if [ "$type" = "masque" ]; then
config_get "masque_private_key" "$section" "masque_private_key" ""
config_get "masque_public_key" "$section" "masque_public_key" ""
config_get "masque_ip" "$section" "masque_ip" ""
config_get "masque_ipv6" "$section" "masque_ipv6" ""
config_get "masque_mtu" "$section" "masque_mtu" ""
config_get "masque_remote_dns_resolve" "$section" "masque_remote_dns_resolve" ""
config_get "masque_dns" "$section" "masque_dns" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
server: "$server"
port: $port
EOF
if [ -n "$masque_private_key" ]; then
cat >> "$SERVER_FILE" <<-EOF
private-key: "$masque_private_key"
EOF
fi
if [ -n "$masque_public_key" ]; then
cat >> "$SERVER_FILE" <<-EOF
public-key: "$masque_public_key"
EOF
fi
if [ -n "$masque_ip" ]; then
cat >> "$SERVER_FILE" <<-EOF
ip: "$masque_ip"
EOF
fi
if [ -n "$masque_ipv6" ]; then
cat >> "$SERVER_FILE" <<-EOF
ipv6: "$masque_ipv6"
EOF
fi
if [ -n "$masque_mtu" ]; then
cat >> "$SERVER_FILE" <<-EOF
mtu: $masque_mtu
EOF
fi
if [ -n "$masque_remote_dns_resolve" ]; then
cat >> "$SERVER_FILE" <<-EOF
remote-dns-resolve: $masque_remote_dns_resolve
EOF
fi
if [ ! -z "$udp" ]; then
cat >> "$SERVER_FILE" <<-EOF
udp: $udp
EOF
fi
if [ ! -z "$masque_dns" ]; then
cat >> "$SERVER_FILE" <<-EOF
dns:
EOF
config_list_foreach "$section" "masque_dns" set_alpn
fi
fi
#TrustTunnel
if [ "$type" = "trusttunnel" ]; then
config_get "trusttunnel_username" "$section" "trusttunnel_username" ""
config_get "trusttunnel_password" "$section" "trusttunnel_password" ""
config_get "trusttunnel_health_check" "$section" "trusttunnel_health_check" ""
config_get "trusttunnel_quic" "$section" "trusttunnel_quic" ""
config_get "trusttunnel_congestion_controller" "$section" "trusttunnel_congestion_controller" ""
cat >> "$SERVER_FILE" <<-EOF
- name: "$name"
type: $type
server: "$server"
port: $port
EOF
if [ -n "$trusttunnel_username" ]; then
cat >> "$SERVER_FILE" <<-EOF
username: "$trusttunnel_username"
EOF
fi
if [ -n "$trusttunnel_password" ]; then
cat >> "$SERVER_FILE" <<-EOF
password: "$trusttunnel_password"
EOF
fi
if [ -n "$trusttunnel_health_check" ]; then
cat >> "$SERVER_FILE" <<-EOF
health-check: $trusttunnel_health_check
EOF
fi
if [ -n "$trusttunnel_quic" ]; then
cat >> "$SERVER_FILE" <<-EOF
quic: $trusttunnel_quic
EOF
fi
if [ -n "$trusttunnel_congestion_controller" ]; then
cat >> "$SERVER_FILE" <<-EOF
congestion-controller: "$trusttunnel_congestion_controller"
EOF
fi
if [ -n "$client_fingerprint" ]; then
cat >> "$SERVER_FILE" <<-EOF
client-fingerprint: "$client_fingerprint"
EOF
fi
if [ -n "$udp" ]; then
cat >> "$SERVER_FILE" <<-EOF
udp: $udp
EOF
fi
if [ -n "$sni" ]; then
cat >> "$SERVER_FILE" <<-EOF
sni: "$sni"
EOF
fi
if [ ! -z "$alpn" ]; then
cat >> "$SERVER_FILE" <<-EOF
alpn:
EOF
config_list_foreach "$section" "alpn" set_alpn
fi
if [ ! -z "$skip_cert_verify" ]; then
cat >> "$SERVER_FILE" <<-EOF
skip-cert-verify: $skip_cert_verify
EOF
fi
fi
#ip_version
if [ ! -z "$ip_version" ]; then
cat >> "$SERVER_FILE" <<-EOF
@@ -21,6 +21,9 @@ yml_other_set()
begin
thread_pool = [];
# GEOIP replace
geoip_pattern = /GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
match_pattern = /(^MATCH.*|^FINAL.*)/;
thread_pool << Thread.new{
#BT/P2P DIRECT Rules
begin
@@ -102,10 +105,6 @@ yml_other_set()
end;
end;
# GEOIP replace
geoip_pattern = /GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
match_pattern = /(^MATCH.*|^FINAL.*)/;
Value['rules'].to_a.collect!{|x|
x.to_s.gsub(geoip_pattern, 'GEOIP,\1,DIRECT\3').gsub(match_pattern, 'MATCH,DIRECT')
};
@@ -136,6 +135,18 @@ yml_other_set()
{ file: '/etc/openclash/custom/openclash_custom_rules_2.list', position: 'bottom' }
];
# 在开始对 custom_files 进行插入之前,记录当前 rules 中 GEOIP、MATCH、DST-PORT,80 的原始规则文本(锚点)
anchors_orig = {}
if Value.has_key?('rules') and not Value['rules'].to_a.empty? then
anchors_orig[:dst80_rule] = Value['rules'].grep(/DST-PORT,80/).last
anchors_orig[:match_rule] = Value['rules'].grep(match_pattern).first
anchors_orig[:geo_rule] = Value['rules'].grep(geoip_pattern).first
else
anchors_orig[:dst80_rule] = nil
anchors_orig[:match_rule] = nil
anchors_orig[:geo_rule] = nil
end;
custom_files.each{|file_info|
if File::exist?(file_info[:file]) then
custom_data = YAML.load_file(file_info[:file]);
@@ -150,6 +161,13 @@ yml_other_set()
[]
end;
rule_providers_array = case custom_data.class.to_s
when 'Hash'
custom_data['rule-providers'].to_a if custom_data['rule-providers'].class.to_s == 'Hash'
else
[]
end;
next unless rules_array;
ipv4_regex = /^(\d{1,3}\.){3}\d{1,3}$/;
@@ -157,9 +175,10 @@ yml_other_set()
cidr_regex = /\/\d+$/;
rule_suffix_regex = /^no-resolve$|^src$/;
ip_rule_types = ['IP-CIDR', 'IP-CIDR6', 'SRC-IP-CIDR', 'SRC-IP-CIDR6', 'IP-SUFFIX', 'SRC-IP-SUFFIX'];
transformed_rules = rules_array.map{|x|
parts = x.split(',');
if parts.length >= 2 then
if parts.length >= 2 && ip_rule_types.include?(parts[0].strip.upcase) then
ip_part = parts[1].strip;
if ip_part !~ cidr_regex then
# IPv4
@@ -194,11 +213,15 @@ yml_other_set()
if file_info[:position] == 'top' then
valid_rules.reverse.each{|x| Value['rules'].insert(0,x)};
else
if Value['rules'].grep(/GEOIP/)[0].nil? or Value['rules'].grep(/GEOIP/)[0].empty? then
ruby_add_index = Value['rules'].index(Value['rules'].grep(/DST-PORT,80/).last);
ruby_add_index ||= Value['rules'].index(Value['rules'].grep(/(MATCH|FINAL)/).first);
else
ruby_add_index = Value['rules'].index(Value['rules'].grep(/GEOIP/).first);
ruby_add_index = nil;
if anchors_orig[:dst80_rule]
ruby_add_index = Value['rules'].rindex(anchors_orig[:dst80_rule])
end;
if ruby_add_index.nil? and anchors_orig[:match_rule]
ruby_add_index = Value['rules'].rindex(anchors_orig[:match_rule])
end;
if ruby_add_index.nil? and anchors_orig[:geo_rule]
ruby_add_index = Value['rules'].rindex(anchors_orig[:geo_rule])
end;
ruby_add_index ||= -1;
@@ -209,6 +232,11 @@ yml_other_set()
else
Value['rules'] = valid_rules.uniq;
end;
if rule_providers_array and not rule_providers_array.empty? then
Value['rule-providers'] ||= {};
Value['rule-providers'] = Value['rule-providers'].merge!(custom_data['rule-providers']);
end;
end;
};
@@ -256,19 +284,13 @@ yml_other_set()
thread_pool << Thread.new{
threads = [];
#provider path
#provider CDN
begin
provider_configs = {'proxy-providers' => 'proxy_provider', 'rule-providers' => 'rule_provider'};
provider_configs.each do |provider_type, path_prefix|
if Value.key?(provider_type) then
if Value.key?(provider_type) && Value[provider_type].is_a?(Hash) then
Value[provider_type].each{|name, config|
threads << Thread.new {
if config['path'] and not config['path'] =~ /.\/#{path_prefix}\/*/ then
config['path'] = './'+path_prefix+'/'+File.basename(config['path']);
elsif not config['path'] and config['type'] == 'http' then
config['path'] = './'+path_prefix+'/'+name;
end;
# CDN
if '$github_address_mod' != '0' and config['url'] then
if config['url'] =~ /^https:\/\/raw.githubusercontent.com/ then
@@ -293,12 +315,12 @@ yml_other_set()
end;
end;
rescue Exception => e
YAML.LOG_ERROR('Edit Provider Path Failed,【' + e.message + '】');
YAML.LOG_ERROR('Edit Provider CDN Failed,【' + e.message + '】');
end;
# tolerance
begin
if '$tolerance' != '0' and Value.key?('proxy-groups') then
if '$tolerance' != '0' and Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
Value['proxy-groups'].each{|group|
threads << Thread.new {
if group['type'] == 'url-test' then
@@ -314,7 +336,7 @@ yml_other_set()
# URL-Test interval
begin
if '$urltest_interval_mod' != '0' then
if Value.key?('proxy-groups') then
if Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
Value['proxy-groups'].each{|group|
threads << Thread.new {
if ['url-test', 'fallback', 'load-balance', 'smart'].include?(group['type']) then
@@ -349,7 +371,7 @@ yml_other_set()
};
};
end;
if Value.key?('proxy-groups') then
if Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
Value['proxy-groups'].each{|group|
threads << Thread.new {
if ['url-test', 'fallback', 'load-balance', 'smart'].include?(group['type']) then
@@ -365,7 +387,7 @@ yml_other_set()
# smart auto switch
begin
if ('${8}' == '1' or '${9}' == '1' or '${11}' != '0' or '${12}' != '0' or '${12}' == '1' or '${13}' == '1') and Value.key?('proxy-groups') then
if ('${8}' == '1' or '${9}' == '1' or '${11}' != '0' or '${12}' != '0' or '${12}' == '1' or '${13}' == '1') and Value.key?('proxy-groups') and Value['proxy-groups'].is_a?(Array) then
Value['proxy-groups'].each{|group|
threads << Thread.new {
if '${8}' == '1' and ['url-test', 'load-balance'].include?(group['type']) then
@@ -1,2 +1,2 @@
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsyaml={})}(this,(function(e){"use strict";function t(e){return null==e}var n={isNothing:t,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:t(e)?[]:[e]},repeat:function(e,t){var n,i="";for(n=0;n<t;n+=1)i+=e;return i},isNegativeZero:function(e){return 0===e&&Number.NEGATIVE_INFINITY===1/e},extend:function(e,t){var n,i,r,o;if(t)for(n=0,i=(o=Object.keys(t)).length;n<i;n+=1)e[r=o[n]]=t[r];return e}};function i(e,t){var n="",i=e.reason||"(unknown reason)";return e.mark?(e.mark.name&&(n+='in "'+e.mark.name+'" '),n+="("+(e.mark.line+1)+":"+(e.mark.column+1)+")",!t&&e.mark.snippet&&(n+="\n\n"+e.mark.snippet),i+" "+n):i}function r(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=i(this,!1),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r.prototype.toString=function(e){return this.name+": "+i(this,e)};var o=r;function a(e,t,n,i,r){var o="",a="",l=Math.floor(r/2)-1;return i-t>l&&(t=i-l+(o=" ... ").length),n-i>l&&(n=i+l-(a=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+a,pos:i-t+o.length}}function l(e,t){return n.repeat(" ",t-e.length)+e}var c=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,r=/\r?\n|\r|\0/g,o=[0],c=[],s=-1;i=r.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&s<0&&(s=o.length-2);s<0&&(s=o.length-1);var u,p,f="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(u=1;u<=t.linesBefore&&!(s-u<0);u++)p=a(e.buffer,o[s-u],c[s-u],e.position-(o[s]-o[s-u]),h),f=n.repeat(" ",t.indent)+l((e.line-u+1).toString(),d)+" | "+p.str+"\n"+f;for(p=a(e.buffer,o[s],c[s],e.position,h),f+=n.repeat(" ",t.indent)+l((e.line+1).toString(),d)+" | "+p.str+"\n",f+=n.repeat("-",t.indent+d+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(s+u>=c.length);u++)p=a(e.buffer,o[s+u],c[s+u],e.position-(o[s]-o[s+u]),h),f+=n.repeat(" ",t.indent)+l((e.line+u+1).toString(),d)+" | "+p.str+"\n";return f.replace(/\n$/,"")},s=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],u=["scalar","sequence","mapping"];var p=function(e,t){if(t=t||{},Object.keys(t).forEach((function(t){if(-1===s.indexOf(t))throw new o('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach((function(n){e[n].forEach((function(e){t[String(e)]=n}))})),t}(t.styleAliases||null),-1===u.indexOf(this.kind))throw new o('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function f(e,t){var n=[];return e[t].forEach((function(e){var t=n.length;n.forEach((function(n,i){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=i)})),n[t]=e})),n}function d(e){return this.extend(e)}d.prototype.extend=function(e){var t=[],n=[];if(e instanceof p)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new o("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new o("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new o("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),n.forEach((function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var i=Object.create(d.prototype);return i.implicit=(this.implicit||[]).concat(t),i.explicit=(this.explicit||[]).concat(n),i.compiledImplicit=f(i,"implicit"),i.compiledExplicit=f(i,"explicit"),i.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function i(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.Line truncated
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsyaml={})}(this,function(e){"use strict";function t(e){return null==e}var n={isNothing:t,isObject:function(e){return"object"==typeof e&&null!==e},toArray:function(e){return Array.isArray(e)?e:t(e)?[]:[e]},repeat:function(e,t){var n,i="";for(n=0;n<t;n+=1)i+=e;return i},isNegativeZero:function(e){return 0===e&&Number.NEGATIVE_INFINITY===1/e},extend:function(e,t){var n,i,r,o;if(t)for(n=0,i=(o=Object.keys(t)).length;n<i;n+=1)e[r=o[n]]=t[r];return e}};function i(e,t){var n="",i=e.reason||"(unknown reason)";return e.mark?(e.mark.name&&(n+='in "'+e.mark.name+'" '),n+="("+(e.mark.line+1)+":"+(e.mark.column+1)+")",!t&&e.mark.snippet&&(n+="\n\n"+e.mark.snippet),i+" "+n):i}function r(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=i(this,!1),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r.prototype.toString=function(e){return this.name+": "+i(this,e)};var o=r;function a(e,t,n,i,r){var o="",a="",l=Math.floor(r/2)-1;return i-t>l&&(t=i-l+(o=" ... ").length),n-i>l&&(n=i+l-(a=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+a,pos:i-t+o.length}}function l(e,t){return n.repeat(" ",t-e.length)+e}var c=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,r=/\r?\n|\r|\0/g,o=[0],c=[],s=-1;i=r.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&s<0&&(s=o.length-2);s<0&&(s=o.length-1);var u,p,f="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(u=1;u<=t.linesBefore&&!(s-u<0);u++)p=a(e.buffer,o[s-u],c[s-u],e.position-(o[s]-o[s-u]),h),f=n.repeat(" ",t.indent)+l((e.line-u+1).toString(),d)+" | "+p.str+"\n"+f;for(p=a(e.buffer,o[s],c[s],e.position,h),f+=n.repeat(" ",t.indent)+l((e.line+1).toString(),d)+" | "+p.str+"\n",f+=n.repeat("-",t.indent+d+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(s+u>=c.length);u++)p=a(e.buffer,o[s+u],c[s+u],e.position-(o[s]-o[s+u]),h),f+=n.repeat(" ",t.indent)+l((e.line+u+1).toString(),d)+" | "+p.str+"\n";return f.replace(/\n$/,"")},s=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],u=["scalar","sequence","mapping"];var p=function(e,t){if(t=t||{},Object.keys(t).forEach(function(t){if(-1===s.indexOf(t))throw new o('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')}),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=function(e){var t={};return null!==e&&Object.keys(e).forEach(function(n){e[n].forEach(function(e){t[String(e)]=n})}),t}(t.styleAliases||null),-1===u.indexOf(this.kind))throw new o('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')};function f(e,t){var n=[];return e[t].forEach(function(e){var t=n.length;n.forEach(function(n,i){n.tag===e.tag&&n.kind===e.kind&&n.multi===e.multi&&(t=i)}),n[t]=e}),n}function d(e){return this.extend(e)}d.prototype.extend=function(e){var t=[],n=[];if(e instanceof p)n.push(e);else if(Array.isArray(e))n=n.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new o("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(n=n.concat(e.explicit))}t.forEach(function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new o("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new o("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")}),n.forEach(function(e){if(!(e instanceof p))throw new o("Specified list of YAML types (or a single Type object) contains a non-Type object.")});var i=Object.create(d.prototype);return i.implicit=(this.implicit||[]).concat(t),i.explicit=(this.explicit||[]).concat(n),i.compiledImplicit=f(i,"implicit"),i.compiledExplicit=f(i,"explicit"),i.compiledTypeMap=function(){var e,t,n={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function i(e){e.multi?(n.multi[e.kind].push(e),n.multi.fallback.push(e)):n[e.kiLine truncated
@@ -1,49 +1,55 @@
(function(){function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=32}
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[0,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);var f=this.diff_commonSuffix(a,b),g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a,
b,e,d);c&&a.unshift([0,c]);g&&a.push([0,g]);this.diff_cleanupMerge(a);return a};
diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[1,b]];if(!b)return[[-1,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[1,e.substring(0,g)],[0,f],[1,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=-1),c):1==f.length?[[-1,a],[1,b]]:(e=this.diff_halfMatch_(a,b))?(f=e[0],a=e[1],g=e[2],b=e[3],e=e[4],f=this.diff_main(f,g,c,d),c=this.diff_main(a,b,c,d),f.concat([[0,e]],c)):c&&100<a.length&&100<b.length?this.diff_lineMode_(a,b,
d):this.diff_bisect_(a,b,d)};
diff_match_patch.prototype.diff_lineMode_=function(a,b,c){var d=this.diff_linesToChars_(a,b);a=d.chars1;b=d.chars2;d=d.lineArray;a=this.diff_main(a,b,!1,c);this.diff_charsToLines_(a,d);this.diff_cleanupSemantic(a);a.push([0,""]);for(var e=d=b=0,f="",g="";b<a.length;){switch(a[b][0]){case 1:e++;g+=a[b][1];break;case -1:d++;f+=a[b][1];break;case 0:if(1<=d&&1<=e){a.splice(b-d-e,d+e);b=b-d-e;d=this.diff_main(f,g,!1,c);for(e=d.length-1;0<=e;e--)a.splice(b,0,d[e]);b+=d.length}d=e=0;g=f=""}b++}a.pop();return a};
diff_match_patch.prototype.diff_bisect_=function(a,b,c){for(var d=a.length,e=b.length,f=Math.ceil((d+e)/2),g=f,h=2*f,j=Array(h),i=Array(h),k=0;k<h;k++)j[k]=-1,i[k]=-1;j[g+1]=0;i[g+1]=0;for(var k=d-e,q=0!=k%2,r=0,t=0,p=0,w=0,v=0;v<f&&!((new Date).getTime()>c);v++){for(var n=-v+r;n<=v-t;n+=2){var l=g+n,m;m=n==-v||n!=v&&j[l-1]<j[l+1]?j[l+1]:j[l-1]+1;for(var s=m-n;m<d&&s<e&&a.charAt(m)==b.charAt(s);)m++,s++;j[l]=m;if(m>d)t+=2;else if(s>e)r+=2;else if(q&&(l=g+k-n,0<=l&&l<h&&-1!=i[l])){var u=d-i[l];if(m>=
u)return this.diff_bisectSplit_(a,b,m,s,c)}}for(n=-v+p;n<=v-w;n+=2){l=g+n;u=n==-v||n!=v&&i[l-1]<i[l+1]?i[l+1]:i[l-1]+1;for(m=u-n;u<d&&m<e&&a.charAt(d-u-1)==b.charAt(e-m-1);)u++,m++;i[l]=u;if(u>d)w+=2;else if(m>e)p+=2;else if(!q&&(l=g+k-n,0<=l&&(l<h&&-1!=j[l])&&(m=j[l],s=g+m-l,u=d-u,m>=u)))return this.diff_bisectSplit_(a,b,m,s,c)}}return[[-1,a],[1,b]]};
var diff_match_patch=function(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=.5;this.Patch_Margin=4;this.Match_MaxBits=32},DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;diff_match_patch.Diff=function(a,b){this[0]=a;this[1]=b};diff_match_patch.Diff.prototype.length=2;diff_match_patch.Diff.prototype.toString=function(){return this[0]+","+this[1]};
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[new diff_match_patch.Diff(DIFF_EQUAL,a)]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);f=this.diff_commonSuffix(a,b);var g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,
b.length-f);a=this.diff_compute_(a,b,e,d);c&&a.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));g&&a.push(new diff_match_patch.Diff(DIFF_EQUAL,g));this.diff_cleanupMerge(a);return a};
diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[new diff_match_patch.Diff(DIFF_INSERT,b)];if(!b)return[new diff_match_patch.Diff(DIFF_DELETE,a)];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[new diff_match_patch.Diff(DIFF_INSERT,e.substring(0,g)),new diff_match_patch.Diff(DIFF_EQUAL,f),new diff_match_patch.Diff(DIFF_INSERT,e.substring(g+f.length))],a.length>b.length&&(c[0][0]=c[2][0]=DIFF_DELETE),c):1==f.length?[new diff_match_patch.Diff(DIFF_DELETE,
a),new diff_match_patch.Diff(DIFF_INSERT,b)]:(e=this.diff_halfMatch_(a,b))?(b=e[1],f=e[3],a=e[4],e=this.diff_main(e[0],e[2],c,d),c=this.diff_main(b,f,c,d),e.concat([new diff_match_patch.Diff(DIFF_EQUAL,a)],c)):c&&100<a.length&&100<b.length?this.diff_lineMode_(a,b,d):this.diff_bisect_(a,b,d)};
diff_match_patch.prototype.diff_lineMode_=function(a,b,c){var d=this.diff_linesToChars_(a,b);a=d.chars1;b=d.chars2;d=d.lineArray;a=this.diff_main(a,b,!1,c);this.diff_charsToLines_(a,d);this.diff_cleanupSemantic(a);a.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var e=d=b=0,f="",g="";b<a.length;){switch(a[b][0]){case DIFF_INSERT:e++;g+=a[b][1];break;case DIFF_DELETE:d++;f+=a[b][1];break;case DIFF_EQUAL:if(1<=d&&1<=e){a.splice(b-d-e,d+e);b=b-d-e;d=this.diff_main(f,g,!1,c);for(e=d.length-1;0<=e;e--)a.splice(b,
0,d[e]);b+=d.length}d=e=0;g=f=""}b++}a.pop();return a};
diff_match_patch.prototype.diff_bisect_=function(a,b,c){for(var d=a.length,e=b.length,f=Math.ceil((d+e)/2),g=2*f,h=Array(g),l=Array(g),k=0;k<g;k++)h[k]=-1,l[k]=-1;h[f+1]=0;l[f+1]=0;k=d-e;for(var m=0!=k%2,p=0,x=0,w=0,q=0,t=0;t<f&&!((new Date).getTime()>c);t++){for(var v=-t+p;v<=t-x;v+=2){var n=f+v;var r=v==-t||v!=t&&h[n-1]<h[n+1]?h[n+1]:h[n-1]+1;for(var y=r-v;r<d&&y<e&&a.charAt(r)==b.charAt(y);)r++,y++;h[n]=r;if(r>d)x+=2;else if(y>e)p+=2;else if(m&&(n=f+k-v,0<=n&&n<g&&-1!=l[n])){var u=d-l[n];if(r>=
u)return this.diff_bisectSplit_(a,b,r,y,c)}}for(v=-t+w;v<=t-q;v+=2){n=f+v;u=v==-t||v!=t&&l[n-1]<l[n+1]?l[n+1]:l[n-1]+1;for(r=u-v;u<d&&r<e&&a.charAt(d-u-1)==b.charAt(e-r-1);)u++,r++;l[n]=u;if(u>d)q+=2;else if(r>e)w+=2;else if(!m&&(n=f+k-v,0<=n&&n<g&&-1!=h[n]&&(r=h[n],y=f+r-n,u=d-u,r>=u)))return this.diff_bisectSplit_(a,b,r,y,c)}}return[new diff_match_patch.Diff(DIFF_DELETE,a),new diff_match_patch.Diff(DIFF_INSERT,b)]};
diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)};
diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;f<a.length-1;){f=a.indexOf("\n",c);-1==f&&(f=a.length-1);var r=a.substring(c,f+1),c=f+1;(e.hasOwnProperty?e.hasOwnProperty(r):void 0!==e[r])?b+=String.fromCharCode(e[r]):(b+=String.fromCharCode(g),e[r]=g,d[g++]=r)}return b}var d=[],e={};d[0]="";var f=c(a),g=c(b);return{chars1:f,chars2:g,lineArray:d}};
diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,g=-1,h=d.length;g<a.length-1;){g=a.indexOf("\n",c);-1==g&&(g=a.length-1);var l=a.substring(c,g+1);(e.hasOwnProperty?e.hasOwnProperty(l):void 0!==e[l])?b+=String.fromCharCode(e[l]):(h==f&&(l=a.substring(c),g=a.length),b+=String.fromCharCode(h),e[l]=h,d[h++]=l);c=g+1}return b}var d=[],e={};d[0]="";var f=4E4,g=c(a);f=65535;var h=c(b);return{chars1:g,chars2:h,lineArray:d}};
diff_match_patch.prototype.diff_charsToLines_=function(a,b){for(var c=0;c<a.length;c++){for(var d=a[c][1],e=[],f=0;f<d.length;f++)e[f]=b[d.charCodeAt(f)];a[c][1]=e.join("")}};diff_match_patch.prototype.diff_commonPrefix=function(a,b){if(!a||!b||a.charAt(0)!=b.charAt(0))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(f,e)==b.substring(f,e)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
diff_match_patch.prototype.diff_commonSuffix=function(a,b){if(!a||!b||a.charAt(a.length-1)!=b.charAt(b.length-1))return 0;for(var c=0,d=Math.min(a.length,b.length),e=d,f=0;c<e;)a.substring(a.length-e,a.length-f)==b.substring(b.length-e,b.length-f)?f=c=e:d=e,e=Math.floor((d-c)/2+c);return e};
diff_match_patch.prototype.diff_commonOverlap_=function(a,b){var c=a.length,d=b.length;if(0==c||0==d)return 0;c>d?a=a.substring(c-d):c<d&&(b=b.substring(0,c));c=Math.min(c,d);if(a==b)return c;for(var d=0,e=1;;){var f=a.substring(c-e),f=b.indexOf(f);if(-1==f)return d;e+=f;if(0==f||a.substring(c-e)==b.substring(0,e))d=e,e++}};
diff_match_patch.prototype.diff_halfMatch_=function(a,b){function c(a,b,c){for(var d=a.substring(c,c+Math.floor(a.length/4)),e=-1,g="",h,j,n,l;-1!=(e=b.indexOf(d,e+1));){var m=f.diff_commonPrefix(a.substring(c),b.substring(e)),s=f.diff_commonSuffix(a.substring(0,c),b.substring(0,e));g.length<s+m&&(g=b.substring(e-s,e)+b.substring(e,e+m),h=a.substring(0,c-s),j=a.substring(c+m),n=b.substring(0,e-s),l=b.substring(e+m))}return 2*g.length>=a.length?[h,j,n,l,g]:null}if(0>=this.Diff_Timeout)return null;
var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.length<d.length)return null;var f=this,g=c(d,e,Math.ceil(d.length/4)),d=c(d,e,Math.ceil(d.length/2)),h;if(!g&&!d)return null;h=d?g?g[4].length>d[4].length?g:d:d:g;var j;a.length>b.length?(g=h[0],d=h[1],e=h[2],j=h[3]):(e=h[0],j=h[1],g=h[2],d=h[3]);h=h[4];return[g,d,e,j,h]};
diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,j=0,i=0;f<a.length;)0==a[f][0]?(c[d++]=f,g=j,h=i,i=j=0,e=a[f][1]):(1==a[f][0]?j+=a[f][1].length:i+=a[f][1].length,e&&(e.length<=Math.max(g,h)&&e.length<=Math.max(j,i))&&(a.splice(c[d-1],0,[-1,e]),a[c[d-1]+1][0]=1,d--,d--,f=0<d?c[d-1]:-1,i=j=h=g=0,e=null,b=!0)),f++;b&&this.diff_cleanupMerge(a);this.diff_cleanupSemanticLossless(a);for(f=1;f<a.length;){if(-1==a[f-1][0]&&1==a[f][0]){b=a[f-1][1];c=a[f][1];
d=this.diff_commonOverlap_(b,c);e=this.diff_commonOverlap_(c,b);if(d>=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[0,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[0,b.substring(0,e)]),a[f-1][0]=1,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=-1,a[f+1][1]=b.substring(e),f++;f++}f++}};
diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_),c=g&&c.match(diff_match_patch.linebreakRegex_),d=h&&d.match(diff_match_patch.linebreakRegex_),i=c&&a.match(diff_match_patch.blanklineEndRegex_),j=d&&b.match(diff_match_patch.blanklineStartRegex_);
return i||j?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c<a.length-1;){if(0==a[c-1][0]&&0==a[c+1][0]){var d=a[c-1][1],e=a[c][1],f=a[c+1][1],g=this.diff_commonSuffix(d,e);if(g)var h=e.substring(e.length-g),d=d.substring(0,d.length-g),e=h+e.substring(0,e.length-g),f=h+f;for(var g=d,h=e,j=f,i=b(d,e)+b(e,f);e.charAt(0)===f.charAt(0);){var d=d+e.charAt(0),e=e.substring(1)+f.charAt(0),f=f.substring(1),k=b(d,e)+b(e,f);k>=i&&(i=k,g=d,h=e,j=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-1,1),c--),a[c][1]=
h,j?a[c+1][1]=j:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/;
diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,j=!1,i=!1;f<a.length;){if(0==a[f][0])a[f][1].length<this.Diff_EditCost&&(j||i)?(c[d++]=f,g=j,h=i,e=a[f][1]):(d=0,e=null),j=i=!1;else if(-1==a[f][0]?i=!0:j=!0,e&&(g&&h&&j&&i||e.length<this.Diff_EditCost/2&&3==g+h+j+i))a.splice(c[d-1],0,[-1,e]),a[c[d-1]+1][0]=1,d--,e=null,g&&h?(j=i=!0,d=0):(d--,f=0<d?c[d-1]:-1,j=i=!1),b=!0;f++}b&&this.diff_cleanupMerge(a)};
diff_match_patch.prototype.diff_cleanupMerge=function(a){a.push([0,""]);for(var b=0,c=0,d=0,e="",f="",g;b<a.length;)switch(a[b][0]){case 1:d++;f+=a[b][1];b++;break;case -1:c++;e+=a[b][1];b++;break;case 0:1<c+d?(0!==c&&0!==d&&(g=this.diff_commonPrefix(f,e),0!==g&&(0<b-c-d&&0==a[b-c-d-1][0]?a[b-c-d-1][1]+=f.substring(0,g):(a.splice(0,0,[0,f.substring(0,g)]),b++),f=f.substring(g),e=e.substring(g)),g=this.diff_commonSuffix(f,e),0!==g&&(a[b][1]=f.substring(f.length-g)+a[b][1],f=f.substring(0,f.length-
g),e=e.substring(0,e.length-g))),0===c?a.splice(b-d,c+d,[1,f]):0===d?a.splice(b-c,c+d,[-1,e]):a.splice(b-c-d,c+d,[-1,e],[1,f]),b=b-c-d+(c?1:0)+(d?1:0)+1):0!==b&&0==a[b-1][0]?(a[b-1][1]+=a[b][1],a.splice(b,1)):b++,c=d=0,f=e=""}""===a[a.length-1][1]&&a.pop();c=!1;for(b=1;b<a.length-1;)0==a[b-1][0]&&0==a[b+1][0]&&(a[b][1].substring(a[b][1].length-a[b-1][1].length)==a[b-1][1]?(a[b][1]=a[b-1][1]+a[b][1].substring(0,a[b][1].length-a[b-1][1].length),a[b+1][1]=a[b-1][1]+a[b+1][1],a.splice(b-1,1),c=!0):a[b][1].substring(0,
a[b+1][1].length)==a[b+1][1]&&(a[b-1][1]+=a[b+1][1],a[b][1]=a[b][1].substring(a[b+1][1].length)+a[b+1][1],a.splice(b+1,1),c=!0)),b++;c&&this.diff_cleanupMerge(a)};diff_match_patch.prototype.diff_xIndex=function(a,b){var c=0,d=0,e=0,f=0,g;for(g=0;g<a.length;g++){1!==a[g][0]&&(c+=a[g][1].length);-1!==a[g][0]&&(d+=a[g][1].length);if(c>b)break;e=c;f=d}return a.length!=g&&-1===a[g][0]?f:f+(b-e)};
diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=/</g,e=/>/g,f=/\n/g,g=0;g<a.length;g++){var h=a[g][0],j=a[g][1],j=j.replace(c,"&amp;").replace(d,"&lt;").replace(e,"&gt;").replace(f,"&para;<br>");switch(h){case 1:b[g]='<ins style="background:#e6ffe6;">'+j+"</ins>";break;case -1:b[g]='<del style="background:#ffe6e6;">'+j+"</del>";break;case 0:b[g]="<span>"+j+"</span>"}}return b.join("")};
diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;c<a.length;c++)1!==a[c][0]&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_text2=function(a){for(var b=[],c=0;c<a.length;c++)-1!==a[c][0]&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_levenshtein=function(a){for(var b=0,c=0,d=0,e=0;e<a.length;e++){var f=a[e][0],g=a[e][1];switch(f){case 1:c+=g.length;break;case -1:d+=g.length;break;case 0:b+=Math.max(c,d),d=c=0}}return b+=Math.max(c,d)};
diff_match_patch.prototype.diff_toDelta=function(a){for(var b=[],c=0;c<a.length;c++)switch(a[c][0]){case 1:b[c]="+"+encodeURI(a[c][1]);break;case -1:b[c]="-"+a[c][1].length;break;case 0:b[c]="="+a[c][1].length}return b.join("\t").replace(/%20/g," ")};
diff_match_patch.prototype.diff_fromDelta=function(a,b){for(var c=[],d=0,e=0,f=b.split(/\t/g),g=0;g<f.length;g++){var h=f[g].substring(1);switch(f[g].charAt(0)){case "+":try{c[d++]=[1,decodeURI(h)]}catch(j){throw Error("Illegal escape in diff_fromDelta: "+h);}break;case "-":case "=":var i=parseInt(h,10);if(isNaN(i)||0>i)throw Error("Invalid number in diff_fromDelta: "+h);h=a.substring(e,e+=i);"="==f[g].charAt(0)?c[d++]=[0,h]:c[d++]=[-1,h];break;default:if(f[g])throw Error("Invalid diff operation in diff_fromDelta: "+
f[g]);}}if(e!=a.length)throw Error("Delta length ("+e+") does not equal source text length ("+a.length+").");return c};diff_match_patch.prototype.match_main=function(a,b,c){if(null==a||null==b||null==c)throw Error("Null input. (match_main)");c=Math.max(0,Math.min(c,a.length));return a==b?0:a.length?a.substring(c,c+b.length)==b?c:this.match_bitap_(a,b,c):-1};
diff_match_patch.prototype.match_bitap_=function(a,b,c){function d(a,d){var e=a/b.length,g=Math.abs(c-d);return!f.Match_Distance?g?1:e:e+g/f.Match_Distance}if(b.length>this.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));for(var j=1<<b.length-1,h=-1,i,k,q=b.length+a.length,r,t=0;t<b.length;t++){i=0;for(k=q;i<k;)d(t,c+
k)<=g?i=k:q=k,k=Math.floor((q-i)/2+i);q=k;i=Math.max(1,c-k+1);var p=Math.min(c+k,a.length)+b.length;k=Array(p+2);for(k[p+1]=(1<<t)-1;p>=i;p--){var w=e[a.charAt(p-1)];k[p]=0===t?(k[p+1]<<1|1)&w:(k[p+1]<<1|1)&w|((r[p+1]|r[p])<<1|1)|r[p+1];if(k[p]&j&&(w=d(t,p-1),w<=g))if(g=w,h=p-1,h>c)i=Math.max(1,2*c-h);else break}if(d(t+1,c)>g)break;r=k}return h};
diff_match_patch.prototype.diff_commonOverlap_=function(a,b){var c=a.length,d=b.length;if(0==c||0==d)return 0;c>d?a=a.substring(c-d):c<d&&(b=b.substring(0,c));c=Math.min(c,d);if(a==b)return c;d=0;for(var e=1;;){var f=a.substring(c-e);f=b.indexOf(f);if(-1==f)return d;e+=f;if(0==f||a.substring(c-e)==b.substring(0,e))d=e,e++}};
diff_match_patch.prototype.diff_halfMatch_=function(a,b){function c(a,b,c){for(var d=a.substring(c,c+Math.floor(a.length/4)),e=-1,g="",h,k,l,m;-1!=(e=b.indexOf(d,e+1));){var p=f.diff_commonPrefix(a.substring(c),b.substring(e)),u=f.diff_commonSuffix(a.substring(0,c),b.substring(0,e));g.length<u+p&&(g=b.substring(e-u,e)+b.substring(e,e+p),h=a.substring(0,c-u),k=a.substring(c+p),l=b.substring(0,e-u),m=b.substring(e+p))}return 2*g.length>=a.length?[h,k,l,m,g]:null}if(0>=this.Diff_Timeout)return null;
var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.length<d.length)return null;var f=this,g=c(d,e,Math.ceil(d.length/4));d=c(d,e,Math.ceil(d.length/2));if(g||d)g=d?g?g[4].length>d[4].length?g:d:d:g;else return null;if(a.length>b.length){d=g[0];e=g[1];var h=g[2];var l=g[3]}else h=g[0],l=g[1],d=g[2],e=g[3];return[d,e,h,l,g[4]]};
diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,l=0,k=0;f<a.length;)a[f][0]==DIFF_EQUAL?(c[d++]=f,g=l,h=k,k=l=0,e=a[f][1]):(a[f][0]==DIFF_INSERT?l+=a[f][1].length:k+=a[f][1].length,e&&e.length<=Math.max(g,h)&&e.length<=Math.max(l,k)&&(a.splice(c[d-1],0,new diff_match_patch.Diff(DIFF_DELETE,e)),a[c[d-1]+1][0]=DIFF_INSERT,d--,d--,f=0<d?c[d-1]:-1,k=l=h=g=0,e=null,b=!0)),f++;b&&this.diff_cleanupMerge(a);this.diff_cleanupSemanticLossless(a);for(f=1;f<
a.length;){if(a[f-1][0]==DIFF_DELETE&&a[f][0]==DIFF_INSERT){b=a[f-1][1];c=a[f][1];d=this.diff_commonOverlap_(b,c);e=this.diff_commonOverlap_(c,b);if(d>=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,new diff_match_patch.Diff(DIFF_EQUAL,c.substring(0,d))),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,new diff_match_patch.Diff(DIFF_EQUAL,b.substring(0,e))),a[f-1][0]=DIFF_INSERT,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=DIFF_DELETE,
a[f+1][1]=b.substring(e),f++;f++}f++}};
diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_);c=g&&c.match(diff_match_patch.linebreakRegex_);d=h&&d.match(diff_match_patch.linebreakRegex_);var k=c&&a.match(diff_match_patch.blanklineEndRegex_),l=d&&b.match(diff_match_patch.blanklineStartRegex_);
return k||l?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c<a.length-1;){if(a[c-1][0]==DIFF_EQUAL&&a[c+1][0]==DIFF_EQUAL){var d=a[c-1][1],e=a[c][1],f=a[c+1][1],g=this.diff_commonSuffix(d,e);if(g){var h=e.substring(e.length-g);d=d.substring(0,d.length-g);e=h+e.substring(0,e.length-g);f=h+f}g=d;h=e;for(var l=f,k=b(d,e)+b(e,f);e.charAt(0)===f.charAt(0);){d+=e.charAt(0);e=e.substring(1)+f.charAt(0);f=f.substring(1);var m=b(d,e)+b(e,f);m>=k&&(k=m,g=d,h=e,l=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-
1,1),c--),a[c][1]=h,l?a[c+1][1]=l:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/;
diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,l=!1,k=!1;f<a.length;)a[f][0]==DIFF_EQUAL?(a[f][1].length<this.Diff_EditCost&&(l||k)?(c[d++]=f,g=l,h=k,e=a[f][1]):(d=0,e=null),l=k=!1):(a[f][0]==DIFF_DELETE?k=!0:l=!0,e&&(g&&h&&l&&k||e.length<this.Diff_EditCost/2&&3==g+h+l+k)&&(a.splice(c[d-1],0,new diff_match_patch.Diff(DIFF_DELETE,e)),a[c[d-1]+1][0]=DIFF_INSERT,d--,e=null,g&&h?(l=k=!0,d=0):(d--,f=0<d?c[d-1]:-1,l=k=!1),b=!0)),f++;b&&this.diff_cleanupMerge(a)};
diff_match_patch.prototype.diff_cleanupMerge=function(a){a.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var b=0,c=0,d=0,e="",f="",g;b<a.length;)switch(a[b][0]){case DIFF_INSERT:d++;f+=a[b][1];b++;break;case DIFF_DELETE:c++;e+=a[b][1];b++;break;case DIFF_EQUAL:1<c+d?(0!==c&&0!==d&&(g=this.diff_commonPrefix(f,e),0!==g&&(0<b-c-d&&a[b-c-d-1][0]==DIFF_EQUAL?a[b-c-d-1][1]+=f.substring(0,g):(a.splice(0,0,new diff_match_patch.Diff(DIFF_EQUAL,f.substring(0,g))),b++),f=f.substring(g),e=e.substring(g)),
g=this.diff_commonSuffix(f,e),0!==g&&(a[b][1]=f.substring(f.length-g)+a[b][1],f=f.substring(0,f.length-g),e=e.substring(0,e.length-g))),b-=c+d,a.splice(b,c+d),e.length&&(a.splice(b,0,new diff_match_patch.Diff(DIFF_DELETE,e)),b++),f.length&&(a.splice(b,0,new diff_match_patch.Diff(DIFF_INSERT,f)),b++),b++):0!==b&&a[b-1][0]==DIFF_EQUAL?(a[b-1][1]+=a[b][1],a.splice(b,1)):b++,c=d=0,f=e=""}""===a[a.length-1][1]&&a.pop();c=!1;for(b=1;b<a.length-1;)a[b-1][0]==DIFF_EQUAL&&a[b+1][0]==DIFF_EQUAL&&(a[b][1].substring(a[b][1].length-
a[b-1][1].length)==a[b-1][1]?(a[b][1]=a[b-1][1]+a[b][1].substring(0,a[b][1].length-a[b-1][1].length),a[b+1][1]=a[b-1][1]+a[b+1][1],a.splice(b-1,1),c=!0):a[b][1].substring(0,a[b+1][1].length)==a[b+1][1]&&(a[b-1][1]+=a[b+1][1],a[b][1]=a[b][1].substring(a[b+1][1].length)+a[b+1][1],a.splice(b+1,1),c=!0)),b++;c&&this.diff_cleanupMerge(a)};
diff_match_patch.prototype.diff_xIndex=function(a,b){var c=0,d=0,e=0,f=0,g;for(g=0;g<a.length;g++){a[g][0]!==DIFF_INSERT&&(c+=a[g][1].length);a[g][0]!==DIFF_DELETE&&(d+=a[g][1].length);if(c>b)break;e=c;f=d}return a.length!=g&&a[g][0]===DIFF_DELETE?f:f+(b-e)};
diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=/</g,e=/>/g,f=/\n/g,g=0;g<a.length;g++){var h=a[g][0],l=a[g][1].replace(c,"&amp;").replace(d,"&lt;").replace(e,"&gt;").replace(f,"&para;<br>");switch(h){case DIFF_INSERT:b[g]='<ins style="background:#e6ffe6;">'+l+"</ins>";break;case DIFF_DELETE:b[g]='<del style="background:#ffe6e6;">'+l+"</del>";break;case DIFF_EQUAL:b[g]="<span>"+l+"</span>"}}return b.join("")};
diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;c<a.length;c++)a[c][0]!==DIFF_INSERT&&(b[c]=a[c][1]);return b.join("")};diff_match_patch.prototype.diff_text2=function(a){for(var b=[],c=0;c<a.length;c++)a[c][0]!==DIFF_DELETE&&(b[c]=a[c][1]);return b.join("")};
diff_match_patch.prototype.diff_levenshtein=function(a){for(var b=0,c=0,d=0,e=0;e<a.length;e++){var f=a[e][1];switch(a[e][0]){case DIFF_INSERT:c+=f.length;break;case DIFF_DELETE:d+=f.length;break;case DIFF_EQUAL:b+=Math.max(c,d),d=c=0}}return b+=Math.max(c,d)};
diff_match_patch.prototype.diff_toDelta=function(a){for(var b=[],c=0;c<a.length;c++)switch(a[c][0]){case DIFF_INSERT:b[c]="+"+encodeURI(a[c][1]);break;case DIFF_DELETE:b[c]="-"+a[c][1].length;break;case DIFF_EQUAL:b[c]="="+a[c][1].length}return b.join("\t").replace(/%20/g," ")};
diff_match_patch.prototype.diff_fromDelta=function(a,b){for(var c=[],d=0,e=0,f=b.split(/\t/g),g=0;g<f.length;g++){var h=f[g].substring(1);switch(f[g].charAt(0)){case "+":try{c[d++]=new diff_match_patch.Diff(DIFF_INSERT,decodeURI(h))}catch(k){throw Error("Illegal escape in diff_fromDelta: "+h);}break;case "-":case "=":var l=parseInt(h,10);if(isNaN(l)||0>l)throw Error("Invalid number in diff_fromDelta: "+h);h=a.substring(e,e+=l);"="==f[g].charAt(0)?c[d++]=new diff_match_patch.Diff(DIFF_EQUAL,h):c[d++]=
new diff_match_patch.Diff(DIFF_DELETE,h);break;default:if(f[g])throw Error("Invalid diff operation in diff_fromDelta: "+f[g]);}}if(e!=a.length)throw Error("Delta length ("+e+") does not equal source text length ("+a.length+").");return c};diff_match_patch.prototype.match_main=function(a,b,c){if(null==a||null==b||null==c)throw Error("Null input. (match_main)");c=Math.max(0,Math.min(c,a.length));return a==b?0:a.length?a.substring(c,c+b.length)==b?c:this.match_bitap_(a,b,c):-1};
diff_match_patch.prototype.match_bitap_=function(a,b,c){function d(a,d){var e=a/b.length,g=Math.abs(c-d);return f.Match_Distance?e+g/f.Match_Distance:g?1:e}if(b.length>this.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));var l=1<<b.length-1;h=-1;for(var k,m,p=b.length+a.length,x,w=0;w<b.length;w++){k=0;for(m=p;k<m;)d(w,
c+m)<=g?k=m:p=m,m=Math.floor((p-k)/2+k);p=m;k=Math.max(1,c-m+1);var q=Math.min(c+m,a.length)+b.length;m=Array(q+2);for(m[q+1]=(1<<w)-1;q>=k;q--){var t=e[a.charAt(q-1)];m[q]=0===w?(m[q+1]<<1|1)&t:(m[q+1]<<1|1)&t|(x[q+1]|x[q])<<1|1|x[q+1];if(m[q]&l&&(t=d(w,q-1),t<=g))if(g=t,h=q-1,h>c)k=Math.max(1,2*c-h);else break}if(d(w+1,c)>g)break;x=m}return h};
diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c<a.length;c++)b[a.charAt(c)]=0;for(c=0;c<a.length;c++)b[a.charAt(c)]|=1<<a.length-c-1;return b};
diff_match_patch.prototype.patch_addContext_=function(a,b){if(0!=b.length){for(var c=b.substring(a.start2,a.start2+a.length1),d=0;b.indexOf(c)!=b.lastIndexOf(c)&&c.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)d+=this.Patch_Margin,c=b.substring(a.start2-d,a.start2+a.length1+d);d+=this.Patch_Margin;(c=b.substring(a.start2-d,a.start2))&&a.diffs.unshift([0,c]);(d=b.substring(a.start2+a.length1,a.start2+a.length1+d))&&a.diffs.push([0,d]);a.start1-=c.length;a.start2-=c.length;a.length1+=
c.length+d.length;a.length2+=c.length+d.length}};
diff_match_patch.prototype.patch_make=function(a,b,c){var d;if("string"==typeof a&&"string"==typeof b&&"undefined"==typeof c)d=a,b=this.diff_main(d,b,!0),2<b.length&&(this.diff_cleanupSemantic(b),this.diff_cleanupEfficiency(b));else if(a&&"object"==typeof a&&"undefined"==typeof b&&"undefined"==typeof c)b=a,d=this.diff_text1(b);else if("string"==typeof a&&b&&"object"==typeof b&&"undefined"==typeof c)d=a;else if("string"==typeof a&&"string"==typeof b&&c&&"object"==typeof c)d=a,b=c;else throw Error("Unknown call format to patch_make.");
if(0===b.length)return[];c=[];a=new diff_match_patch.patch_obj;for(var e=0,f=0,g=0,h=d,j=0;j<b.length;j++){var i=b[j][0],k=b[j][1];!e&&0!==i&&(a.start1=f,a.start2=g);switch(i){case 1:a.diffs[e++]=b[j];a.length2+=k.length;d=d.substring(0,g)+k+d.substring(g);break;case -1:a.length1+=k.length;a.diffs[e++]=b[j];d=d.substring(0,g)+d.substring(g+k.length);break;case 0:k.length<=2*this.Patch_Margin&&e&&b.length!=j+1?(a.diffs[e++]=b[j],a.length1+=k.length,a.length2+=k.length):k.length>=2*this.Patch_Margin&&
e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}1!==i&&(f+=k.length);-1!==i&&(g+=k.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c],e=new diff_match_patch.patch_obj;e.diffs=[];for(var f=0;f<d.diffs.length;f++)e.diffs[f]=d.diffs[f].slice();e.start1=d.start1;e.start2=d.start2;e.length1=d.length1;e.length2=d.length2;b[c]=e}return b};
diff_match_patch.prototype.patch_apply=function(a,b){if(0==a.length)return[b,[]];a=this.patch_deepCopy(a);var c=this.patch_addPadding(a);b=c+b+c;this.patch_splitMax(a);for(var d=0,e=[],f=0;f<a.length;f++){var g=a[f].start2+d,h=this.diff_text1(a[f].diffs),j,i=-1;if(h.length>this.Match_MaxBits){if(j=this.match_main(b,h.substring(0,this.Match_MaxBits),g),-1!=j&&(i=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==i||j>=i))j=-1}else j=this.match_main(b,h,g);
if(-1==j)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=j-g,g=-1==i?b.substring(j,j+h.length):b.substring(j,i+this.Match_MaxBits),h==g)b=b.substring(0,j)+this.diff_text2(a[f].diffs)+b.substring(j+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);for(var h=0,k,i=0;i<a[f].diffs.length;i++){var q=a[f].diffs[i];0!==q[0]&&(k=this.diff_xIndex(g,h));1===q[0]?b=b.substring(0,
j+k)+q[1]+b.substring(j+k):-1===q[0]&&(b=b.substring(0,j+k)+b.substring(j+this.diff_xIndex(g,h+q[1].length)));-1!==q[0]&&(h+=q[1].length)}}}b=b.substring(c.length,b.length-c.length);return[b,e]};
diff_match_patch.prototype.patch_addPadding=function(a){for(var b=this.Patch_Margin,c="",d=1;d<=b;d++)c+=String.fromCharCode(d);for(d=0;d<a.length;d++)a[d].start1+=b,a[d].start2+=b;var d=a[0],e=d.diffs;if(0==e.length||0!=e[0][0])e.unshift([0,c]),d.start1-=b,d.start2-=b,d.length1+=b,d.length2+=b;else if(b>e[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||0!=e[e.length-1][0]?(e.push([0,
c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c};
diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c<a.length;c++)if(!(a[c].length1<=b)){var d=a[c];a.splice(c--,1);for(var e=d.start1,f=d.start2,g="";0!==d.diffs.length;){var h=new diff_match_patch.patch_obj,j=!0;h.start1=e-g.length;h.start2=f-g.length;""!==g&&(h.length1=h.length2=g.length,h.diffs.push([0,g]));for(;0!==d.diffs.length&&h.length1<b-this.Patch_Margin;){var g=d.diffs[0][0],i=d.diffs[0][1];1===g?(h.length2+=i.length,f+=i.length,h.diffs.push(d.diffs.shift()),
j=!1):-1===g&&1==h.diffs.length&&0==h.diffs[0][0]&&i.length>2*b?(h.length1+=i.length,e+=i.length,j=!1,h.diffs.push([g,i]),d.diffs.shift()):(i=i.substring(0,b-h.length1-this.Patch_Margin),h.length1+=i.length,e+=i.length,0===g?(h.length2+=i.length,f+=i.length):j=!1,h.diffs.push([g,i]),i==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(i.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);i=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==i&&
(h.length1+=i.length,h.length2+=i.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=i:h.diffs.push([0,i]));j||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=a[c];return b.join("")};
diff_match_patch.prototype.patch_addContext_=function(a,b){if(0!=b.length){if(null===a.start2)throw Error("patch not initialized");for(var c=b.substring(a.start2,a.start2+a.length1),d=0;b.indexOf(c)!=b.lastIndexOf(c)&&c.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)d+=this.Patch_Margin,c=b.substring(a.start2-d,a.start2+a.length1+d);d+=this.Patch_Margin;(c=b.substring(a.start2-d,a.start2))&&a.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));(d=b.substring(a.start2+a.length1,
a.start2+a.length1+d))&&a.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,d));a.start1-=c.length;a.start2-=c.length;a.length1+=c.length+d.length;a.length2+=c.length+d.length}};
diff_match_patch.prototype.patch_make=function(a,b,c){if("string"==typeof a&&"string"==typeof b&&"undefined"==typeof c){var d=a;b=this.diff_main(d,b,!0);2<b.length&&(this.diff_cleanupSemantic(b),this.diff_cleanupEfficiency(b))}else if(a&&"object"==typeof a&&"undefined"==typeof b&&"undefined"==typeof c)b=a,d=this.diff_text1(b);else if("string"==typeof a&&b&&"object"==typeof b&&"undefined"==typeof c)d=a;else if("string"==typeof a&&"string"==typeof b&&c&&"object"==typeof c)d=a,b=c;else throw Error("Unknown call format to patch_make.");
if(0===b.length)return[];c=[];a=new diff_match_patch.patch_obj;for(var e=0,f=0,g=0,h=d,l=0;l<b.length;l++){var k=b[l][0],m=b[l][1];e||k===DIFF_EQUAL||(a.start1=f,a.start2=g);switch(k){case DIFF_INSERT:a.diffs[e++]=b[l];a.length2+=m.length;d=d.substring(0,g)+m+d.substring(g);break;case DIFF_DELETE:a.length1+=m.length;a.diffs[e++]=b[l];d=d.substring(0,g)+d.substring(g+m.length);break;case DIFF_EQUAL:m.length<=2*this.Patch_Margin&&e&&b.length!=l+1?(a.diffs[e++]=b[l],a.length1+=m.length,a.length2+=m.length):
m.length>=2*this.Patch_Margin&&e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}k!==DIFF_INSERT&&(f+=m.length);k!==DIFF_DELETE&&(g+=m.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};
diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c],e=new diff_match_patch.patch_obj;e.diffs=[];for(var f=0;f<d.diffs.length;f++)e.diffs[f]=new diff_match_patch.Diff(d.diffs[f][0],d.diffs[f][1]);e.start1=d.start1;e.start2=d.start2;e.length1=d.length1;e.length2=d.length2;b[c]=e}return b};
diff_match_patch.prototype.patch_apply=function(a,b){if(0==a.length)return[b,[]];a=this.patch_deepCopy(a);var c=this.patch_addPadding(a);b=c+b+c;this.patch_splitMax(a);for(var d=0,e=[],f=0;f<a.length;f++){var g=a[f].start2+d,h=this.diff_text1(a[f].diffs),l=-1;if(h.length>this.Match_MaxBits){var k=this.match_main(b,h.substring(0,this.Match_MaxBits),g);-1!=k&&(l=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==l||k>=l)&&(k=-1)}else k=this.match_main(b,h,
g);if(-1==k)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=k-g,g=-1==l?b.substring(k,k+h.length):b.substring(k,l+this.Match_MaxBits),h==g)b=b.substring(0,k)+this.diff_text2(a[f].diffs)+b.substring(k+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);h=0;var m;for(l=0;l<a[f].diffs.length;l++){var p=a[f].diffs[l];p[0]!==DIFF_EQUAL&&(m=this.diff_xIndex(g,h));p[0]===
DIFF_INSERT?b=b.substring(0,k+m)+p[1]+b.substring(k+m):p[0]===DIFF_DELETE&&(b=b.substring(0,k+m)+b.substring(k+this.diff_xIndex(g,h+p[1].length)));p[0]!==DIFF_DELETE&&(h+=p[1].length)}}}b=b.substring(c.length,b.length-c.length);return[b,e]};
diff_match_patch.prototype.patch_addPadding=function(a){for(var b=this.Patch_Margin,c="",d=1;d<=b;d++)c+=String.fromCharCode(d);for(d=0;d<a.length;d++)a[d].start1+=b,a[d].start2+=b;d=a[0];var e=d.diffs;if(0==e.length||e[0][0]!=DIFF_EQUAL)e.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c)),d.start1-=b,d.start2-=b,d.length1+=b,d.length2+=b;else if(b>e[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;
0==e.length||e[e.length-1][0]!=DIFF_EQUAL?(e.push(new diff_match_patch.Diff(DIFF_EQUAL,c)),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c};
diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c<a.length;c++)if(!(a[c].length1<=b)){var d=a[c];a.splice(c--,1);for(var e=d.start1,f=d.start2,g="";0!==d.diffs.length;){var h=new diff_match_patch.patch_obj,l=!0;h.start1=e-g.length;h.start2=f-g.length;""!==g&&(h.length1=h.length2=g.length,h.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,g)));for(;0!==d.diffs.length&&h.length1<b-this.Patch_Margin;){g=d.diffs[0][0];var k=d.diffs[0][1];g===DIFF_INSERT?(h.length2+=
k.length,f+=k.length,h.diffs.push(d.diffs.shift()),l=!1):g===DIFF_DELETE&&1==h.diffs.length&&h.diffs[0][0]==DIFF_EQUAL&&k.length>2*b?(h.length1+=k.length,e+=k.length,l=!1,h.diffs.push(new diff_match_patch.Diff(g,k)),d.diffs.shift()):(k=k.substring(0,b-h.length1-this.Patch_Margin),h.length1+=k.length,e+=k.length,g===DIFF_EQUAL?(h.length2+=k.length,f+=k.length):l=!1,h.diffs.push(new diff_match_patch.Diff(g,k)),k==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(k.length))}g=this.diff_text2(h.diffs);
g=g.substring(g.length-this.Patch_Margin);k=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==k&&(h.length1+=k.length,h.length2+=k.length,0!==h.diffs.length&&h.diffs[h.diffs.length-1][0]===DIFF_EQUAL?h.diffs[h.diffs.length-1][1]+=k:h.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,k)));l||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=a[c];return b.join("")};
diff_match_patch.prototype.patch_fromText=function(a){var b=[];if(!a)return b;a=a.split("\n");for(var c=0,d=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;c<a.length;){var e=a[c].match(d);if(!e)throw Error("Invalid patch string: "+a[c]);var f=new diff_match_patch.patch_obj;b.push(f);f.start1=parseInt(e[1],10);""===e[2]?(f.start1--,f.length1=1):"0"==e[2]?f.length1=0:(f.start1--,f.length1=parseInt(e[2],10));f.start2=parseInt(e[3],10);""===e[4]?(f.start2--,f.length2=1):"0"==e[4]?f.length2=0:(f.start2--,f.length2=
parseInt(e[4],10));for(c++;c<a.length;){e=a[c].charAt(0);try{var g=decodeURI(a[c].substring(1))}catch(h){throw Error("Illegal escape in patch_fromText: "+g);}if("-"==e)f.diffs.push([-1,g]);else if("+"==e)f.diffs.push([1,g]);else if(" "==e)f.diffs.push([0,g]);else if("@"==e)break;else if(""!==e)throw Error('Invalid patch mode "'+e+'" in: '+g);c++}}return b};diff_match_patch.patch_obj=function(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0};
diff_match_patch.patch_obj.prototype.toString=function(){var a,b;a=0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1;b=0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2;a=["@@ -"+a+" +"+b+" @@\n"];var c;for(b=0;b<this.diffs.length;b++){switch(this.diffs[b][0]){case 1:c="+";break;case -1:c="-";break;case 0:c=" "}a[b+1]=c+encodeURI(this.diffs[b][1])+"\n"}return a.join("").replace(/%20/g," ")};
this.diff_match_patch=diff_match_patch;this.DIFF_DELETE=-1;this.DIFF_INSERT=1;this.DIFF_EQUAL=0;})()
parseInt(e[4],10));for(c++;c<a.length;){e=a[c].charAt(0);try{var g=decodeURI(a[c].substring(1))}catch(h){throw Error("Illegal escape in patch_fromText: "+g);}if("-"==e)f.diffs.push(new diff_match_patch.Diff(DIFF_DELETE,g));else if("+"==e)f.diffs.push(new diff_match_patch.Diff(DIFF_INSERT,g));else if(" "==e)f.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,g));else if("@"==e)break;else if(""!==e)throw Error('Invalid patch mode "'+e+'" in: '+g);c++}}return b};
diff_match_patch.patch_obj=function(){this.diffs=[];this.start2=this.start1=null;this.length2=this.length1=0};
diff_match_patch.patch_obj.prototype.toString=function(){for(var a=["@@ -"+(0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1)+" +"+(0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2)+" @@\n"],b,c=0;c<this.diffs.length;c++){switch(this.diffs[c][0]){case DIFF_INSERT:b="+";break;case DIFF_DELETE:b="-";break;case DIFF_EQUAL:b=" "}a[c+1]=b+encodeURI(this.diffs[c][1])+"\n"}return a.join("").replace(/%20/g," ")};
this.diff_match_patch=diff_match_patch;this.DIFF_DELETE=DIFF_DELETE;this.DIFF_INSERT=DIFF_INSERT;this.DIFF_EQUAL=DIFF_EQUAL;
@@ -166,7 +166,7 @@
function copyObj(obj, target, overwrite) {
if (!target) { target = {}; }
for (var prop in obj)
{ if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
{ if (Object.prototype.hasOwnProperty.call(obj, prop) && (overwrite !== false || !Object.prototype.hasOwnProperty.call(target, prop)))
{ target[prop] = obj[prop]; } }
return target
}
@@ -4426,7 +4426,7 @@
// The element in which the editor lives.
d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
// See #6982. FIXME remove when this has been fixed for a while in Chrome
if (chrome && chrome_version >= 105) { d.wrapper.style.clipPath = "inset(0px)"; }
if (chrome && chrome_version === 105) { d.wrapper.style.clipPath = "inset(0px)"; }
// This attribute is respected by automatic translation systems such as Google Translate,
// and may also be respected by tools used by human translators.
@@ -9877,7 +9877,7 @@
addLegacyProps(CodeMirror);
CodeMirror.version = "5.65.18";
CodeMirror.version = "5.65.21";
return CodeMirror;